ferron_common/util/
ttl_cache.rs

1use std::collections::HashMap;
2use std::time::{Duration, Instant};
3
4/// A TTL cache
5pub struct TtlCache<K, V> {
6  cache: HashMap<K, (V, Instant)>,
7  ttl: Duration,
8}
9
10impl<K, V> TtlCache<K, V>
11where
12  K: std::cmp::Eq + std::hash::Hash + Clone,
13  V: Clone,
14{
15  /// Creates a new TTL cache with specific lifetime
16  pub fn new(ttl: Duration) -> Self {
17    Self {
18      cache: HashMap::new(),
19      ttl,
20    }
21  }
22
23  /// Inserts a value into the TTL cache
24  pub fn insert(&mut self, key: K, value: V) {
25    self.cache.insert(key, (value, Instant::now()));
26  }
27
28  /// Obtains a value from the TTL cache
29  pub fn get(&self, key: &K) -> Option<V> {
30    self.cache.get(key).and_then(|(value, timestamp)| {
31      if timestamp.elapsed() < self.ttl {
32        Some(value.clone())
33      } else {
34        None
35      }
36    })
37  }
38
39  /// Removes a value from the TTL cache
40  #[allow(dead_code)]
41  pub fn remove(&mut self, key: &K) -> Option<V> {
42    self.cache.remove(key).map(|(value, _)| value)
43  }
44
45  /// Cleans up the TTL cache
46  pub fn cleanup(&mut self) {
47    self.cache.retain(|_, (_, timestamp)| timestamp.elapsed() < self.ttl);
48  }
49}
50
51#[cfg(test)]
52mod tests {
53  use super::*;
54  use std::thread::sleep;
55  use std::time::Duration;
56
57  #[test]
58  fn test_insert_and_get() {
59    let mut cache = TtlCache::new(Duration::new(5, 0));
60    cache.insert("key1", "value1");
61
62    assert_eq!(cache.get(&"key1"), Some("value1"));
63  }
64
65  #[test]
66  fn test_get_expired() {
67    let mut cache = TtlCache::new(Duration::new(1, 0));
68    cache.insert("key1", "value1");
69
70    // Sleep for 2 seconds to ensure the entry expires
71    sleep(Duration::new(2, 0));
72
73    assert_eq!(cache.get(&"key1"), None);
74  }
75
76  #[test]
77  fn test_remove() {
78    let mut cache = TtlCache::new(Duration::new(5, 0));
79    cache.insert("key1", "value1");
80    cache.remove(&"key1");
81
82    assert_eq!(cache.get(&"key1"), None);
83  }
84
85  #[test]
86  fn test_cleanup() {
87    let mut cache = TtlCache::new(Duration::new(1, 0));
88    cache.insert("key1", "value1");
89    cache.insert("key2", "value2");
90
91    // Sleep for 2 seconds to ensure the entries expire
92    sleep(Duration::new(2, 0));
93
94    cache.cleanup();
95
96    assert_eq!(cache.get(&"key1"), None);
97    assert_eq!(cache.get(&"key2"), None);
98  }
99
100  #[test]
101  fn test_get_non_existent() {
102    let cache: TtlCache<&str, &str> = TtlCache::new(Duration::new(5, 0));
103    assert_eq!(cache.get(&"key1"), None);
104  }
105
106  #[test]
107  fn test_insert_and_get_multiple() {
108    let mut cache = TtlCache::new(Duration::new(5, 0));
109    cache.insert("key1", "value1");
110    cache.insert("key2", "value2");
111
112    assert_eq!(cache.get(&"key1"), Some("value1"));
113    assert_eq!(cache.get(&"key2"), Some("value2"));
114  }
115}