1use std::hash::{Hash, Hasher};
25use std::time::{Duration, Instant};
26
27use dashmap::DashMap;
28
29#[derive(Clone, Debug)]
31pub struct CacheConfig {
32 pub ttl: Duration,
34 pub capacity: usize,
38}
39
40impl CacheConfig {
41 pub fn new(ttl: Duration, capacity: usize) -> Self {
42 Self { ttl, capacity }
43 }
44
45 pub fn default_authz() -> Self {
47 Self::new(Duration::from_millis(500), 1_000)
48 }
49}
50
51#[derive(Clone)]
53struct CachedEntry<R> {
54 value: R,
55 expires_at: Instant,
56}
57
58#[derive(Clone, PartialEq, Eq)]
60struct CacheKey(Vec<u8>);
61
62impl Hash for CacheKey {
63 fn hash<H: Hasher>(&self, state: &mut H) {
64 self.0.hash(state);
65 }
66}
67
68pub struct ResponseCache<R> {
70 map: DashMap<CacheKey, CachedEntry<R>>,
71 config: CacheConfig,
72}
73
74impl<R: Clone + Send + 'static> ResponseCache<R> {
75 pub fn new(config: CacheConfig) -> Self {
76 Self {
77 map: DashMap::new(),
78 config,
79 }
80 }
81
82 pub fn get(&self, key: &[u8]) -> Option<R> {
85 let k = CacheKey(key.to_vec());
86 if let Some(entry) = self.map.get(&k)
88 && entry.expires_at > Instant::now()
89 {
90 return Some(entry.value.clone());
91 }
92 self.map.remove(&k);
94 None
95 }
96
97 pub fn insert(&self, key: &[u8], value: R) {
100 if self.map.len() >= self.config.capacity {
101 return;
102 }
103 let k = CacheKey(key.to_vec());
104 self.map.insert(
105 k,
106 CachedEntry {
107 value,
108 expires_at: Instant::now() + self.config.ttl,
109 },
110 );
111 }
112
113 #[cfg(test)]
115 pub fn clear(&self) {
116 self.map.clear();
117 }
118}
119
120impl<R: Clone + Send + 'static> Clone for ResponseCache<R> {
121 fn clone(&self) -> Self {
122 Self {
123 map: self.map.clone(),
124 config: self.config.clone(),
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use std::time::Duration;
133
134 #[test]
135 fn hit_within_ttl() {
136 let cache: ResponseCache<String> =
137 ResponseCache::new(CacheConfig::new(Duration::from_secs(10), 100));
138 cache.insert(b"key", "value".into());
139 assert_eq!(cache.get(b"key").as_deref(), Some("value"));
140 }
141
142 #[test]
143 fn miss_after_expiry() {
144 let cache: ResponseCache<String> =
145 ResponseCache::new(CacheConfig::new(Duration::from_millis(1), 100));
146 cache.insert(b"key", "value".into());
147 std::thread::sleep(Duration::from_millis(5));
148 assert_eq!(cache.get(b"key"), None);
149 }
150
151 #[test]
152 fn error_is_not_cached() {
153 let cache: ResponseCache<String> =
156 ResponseCache::new(CacheConfig::new(Duration::from_secs(10), 100));
157 assert_eq!(cache.get(b"key"), None);
158 }
159
160 #[test]
161 fn capacity_cap_drops_new_inserts() {
162 let cache: ResponseCache<u32> =
163 ResponseCache::new(CacheConfig::new(Duration::from_secs(10), 2));
164 cache.insert(b"a", 1);
165 cache.insert(b"b", 2);
166 cache.insert(b"c", 3); assert!(cache.get(b"a").is_some());
168 assert!(cache.get(b"b").is_some());
169 assert_eq!(cache.get(b"c"), None);
170 }
171}