1use std::collections::HashMap;
31use std::fmt;
32
33#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum StoreError {
38 StoreFull,
40 KeyNotFound,
42 WriteFailure(String),
44}
45
46impl fmt::Display for StoreError {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 StoreError::StoreFull => write!(f, "backing store is full"),
50 StoreError::KeyNotFound => write!(f, "key not found in backing store"),
51 StoreError::WriteFailure(msg) => write!(f, "write failure: {msg}"),
52 }
53 }
54}
55
56impl std::error::Error for StoreError {}
57
58pub trait BackingStore<K, V> {
65 fn store(&mut self, key: &K, value: &V) -> Result<(), StoreError>;
67
68 fn load(&self, key: &K) -> Option<V>;
70
71 fn remove(&mut self, key: &K) -> bool;
75}
76
77#[derive(Debug, Clone, Default)]
83pub struct InMemoryStore<K, V>
84where
85 K: Eq + std::hash::Hash + Clone,
86 V: Clone,
87{
88 data: HashMap<K, V>,
89 max_entries: usize,
91 write_count: u64,
93 read_count: u64,
95}
96
97impl<K, V> InMemoryStore<K, V>
98where
99 K: Eq + std::hash::Hash + Clone,
100 V: Clone,
101{
102 pub fn new() -> Self {
104 Self {
105 data: HashMap::new(),
106 max_entries: 0,
107 write_count: 0,
108 read_count: 0,
109 }
110 }
111
112 pub fn with_capacity(max_entries: usize) -> Self {
114 Self {
115 data: HashMap::with_capacity(max_entries),
116 max_entries,
117 write_count: 0,
118 read_count: 0,
119 }
120 }
121
122 pub fn len(&self) -> usize {
124 self.data.len()
125 }
126
127 pub fn is_empty(&self) -> bool {
129 self.data.is_empty()
130 }
131
132 pub fn write_count(&self) -> u64 {
134 self.write_count
135 }
136
137 pub fn read_count(&self) -> u64 {
139 self.read_count
140 }
141}
142
143impl<K, V> BackingStore<K, V> for InMemoryStore<K, V>
144where
145 K: Eq + std::hash::Hash + Clone,
146 V: Clone,
147{
148 fn store(&mut self, key: &K, value: &V) -> Result<(), StoreError> {
149 self.write_count += 1;
150 if self.max_entries > 0
151 && self.data.len() >= self.max_entries
152 && !self.data.contains_key(key)
153 {
154 return Err(StoreError::StoreFull);
155 }
156 self.data.insert(key.clone(), value.clone());
157 Ok(())
158 }
159
160 fn load(&self, key: &K) -> Option<V> {
161 self.data.get(key).cloned()
162 }
163
164 fn remove(&mut self, key: &K) -> bool {
165 self.data.remove(key).is_some()
166 }
167}
168
169#[derive(Debug, Clone, Default)]
173pub struct WriteThroughStats {
174 pub cache_hits: u64,
176 pub backing_store_hits: u64,
178 pub misses: u64,
180 pub writes: u64,
184 pub write_errors: u64,
186}
187
188pub struct WriteThroughCache<K, V, S>
197where
198 K: Eq + std::hash::Hash + Clone,
199 V: Clone,
200 S: BackingStore<K, V>,
201{
202 capacity: usize,
203 cache: HashMap<K, V>,
204 insertion_order: Vec<K>,
206 store: S,
207 stats: WriteThroughStats,
208 _marker: std::marker::PhantomData<(K, V)>,
210}
211
212impl<K, V, S> WriteThroughCache<K, V, S>
213where
214 K: Eq + std::hash::Hash + Clone,
215 V: Clone,
216 S: BackingStore<K, V>,
217{
218 pub fn new(capacity: usize, store: S) -> Self {
225 assert!(capacity > 0, "WriteThroughCache capacity must be non-zero");
226 Self {
227 capacity,
228 cache: HashMap::with_capacity(capacity.min(1024)),
229 insertion_order: Vec::with_capacity(capacity.min(1024)),
230 store,
231 stats: WriteThroughStats::default(),
232 _marker: std::marker::PhantomData,
233 }
234 }
235
236 pub fn put(&mut self, key: K, value: V) -> Result<(), StoreError> {
244 if let Err(e) = self.store.store(&key, &value) {
247 self.stats.write_errors += 1;
248 return Err(e);
249 }
250
251 if !self.cache.contains_key(&key) && self.cache.len() >= self.capacity {
253 self.evict_oldest();
254 } else if self.cache.contains_key(&key) {
255 self.insertion_order.retain(|k| k != &key);
257 }
258
259 self.cache.insert(key.clone(), value);
260 self.insertion_order.push(key);
261 self.stats.writes += 1;
262 Ok(())
263 }
264
265 pub fn get(&mut self, key: &K) -> Option<&V> {
273 if self.cache.contains_key(key) {
274 self.stats.cache_hits += 1;
275 return self.cache.get(key);
276 }
277
278 match self.store.load(key) {
280 Some(value) => {
281 self.stats.backing_store_hits += 1;
282 if self.cache.len() >= self.capacity {
284 self.evict_oldest();
285 }
286 self.cache.insert(key.clone(), value);
287 self.insertion_order.push(key.clone());
288 self.cache.get(key)
289 }
290 None => {
291 self.stats.misses += 1;
292 None
293 }
294 }
295 }
296
297 pub fn invalidate(&mut self, key: &K) -> bool {
301 let in_cache = self.cache.remove(key).is_some();
302 if in_cache {
303 self.insertion_order.retain(|k| k != key);
304 }
305 let in_store = self.store.remove(key);
306 in_cache || in_store
307 }
308
309 pub fn stats(&self) -> WriteThroughStats {
311 self.stats.clone()
312 }
313
314 pub fn cache_len(&self) -> usize {
316 self.cache.len()
317 }
318
319 pub fn capacity(&self) -> usize {
321 self.capacity
322 }
323
324 pub fn backing_store(&self) -> &S {
326 &self.store
327 }
328
329 fn evict_oldest(&mut self) {
332 if self.insertion_order.is_empty() {
333 return;
334 }
335 let oldest = self.insertion_order.remove(0);
336 self.cache.remove(&oldest);
337 }
338}
339
340#[cfg(test)]
343mod tests {
344 use super::*;
345
346 fn make_cache(
347 cap: usize,
348 ) -> WriteThroughCache<String, Vec<u8>, InMemoryStore<String, Vec<u8>>> {
349 let store = InMemoryStore::new();
350 WriteThroughCache::new(cap, store)
351 }
352
353 #[test]
354 fn test_put_and_get_from_cache() {
355 let mut cache = make_cache(10);
356 cache.put("key1".to_string(), vec![1, 2, 3]).expect("put");
357 let v = cache.get(&"key1".to_string());
358 assert_eq!(v, Some(&vec![1u8, 2, 3]));
359 assert_eq!(cache.stats().cache_hits, 1);
360 }
361
362 #[test]
363 fn test_put_persists_to_backing_store() {
364 let mut cache = make_cache(10);
365 cache.put("k".to_string(), vec![9]).expect("put");
366 let loaded = cache.backing_store().load(&"k".to_string());
368 assert_eq!(loaded, Some(vec![9u8]));
369 }
370
371 #[test]
372 fn test_get_fallback_to_backing_store() {
373 let store = InMemoryStore::new();
374 let mut cache: WriteThroughCache<String, u32, InMemoryStore<String, u32>> =
375 WriteThroughCache::new(10, store);
376
377 cache.put("target".to_string(), 42u32).expect("put");
382
383 let store2 = InMemoryStore::new();
385 let mut cache2: WriteThroughCache<String, u32, InMemoryStore<String, u32>> =
386 WriteThroughCache::new(2, store2);
387 cache2.put("target".to_string(), 42).expect("put");
388 cache2.put("a".to_string(), 1).expect("put");
389 cache2.put("b".to_string(), 2).expect("put"); let v = cache2.get(&"target".to_string());
393 assert_eq!(v, Some(&42));
394 assert_eq!(cache2.stats().backing_store_hits, 1);
395 }
396
397 #[test]
398 fn test_invalidate_removes_from_both() {
399 let mut cache = make_cache(10);
400 cache.put("x".to_string(), vec![0]).expect("put");
401 let removed = cache.invalidate(&"x".to_string());
402 assert!(removed);
403 assert_eq!(cache.get(&"x".to_string()), None);
404 assert_eq!(cache.backing_store().load(&"x".to_string()), None);
405 }
406
407 #[test]
408 fn test_invalidate_absent_key_returns_false() {
409 let mut cache = make_cache(10);
410 assert!(!cache.invalidate(&"ghost".to_string()));
411 }
412
413 #[test]
414 fn test_stats_writes_and_errors() {
415 let store = InMemoryStore::<String, u32>::with_capacity(1);
416 let mut cache: WriteThroughCache<String, u32, InMemoryStore<String, u32>> =
417 WriteThroughCache::new(10, store);
418
419 cache.put("first".to_string(), 1).expect("first put ok");
420 let result = cache.put("second".to_string(), 2);
422 assert_eq!(result, Err(StoreError::StoreFull));
423
424 let s = cache.stats();
425 assert_eq!(s.writes, 1);
426 assert_eq!(s.write_errors, 1);
427 }
428
429 #[test]
430 fn test_miss_when_not_in_either() {
431 let mut cache = make_cache(10);
432 assert_eq!(cache.get(&"nonexistent".to_string()), None);
433 assert_eq!(cache.stats().misses, 1);
434 }
435
436 #[test]
437 fn test_capacity_eviction() {
438 let mut cache = make_cache(3);
439 cache.put("a".to_string(), vec![1]).expect("put");
440 cache.put("b".to_string(), vec![2]).expect("put");
441 cache.put("c".to_string(), vec![3]).expect("put");
442 cache.put("d".to_string(), vec![4]).expect("put");
444 assert_eq!(cache.stats().cache_hits, 0);
446 let v = cache.get(&"a".to_string());
447 assert_eq!(v, Some(&vec![1u8]));
448 assert_eq!(cache.stats().backing_store_hits, 1);
449 }
450
451 #[test]
452 fn test_overwrite_existing_key() {
453 let mut cache = make_cache(5);
454 cache.put("k".to_string(), vec![1]).expect("put");
455 cache.put("k".to_string(), vec![2]).expect("put"); assert_eq!(cache.get(&"k".to_string()), Some(&vec![2u8]));
457 assert_eq!(cache.cache_len(), 1);
458 }
459
460 #[test]
461 fn test_write_failure_does_not_pollute_cache() {
462 let store = InMemoryStore::<String, i32>::with_capacity(1);
464 let mut cache: WriteThroughCache<String, i32, InMemoryStore<String, i32>> =
465 WriteThroughCache::new(10, store);
466 cache.put("key1".to_string(), 1).expect("first ok");
468 let result = cache.put("key2".to_string(), 2);
470 assert!(result.is_err(), "second put should fail");
471 assert_eq!(cache.get(&"key2".to_string()), None);
473 }
474}