use std::borrow::Borrow;
use std::collections::HashMap;
use std::hash::Hash;
use std::time::{Duration, Instant};
pub struct TtlMap<K: Hash + Eq, V> {
map: HashMap<K, TimedValue<V>>,
ttl: Duration,
}
impl<K: Hash + Eq, V> TtlMap<K, V> {
pub fn new(ttl: Duration) -> Self {
Self {
map: Default::default(),
ttl,
}
}
pub fn contains_key(&self, key: &K) -> bool {
self.map.contains_key(key)
}
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
self.purge_expired_entries();
self.map
.insert(
key,
TimedValue {
expiration: Instant::now() + self.ttl,
value,
},
)
.map(|timed_value| timed_value.value)
}
pub fn remove<Q: ?Sized>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.purge_expired_entries();
self.map.remove(key).map(|timed_value| timed_value.value)
}
fn purge_expired_entries(&mut self) {
let now = Instant::now();
self.map
.retain(|_, timed_value| timed_value.expiration > now);
}
}
struct TimedValue<V> {
expiration: Instant,
value: V,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn insert() {
let mut map = TtlMap::new(Duration::from_secs(60));
assert!(map
.insert("key".to_string(), "value1".to_string())
.is_none());
assert_eq!(
&map.insert("key".to_string(), "value2".to_string())
.expect("Entry not found"),
"value1",
);
}
#[test]
fn remove() {
let mut map = TtlMap::new(Duration::from_secs(60));
map.insert("key".to_string(), "value".to_string());
assert_eq!("value", &map.remove("key").expect("Entry not found"));
assert!(map.remove("key").is_none());
}
#[test]
fn insert_expiration() {
let mut map = TtlMap::new(Duration::from_secs(0));
map.insert("key".to_string(), "value1".to_string());
assert!(map
.insert("key".to_string(), "value2".to_string())
.is_none());
}
#[test]
fn remove_expiration() {
let mut map = TtlMap::new(Duration::from_secs(0));
map.insert("key".to_string(), "value".to_string());
assert!(map.remove("key").is_none());
}
}