diff --git a/src/cache.rs b/src/cache.rs index 59432bf..737cf9b 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -163,9 +163,28 @@ impl Cache { if !path.exists() { return Ok(None); } - let raw = fs::read_to_string(&path).map_err(|e| IdxError::Io(e.to_string()))?; - let entry: CacheEntry = - serde_json::from_str(&raw).map_err(|e| IdxError::ParseError(e.to_string()))?; + let raw = match fs::read_to_string(&path) { + Ok(s) => s, + Err(e) => { + eprintln!( + "warning: corrupted cache entry for {}/{}, treating as miss: {}", + data_type, symbol, e + ); + let _ = fs::remove_file(&path); + return Ok(None); + } + }; + let entry: CacheEntry = match serde_json::from_str(&raw) { + Ok(e) => e, + Err(e) => { + eprintln!( + "warning: corrupted cache entry for {}/{}, treating as miss: {}", + data_type, symbol, e + ); + let _ = fs::remove_file(&path); + return Ok(None); + } + }; if entry.schema_version != CURRENT_SCHEMA_VERSION { eprintln!( "debug: cache schema mismatch for {} (got {}, expected {})", @@ -240,4 +259,53 @@ mod tests { .expect("cache read stale"); assert_eq!(stale, Some(T { v: 7 })); } + + #[test] + fn corrupted_cache_entry_returns_none_and_deletes_file() { + let root = tmp(); + let cache = Cache::with_root(root.clone()); + + // Write invalid JSON to a cache file + let path = root.join("quote/CORRUPT.JK.json"); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).expect("create parent dir"); + } + fs::write(&path, "this is not valid json {{{").expect("write corrupted cache"); + + assert!(path.exists(), "corrupted file should exist before get()"); + + // get() should return Ok(None), not an error + let result: Option = cache.get("quote", "CORRUPT.JK").expect("get should not error"); + assert_eq!(result, None, "corrupted entry should be treated as miss"); + + // The corrupted file should be deleted + assert!(!path.exists(), "corrupted file should be deleted"); + } + + #[test] + fn corrupted_cache_entry_get_stale_returns_none_and_deletes_file() { + let root = tmp(); + let cache = Cache::with_root(root.clone()); + + // Write invalid JSON to a cache file + let path = root.join("quote/STALE_CORRUPT.JK.json"); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).expect("create parent dir"); + } + fs::write(&path, "{ not valid json at all").expect("write corrupted cache"); + + assert!(path.exists(), "corrupted file should exist before get_stale()"); + + // get_stale() should return Ok(None), not an error + let result: Option = cache + .get_stale("quote", "STALE_CORRUPT.JK") + .expect("get_stale should not error"); + assert_eq!( + result, None, + "corrupted entry should be treated as miss in get_stale" + ); + + // The corrupted file should be deleted + assert!(!path.exists(), "corrupted file should be deleted"); + } }