rh_foundation/memory/
mod.rs1mod state;
23mod stats;
24
25use std::hash::Hash;
26use std::sync::Arc;
27
28use parking_lot::RwLock;
29
30use state::StoreState;
31pub use stats::MemoryStoreStats;
32use stats::StatsRecorder;
33
34#[derive(Debug, Clone, Default)]
36pub struct MemoryStoreConfig {
37 pub max_entries: usize,
39 pub track_stats: bool,
41}
42
43impl MemoryStoreConfig {
44 pub fn with_max_entries(max_entries: usize) -> Self {
46 Self {
47 max_entries,
48 ..Default::default()
49 }
50 }
51
52 pub fn with_stats(mut self) -> Self {
54 self.track_stats = true;
55 self
56 }
57}
58
59#[derive(Debug)]
70pub struct MemoryStore<V, K = String>
71where
72 K: Eq + Hash + Clone,
73 V: Clone,
74{
75 state: Arc<RwLock<StoreState<K, V>>>,
76 config: MemoryStoreConfig,
77 stats: StatsRecorder,
78}
79
80impl<V, K> Clone for MemoryStore<V, K>
81where
82 K: Eq + Hash + Clone,
83 V: Clone,
84{
85 fn clone(&self) -> Self {
86 Self {
87 state: Arc::clone(&self.state),
88 config: self.config.clone(),
89 stats: self.stats.clone(),
90 }
91 }
92}
93
94impl<V> Default for MemoryStore<V, String>
95where
96 V: Clone,
97{
98 fn default() -> Self {
99 Self::new(MemoryStoreConfig::default())
100 }
101}
102
103impl<V, K> MemoryStore<V, K>
104where
105 K: Eq + Hash + Clone,
106 V: Clone,
107{
108 pub fn new(config: MemoryStoreConfig) -> Self {
110 Self {
111 state: Arc::new(RwLock::new(StoreState::new(config.max_entries))),
112 stats: StatsRecorder::new(config.track_stats),
113 config,
114 }
115 }
116
117 pub fn insert(&self, key: K, value: V) {
122 let outcome = self.state.write().insert(key, value);
123 if outcome.evicted {
124 self.stats.record_eviction();
125 }
126 self.stats.record_insertion();
127 }
128
129 pub fn get(&self, key: &K) -> Option<V> {
133 let result = self.state.read().get_cloned(key);
134
135 if result.is_some() {
136 self.stats.record_hit();
137 } else {
138 self.stats.record_miss();
139 }
140
141 result
142 }
143
144 pub fn contains(&self, key: &K) -> bool {
146 self.state.read().contains(key)
147 }
148
149 pub fn remove(&self, key: &K) -> Option<V> {
153 let result = self.state.write().remove(key);
154 if result.is_some() {
155 self.stats.record_removal();
156 }
157 result
158 }
159
160 pub fn clear(&self) {
162 self.state.write().clear();
163 }
164
165 pub fn len(&self) -> usize {
167 self.state.read().len()
168 }
169
170 pub fn is_empty(&self) -> bool {
172 self.state.read().is_empty()
173 }
174
175 pub fn keys(&self) -> Vec<K> {
177 self.state.read().keys()
178 }
179
180 pub fn stats(&self) -> MemoryStoreStats {
184 self.stats.snapshot()
185 }
186
187 pub fn reset_stats(&self) {
189 self.stats.reset();
190 }
191
192 pub fn get_or_insert_with<F>(&self, key: K, factory: F) -> V
198 where
199 F: FnOnce() -> V,
200 {
201 if let Some(value) = self.state.read().get_cloned(&key) {
202 self.stats.record_hit();
203 return value;
204 }
205
206 let outcome = self.state.write().get_or_insert_with(key, factory);
207
208 if outcome.inserted {
209 self.stats.record_miss();
210 if outcome.evicted {
211 self.stats.record_eviction();
212 }
213 self.stats.record_insertion();
214 } else {
215 self.stats.record_miss();
216 self.stats.record_hit();
217 }
218
219 outcome.value
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn test_basic_operations() {
229 let store: MemoryStore<String> = MemoryStore::default();
230
231 store.insert("key1".to_string(), "value1".to_string());
232 assert_eq!(store.get(&"key1".to_string()), Some("value1".to_string()));
233 assert!(store.contains(&"key1".to_string()));
234 assert_eq!(store.len(), 1);
235
236 store.remove(&"key1".to_string());
237 assert!(!store.contains(&"key1".to_string()));
238 assert!(store.is_empty());
239 }
240
241 #[test]
242 fn test_max_entries() {
243 let config = MemoryStoreConfig::with_max_entries(2).with_stats();
244 let store: MemoryStore<i32> = MemoryStore::new(config);
245
246 store.insert("a".to_string(), 1);
247 store.insert("b".to_string(), 2);
248 assert_eq!(store.len(), 2);
249
250 store.insert("c".to_string(), 3);
251 assert_eq!(store.len(), 2);
252 assert_eq!(store.stats().evictions, 1);
253 }
254
255 #[test]
256 fn test_stats_tracking() {
257 let config = MemoryStoreConfig::default().with_stats();
258 let store: MemoryStore<String> = MemoryStore::new(config);
259
260 store.insert("key1".to_string(), "value1".to_string());
261 assert_eq!(store.stats().insertions, 1);
262
263 store.get(&"key1".to_string());
264 assert_eq!(store.stats().hits, 1);
265
266 store.get(&"nonexistent".to_string());
267 assert_eq!(store.stats().misses, 1);
268
269 let rate = store.stats().hit_rate();
270 assert!((rate - 0.5).abs() < 0.01);
271 }
272
273 #[test]
274 fn test_get_or_insert_with() {
275 let store: MemoryStore<i32> = MemoryStore::default();
276
277 let value = store.get_or_insert_with("key1".to_string(), || 42);
278 assert_eq!(value, 42);
279
280 let value = store.get_or_insert_with("key1".to_string(), || 100);
281 assert_eq!(value, 42);
282 }
283
284 #[test]
285 fn test_clear() {
286 let store: MemoryStore<String> = MemoryStore::default();
287
288 store.insert("a".to_string(), "1".to_string());
289 store.insert("b".to_string(), "2".to_string());
290 assert_eq!(store.len(), 2);
291
292 store.clear();
293 assert!(store.is_empty());
294 }
295
296 #[test]
297 fn test_keys() {
298 let store: MemoryStore<i32> = MemoryStore::default();
299
300 store.insert("a".to_string(), 1);
301 store.insert("b".to_string(), 2);
302
303 let mut keys = store.keys();
304 keys.sort();
305 assert_eq!(keys, vec!["a".to_string(), "b".to_string()]);
306 }
307
308 #[test]
309 fn test_clone_shares_data() {
310 let store1: MemoryStore<String> = MemoryStore::default();
311 store1.insert("key".to_string(), "value".to_string());
312
313 let store2 = store1.clone();
314 assert_eq!(store2.get(&"key".to_string()), Some("value".to_string()));
315
316 store2.insert("key2".to_string(), "value2".to_string());
317 assert_eq!(store1.get(&"key2".to_string()), Some("value2".to_string()));
318 }
319
320 #[test]
321 fn test_custom_key_type() {
322 let store: MemoryStore<String, i32> = MemoryStore::new(MemoryStoreConfig::default());
323
324 store.insert(1, "one".to_string());
325 store.insert(2, "two".to_string());
326
327 assert_eq!(store.get(&1), Some("one".to_string()));
328 assert_eq!(store.get(&2), Some("two".to_string()));
329 }
330}