autumn_web/cache/
moka_impl.rs1use std::any::Any;
8use std::sync::Arc;
9use std::time::Duration;
10
11use moka::sync::Cache as SyncCache;
12
13use super::Cache;
14
15#[derive(Clone)]
31pub struct MokaCache {
32 inner: SyncCache<String, Arc<dyn Any + Send + Sync>>,
33}
34
35pub struct MokaCacheBuilder {
37 max_capacity: u64,
38 ttl: Option<Duration>,
39}
40
41impl MokaCacheBuilder {
42 #[must_use]
44 pub const fn max_capacity(mut self, max: u64) -> Self {
45 self.max_capacity = max;
46 self
47 }
48
49 #[must_use]
52 pub const fn ttl(mut self, ttl: Duration) -> Self {
53 self.ttl = Some(ttl);
54 self
55 }
56
57 #[must_use]
59 pub fn build(self) -> MokaCache {
60 let mut builder = SyncCache::builder().max_capacity(self.max_capacity);
61 if let Some(ttl) = self.ttl {
62 builder = builder.time_to_live(ttl);
63 }
64 MokaCache {
65 inner: builder.build(),
66 }
67 }
68}
69
70impl MokaCache {
71 #[must_use]
73 pub const fn builder() -> MokaCacheBuilder {
74 MokaCacheBuilder {
75 max_capacity: 10_000,
76 ttl: None,
77 }
78 }
79
80 #[must_use]
84 pub fn new(max_capacity: u64, ttl: Option<Duration>) -> Self {
85 let mut b = Self::builder().max_capacity(max_capacity);
86 if let Some(ttl) = ttl {
87 b = b.ttl(ttl);
88 }
89 b.build()
90 }
91}
92
93impl Cache for MokaCache {
94 fn get_value(&self, key: &str) -> Option<Arc<dyn Any + Send + Sync>> {
95 self.inner.get(key)
96 }
97
98 fn insert_value(&self, key: &str, value: Arc<dyn Any + Send + Sync>) {
99 self.inner.insert(key.to_owned(), value);
100 }
101
102 fn invalidate(&self, key: &str) {
103 self.inner.invalidate(key);
104 }
105
106 fn clear(&self) {
107 self.inner.invalidate_all();
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::cache;
115 use std::thread;
116
117 #[test]
118 fn basic_insert_and_get() {
119 let c = MokaCache::new(100, None);
120 cache::insert(&c, "a", 1_i32);
121 assert_eq!(cache::get::<i32>(&c, "a"), Some(1));
122 assert_eq!(cache::get::<i32>(&c, "b"), None);
123 }
124
125 #[test]
126 fn type_mismatch_returns_none() {
127 let c = MokaCache::new(100, None);
128 cache::insert(&c, "a", 1_i32);
129 assert_eq!(cache::get::<String>(&c, "a"), None);
130 }
131
132 #[test]
133 fn ttl_expiry() {
134 let c = MokaCache::new(100, Some(Duration::from_millis(50)));
135 cache::insert(&c, "a", 1_i32);
136 assert_eq!(cache::get::<i32>(&c, "a"), Some(1));
137 thread::sleep(Duration::from_millis(80));
138 c.inner.run_pending_tasks();
139 assert_eq!(cache::get::<i32>(&c, "a"), None);
140 }
141
142 #[test]
143 fn max_capacity_evicts() {
144 let c = MokaCache::new(10, None);
147 for i in 0..100 {
148 cache::insert(&c, &format!("k{i}"), i);
149 if i % 20 == 0 {
151 c.inner.run_pending_tasks();
152 }
153 }
154 c.inner.run_pending_tasks();
155 let count = (0..100)
156 .filter(|i| cache::get::<i32>(&c, &format!("k{i}")).is_some())
157 .count();
158 assert!(
161 count <= 20,
162 "expected roughly <=10 entries (with slack), got {count}"
163 );
164 assert!(count > 0, "cache should not be empty");
165 }
166
167 #[test]
168 fn clear_removes_all() {
169 let c = MokaCache::new(100, None);
170 cache::insert(&c, "a", 1_i32);
171 cache::insert(&c, "b", 2_i32);
172 c.clear();
173 c.inner.run_pending_tasks();
174 assert_eq!(cache::get::<i32>(&c, "a"), None);
175 assert_eq!(cache::get::<i32>(&c, "b"), None);
176 }
177
178 #[test]
179 fn invalidate_removes_key() {
180 let c = MokaCache::new(100, None);
181 cache::insert(&c, "a", 1_i32);
182 c.invalidate("a");
183 c.inner.run_pending_tasks();
184 assert_eq!(cache::get::<i32>(&c, "a"), None);
185 }
186
187 #[test]
188 fn concurrent_access() {
189 let c = MokaCache::new(1000, None);
190 let handles: Vec<_> = (0_i32..10)
191 .map(|i| {
192 let c = c.clone();
193 thread::spawn(move || {
194 let key = format!("key-{i}");
195 cache::insert(&c, &key, i * 10);
196 (i, cache::get::<i32>(&c, &key))
197 })
198 })
199 .collect();
200
201 for h in handles {
202 let (i, val) = h.join().unwrap();
203 assert_eq!(val, Some(i * 10));
204 }
205 }
206
207 #[test]
208 fn heterogeneous_types() {
209 let c = MokaCache::new(100, None);
210 cache::insert(&c, "int", 42_i32);
211 cache::insert(&c, "string", "hello".to_string());
212 cache::insert(&c, "vec", vec![1_u8, 2, 3]);
213
214 assert_eq!(cache::get::<i32>(&c, "int"), Some(42));
215 assert_eq!(
216 cache::get::<String>(&c, "string"),
217 Some("hello".to_string())
218 );
219 assert_eq!(cache::get::<Vec<u8>>(&c, "vec"), Some(vec![1, 2, 3]));
220 }
221
222 #[test]
223 fn builder_pattern() {
224 let c = MokaCache::builder()
225 .max_capacity(500)
226 .ttl(Duration::from_secs(60))
227 .build();
228 cache::insert(&c, "x", 99_i32);
229 assert_eq!(cache::get::<i32>(&c, "x"), Some(99));
230 }
231}