1use std::hash::Hash;
4use std::time::{Duration, Instant};
5use dashmap::DashMap;
6
7struct CacheEntry<V> {
9 value: V,
10 expires_at: Instant,
11}
12
13pub struct TtlCache<K, V> {
18 inner: DashMap<K, CacheEntry<V>>,
19 ttl: Duration,
20}
21
22impl<K: Eq + Hash + Clone, V: Clone> TtlCache<K, V> {
23 pub fn new(ttl: Duration) -> Self {
25 Self {
26 inner: DashMap::new(),
27 ttl,
28 }
29 }
30
31 pub fn get(&self, key: &K) -> Option<V> {
33 let entry = self.inner.get(key)?;
34 if entry.expires_at > Instant::now() {
35 Some(entry.value.clone())
36 } else {
37 drop(entry);
38 self.inner.remove(key);
39 None
40 }
41 }
42
43 pub fn insert(&self, key: K, value: V) {
45 self.inner.insert(
46 key,
47 CacheEntry {
48 value,
49 expires_at: Instant::now() + self.ttl,
50 },
51 );
52 }
53
54 pub fn remove(&self, key: &K) -> Option<V> {
56 self.inner.remove(key).map(|e| e.1.value)
57 }
58
59 pub fn cleanup(&self) {
61 let now = Instant::now();
62 self.inner.retain(|_, entry| entry.expires_at > now);
63 }
64
65 pub fn len(&self) -> usize {
67 self.inner.len()
68 }
69
70 pub fn is_empty(&self) -> bool {
72 self.inner.is_empty()
73 }
74}
75
76impl<K: Eq + Hash + Clone, V: Clone> Default for TtlCache<K, V> {
77 fn default() -> Self {
78 Self::new(Duration::from_secs(300))
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use std::time::Duration;
86
87 #[test]
88 fn insert_and_get() {
89 let cache = TtlCache::new(Duration::from_secs(60));
90 cache.insert("key1", 42);
91 assert_eq!(cache.get(&"key1"), Some(42));
92 }
93
94 #[test]
95 fn expired_entry_returns_none() {
96 let cache = TtlCache::new(Duration::from_millis(1));
97 cache.insert("key1", 42);
98 std::thread::sleep(Duration::from_millis(5));
99 assert_eq!(cache.get(&"key1"), None);
100 }
101
102 #[test]
103 fn remove_entry() {
104 let cache = TtlCache::new(Duration::from_secs(60));
105 cache.insert("key1", 42);
106 assert_eq!(cache.remove(&"key1"), Some(42));
107 assert_eq!(cache.get(&"key1"), None);
108 }
109
110 #[test]
111 fn cleanup_removes_expired() {
112 let cache = TtlCache::new(Duration::from_millis(1));
113 cache.insert("key1", 1);
114 cache.insert("key2", 2);
115 std::thread::sleep(Duration::from_millis(5));
116 cache.cleanup();
117 assert_eq!(cache.len(), 0);
118 }
119
120 #[test]
121 fn len_and_is_empty() {
122 let cache = TtlCache::new(Duration::from_secs(60));
123 assert!(cache.is_empty());
124 cache.insert("key1", 42);
125 assert_eq!(cache.len(), 1);
126 assert!(!cache.is_empty());
127 }
128}