use std::collections::HashMap;
use std::hash::Hash;
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone, Default)]
pub struct MemoryStoreConfig {
pub max_entries: usize,
pub track_stats: bool,
}
impl MemoryStoreConfig {
pub fn with_max_entries(max_entries: usize) -> Self {
Self {
max_entries,
..Default::default()
}
}
pub fn with_stats(mut self) -> Self {
self.track_stats = true;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct MemoryStoreStats {
pub hits: u64,
pub misses: u64,
pub insertions: u64,
pub removals: u64,
pub evictions: u64,
}
impl MemoryStoreStats {
pub fn hit_rate(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
self.hits as f64 / total as f64
}
}
}
#[derive(Debug)]
pub struct MemoryStore<V, K = String>
where
K: Eq + Hash + Clone,
V: Clone,
{
data: Arc<RwLock<HashMap<K, V>>>,
config: MemoryStoreConfig,
stats: Arc<RwLock<MemoryStoreStats>>,
}
impl<V, K> Clone for MemoryStore<V, K>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn clone(&self) -> Self {
Self {
data: Arc::clone(&self.data),
config: self.config.clone(),
stats: Arc::clone(&self.stats),
}
}
}
impl<V> Default for MemoryStore<V, String>
where
V: Clone,
{
fn default() -> Self {
Self::new(MemoryStoreConfig::default())
}
}
impl<V, K> MemoryStore<V, K>
where
K: Eq + Hash + Clone,
V: Clone,
{
pub fn new(config: MemoryStoreConfig) -> Self {
Self {
data: Arc::new(RwLock::new(HashMap::new())),
config,
stats: Arc::new(RwLock::new(MemoryStoreStats::default())),
}
}
pub fn insert(&self, key: K, value: V) {
let mut data = self.data.write().unwrap();
if self.config.max_entries > 0 && data.len() >= self.config.max_entries {
if let Some(first_key) = data.keys().next().cloned() {
data.remove(&first_key);
if self.config.track_stats {
self.stats.write().unwrap().evictions += 1;
}
}
}
data.insert(key, value);
if self.config.track_stats {
self.stats.write().unwrap().insertions += 1;
}
}
pub fn get(&self, key: &K) -> Option<V> {
let data = self.data.read().unwrap();
let result = data.get(key).cloned();
if self.config.track_stats {
let mut stats = self.stats.write().unwrap();
if result.is_some() {
stats.hits += 1;
} else {
stats.misses += 1;
}
}
result
}
pub fn contains(&self, key: &K) -> bool {
self.data.read().unwrap().contains_key(key)
}
pub fn remove(&self, key: &K) -> Option<V> {
let result = self.data.write().unwrap().remove(key);
if self.config.track_stats && result.is_some() {
self.stats.write().unwrap().removals += 1;
}
result
}
pub fn clear(&self) {
self.data.write().unwrap().clear();
}
pub fn len(&self) -> usize {
self.data.read().unwrap().len()
}
pub fn is_empty(&self) -> bool {
self.data.read().unwrap().is_empty()
}
pub fn keys(&self) -> Vec<K> {
self.data.read().unwrap().keys().cloned().collect()
}
pub fn stats(&self) -> MemoryStoreStats {
self.stats.read().unwrap().clone()
}
pub fn reset_stats(&self) {
*self.stats.write().unwrap() = MemoryStoreStats::default();
}
pub fn get_or_insert_with<F>(&self, key: K, factory: F) -> V
where
F: FnOnce() -> V,
{
if let Some(value) = self.get(&key) {
return value;
}
let mut data = self.data.write().unwrap();
if let Some(value) = data.get(&key) {
if self.config.track_stats {
self.stats.write().unwrap().hits += 1;
}
return value.clone();
}
let value = factory();
if self.config.max_entries > 0 && data.len() >= self.config.max_entries {
if let Some(first_key) = data.keys().next().cloned() {
data.remove(&first_key);
if self.config.track_stats {
self.stats.write().unwrap().evictions += 1;
}
}
}
data.insert(key, value.clone());
if self.config.track_stats {
let mut stats = self.stats.write().unwrap();
stats.misses += 1;
stats.insertions += 1;
}
value
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_operations() {
let store: MemoryStore<String> = MemoryStore::default();
store.insert("key1".to_string(), "value1".to_string());
assert_eq!(store.get(&"key1".to_string()), Some("value1".to_string()));
assert!(store.contains(&"key1".to_string()));
assert_eq!(store.len(), 1);
store.remove(&"key1".to_string());
assert!(!store.contains(&"key1".to_string()));
assert!(store.is_empty());
}
#[test]
fn test_max_entries() {
let config = MemoryStoreConfig::with_max_entries(2).with_stats();
let store: MemoryStore<i32> = MemoryStore::new(config);
store.insert("a".to_string(), 1);
store.insert("b".to_string(), 2);
assert_eq!(store.len(), 2);
store.insert("c".to_string(), 3);
assert_eq!(store.len(), 2);
assert_eq!(store.stats().evictions, 1);
}
#[test]
fn test_stats_tracking() {
let config = MemoryStoreConfig::default().with_stats();
let store: MemoryStore<String> = MemoryStore::new(config);
store.insert("key1".to_string(), "value1".to_string());
assert_eq!(store.stats().insertions, 1);
store.get(&"key1".to_string());
assert_eq!(store.stats().hits, 1);
store.get(&"nonexistent".to_string());
assert_eq!(store.stats().misses, 1);
let rate = store.stats().hit_rate();
assert!((rate - 0.5).abs() < 0.01);
}
#[test]
fn test_get_or_insert_with() {
let store: MemoryStore<i32> = MemoryStore::default();
let value = store.get_or_insert_with("key1".to_string(), || 42);
assert_eq!(value, 42);
let value = store.get_or_insert_with("key1".to_string(), || 100);
assert_eq!(value, 42);
}
#[test]
fn test_clear() {
let store: MemoryStore<String> = MemoryStore::default();
store.insert("a".to_string(), "1".to_string());
store.insert("b".to_string(), "2".to_string());
assert_eq!(store.len(), 2);
store.clear();
assert!(store.is_empty());
}
#[test]
fn test_keys() {
let store: MemoryStore<i32> = MemoryStore::default();
store.insert("a".to_string(), 1);
store.insert("b".to_string(), 2);
let mut keys = store.keys();
keys.sort();
assert_eq!(keys, vec!["a".to_string(), "b".to_string()]);
}
#[test]
fn test_clone_shares_data() {
let store1: MemoryStore<String> = MemoryStore::default();
store1.insert("key".to_string(), "value".to_string());
let store2 = store1.clone();
assert_eq!(store2.get(&"key".to_string()), Some("value".to_string()));
store2.insert("key2".to_string(), "value2".to_string());
assert_eq!(store1.get(&"key2".to_string()), Some("value2".to_string()));
}
#[test]
fn test_custom_key_type() {
let store: MemoryStore<String, i32> = MemoryStore::new(MemoryStoreConfig::default());
store.insert(1, "one".to_string());
store.insert(2, "two".to_string());
assert_eq!(store.get(&1), Some("one".to_string()));
assert_eq!(store.get(&2), Some("two".to_string()));
}
}