use std::collections::HashMap;
use std::time::{Duration, Instant};
struct CacheEntry<V> {
value: V,
inserted_at: Instant,
}
pub struct Cache<K, V> {
entries: HashMap<K, CacheEntry<V>>,
ttl: Duration,
}
impl<K, V> Cache<K, V>
where
K: Eq + std::hash::Hash,
V: Clone,
{
pub fn new(ttl: Duration) -> Self {
Self {
entries: HashMap::new(),
ttl,
}
}
pub fn get(&self, key: &K) -> Option<V> {
self.entries.get(key).and_then(|entry| {
if entry.inserted_at.elapsed() < self.ttl {
Some(entry.value.clone())
} else {
None
}
})
}
pub fn insert(&mut self, key: K, value: V) {
self.entries.insert(
key,
CacheEntry {
value,
inserted_at: Instant::now(),
},
);
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn cleanup(&mut self) {
let now = Instant::now();
self.entries
.retain(|_, entry| now.duration_since(entry.inserted_at) < self.ttl);
}
pub fn invalidate(&mut self, key: &K) {
self.entries.remove(key);
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_cache_basic() {
let mut cache = Cache::new(Duration::from_secs(60));
cache.insert("key1", "value1");
assert_eq!(cache.get(&"key1"), Some("value1"));
assert_eq!(cache.get(&"key2"), None);
}
#[test]
fn test_cache_expiration() {
let mut cache = Cache::new(Duration::from_millis(50));
cache.insert("key1", "value1");
assert_eq!(cache.get(&"key1"), Some("value1"));
thread::sleep(Duration::from_millis(60));
assert_eq!(cache.get(&"key1"), None);
}
#[test]
fn test_cache_cleanup() {
let mut cache = Cache::new(Duration::from_millis(50));
cache.insert("key1", "value1");
cache.insert("key2", "value2");
thread::sleep(Duration::from_millis(60));
cache.insert("key3", "value3");
cache.cleanup();
assert_eq!(cache.entries.len(), 1);
assert_eq!(cache.get(&"key3"), Some("value3"));
}
}