allframe_core/cache/
memory.rs1use std::{
4 collections::HashMap,
5 future::Future,
6 pin::Pin,
7 sync::RwLock,
8 time::{Duration, Instant},
9};
10
11use serde::{de::DeserializeOwned, Serialize};
12
13use super::{Cache, CacheConfig};
14
15struct CacheEntry {
17 data: Vec<u8>,
19 expires_at: Option<Instant>,
21}
22
23impl CacheEntry {
24 fn new(data: Vec<u8>, ttl: Option<Duration>) -> Self {
25 Self {
26 data,
27 expires_at: ttl.map(|d| Instant::now() + d),
28 }
29 }
30
31 fn is_expired(&self) -> bool {
32 self.expires_at.map(|t| Instant::now() > t).unwrap_or(false)
33 }
34}
35
36pub struct MemoryCache {
58 entries: RwLock<HashMap<String, CacheEntry>>,
59 config: CacheConfig,
60}
61
62impl MemoryCache {
63 pub fn new() -> Self {
65 Self::with_config(CacheConfig::default())
66 }
67
68 pub fn with_config(config: CacheConfig) -> Self {
70 Self {
71 entries: RwLock::new(HashMap::new()),
72 config,
73 }
74 }
75
76 pub fn builder() -> MemoryCacheBuilder {
78 MemoryCacheBuilder::new()
79 }
80
81 pub fn cleanup_expired(&self) {
86 let mut entries = self.entries.write().unwrap();
87 entries.retain(|_, entry| !entry.is_expired());
88 }
89
90 fn effective_ttl(&self, ttl: Option<Duration>) -> Option<Duration> {
92 ttl.or(self.config.default_ttl)
93 }
94
95 fn full_key(&self, key: &str) -> String {
97 self.config.build_key(key)
98 }
99}
100
101impl Default for MemoryCache {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107impl Cache for MemoryCache {
108 fn get<T: DeserializeOwned + Send>(
109 &self,
110 key: &str,
111 ) -> Pin<Box<dyn Future<Output = Option<T>> + Send + '_>> {
112 let key = self.full_key(key);
113 Box::pin(async move {
114 let entries = self.entries.read().unwrap();
115 entries.get(&key).and_then(|entry| {
116 if entry.is_expired() {
117 None
118 } else {
119 serde_json::from_slice(&entry.data).ok()
120 }
121 })
122 })
123 }
124
125 fn set<T: Serialize + Send + Sync>(
126 &self,
127 key: &str,
128 value: &T,
129 ttl: Option<Duration>,
130 ) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
131 let key = self.full_key(key);
132 let ttl = self.effective_ttl(ttl);
133
134 let data = match serde_json::to_vec(value) {
136 Ok(d) => d,
137 Err(_) => return Box::pin(async {}),
138 };
139
140 Box::pin(async move {
141 let mut entries = self.entries.write().unwrap();
142
143 if let Some(max) = self.config.max_entries {
145 if entries.len() >= max && !entries.contains_key(&key) {
146 entries.retain(|_, entry| !entry.is_expired());
148
149 if entries.len() >= max {
151 if let Some(oldest_key) = entries.keys().next().cloned() {
152 entries.remove(&oldest_key);
153 }
154 }
155 }
156 }
157
158 entries.insert(key, CacheEntry::new(data, ttl));
159 })
160 }
161
162 fn delete(&self, key: &str) -> Pin<Box<dyn Future<Output = bool> + Send + '_>> {
163 let key = self.full_key(key);
164 Box::pin(async move {
165 let mut entries = self.entries.write().unwrap();
166 entries.remove(&key).is_some()
167 })
168 }
169
170 fn exists(&self, key: &str) -> Pin<Box<dyn Future<Output = bool> + Send + '_>> {
171 let key = self.full_key(key);
172 Box::pin(async move {
173 let entries = self.entries.read().unwrap();
174 entries
175 .get(&key)
176 .map(|entry| !entry.is_expired())
177 .unwrap_or(false)
178 })
179 }
180
181 fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
182 Box::pin(async move {
183 let mut entries = self.entries.write().unwrap();
184 entries.clear();
185 })
186 }
187
188 fn len(&self) -> Pin<Box<dyn Future<Output = Option<usize>> + Send + '_>> {
189 Box::pin(async move {
190 let entries = self.entries.read().unwrap();
191 let count = entries.values().filter(|e| !e.is_expired()).count();
193 Some(count)
194 })
195 }
196}
197
198pub struct MemoryCacheBuilder {
200 config: CacheConfig,
201}
202
203impl MemoryCacheBuilder {
204 pub fn new() -> Self {
206 Self {
207 config: CacheConfig::default(),
208 }
209 }
210
211 pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
213 self.config.prefix = Some(prefix.into());
214 self
215 }
216
217 pub fn default_ttl(mut self, ttl: Duration) -> Self {
219 self.config.default_ttl = Some(ttl);
220 self
221 }
222
223 pub fn max_entries(mut self, max: usize) -> Self {
225 self.config.max_entries = Some(max);
226 self
227 }
228
229 pub fn build(self) -> MemoryCache {
231 MemoryCache::with_config(self.config)
232 }
233}
234
235impl Default for MemoryCacheBuilder {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[tokio::test]
246 async fn test_memory_cache_basic() {
247 let cache = MemoryCache::new();
248
249 cache.set("key", &"value", None).await;
250 let value: Option<String> = cache.get("key").await;
251 assert_eq!(value, Some("value".to_string()));
252 }
253
254 #[tokio::test]
255 async fn test_memory_cache_missing_key() {
256 let cache = MemoryCache::new();
257 let value: Option<String> = cache.get("nonexistent").await;
258 assert_eq!(value, None);
259 }
260
261 #[tokio::test]
262 async fn test_memory_cache_delete() {
263 let cache = MemoryCache::new();
264
265 cache.set("key", &"value", None).await;
266 assert!(cache.exists("key").await);
267
268 let deleted = cache.delete("key").await;
269 assert!(deleted);
270 assert!(!cache.exists("key").await);
271 }
272
273 #[tokio::test]
274 async fn test_memory_cache_delete_nonexistent() {
275 let cache = MemoryCache::new();
276 let deleted = cache.delete("nonexistent").await;
277 assert!(!deleted);
278 }
279
280 #[tokio::test]
281 async fn test_memory_cache_clear() {
282 let cache = MemoryCache::new();
283
284 cache.set("key1", &"value1", None).await;
285 cache.set("key2", &"value2", None).await;
286
287 cache.clear().await;
288
289 assert!(!cache.exists("key1").await);
290 assert!(!cache.exists("key2").await);
291 }
292
293 #[tokio::test]
294 async fn test_memory_cache_len() {
295 let cache = MemoryCache::new();
296
297 assert_eq!(cache.len().await, Some(0));
298
299 cache.set("key1", &"value1", None).await;
300 cache.set("key2", &"value2", None).await;
301
302 assert_eq!(cache.len().await, Some(2));
303 }
304
305 #[tokio::test]
306 async fn test_memory_cache_ttl_expired() {
307 let cache = MemoryCache::new();
308
309 cache
311 .set("key", &"value", Some(Duration::from_millis(1)))
312 .await;
313
314 tokio::time::sleep(Duration::from_millis(10)).await;
316
317 let value: Option<String> = cache.get("key").await;
318 assert_eq!(value, None);
319 }
320
321 #[tokio::test]
322 async fn test_memory_cache_ttl_not_expired() {
323 let cache = MemoryCache::new();
324
325 cache
326 .set("key", &"value", Some(Duration::from_secs(60)))
327 .await;
328
329 let value: Option<String> = cache.get("key").await;
330 assert_eq!(value, Some("value".to_string()));
331 }
332
333 #[tokio::test]
334 async fn test_memory_cache_with_prefix() {
335 let cache = MemoryCache::builder().prefix("test").build();
336
337 cache.set("key", &"value", None).await;
338
339 let value: Option<String> = cache.get("key").await;
341 assert_eq!(value, Some("value".to_string()));
342 }
343
344 #[tokio::test]
345 async fn test_memory_cache_max_entries() {
346 let cache = MemoryCache::builder().max_entries(2).build();
347
348 cache.set("key1", &"value1", None).await;
349 cache.set("key2", &"value2", None).await;
350 cache.set("key3", &"value3", None).await;
351
352 assert!(cache.len().await.unwrap() <= 2);
354 }
355
356 #[tokio::test]
357 async fn test_memory_cache_complex_type() {
358 #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
359 struct User {
360 id: u64,
361 name: String,
362 }
363
364 let cache = MemoryCache::new();
365 let user = User {
366 id: 1,
367 name: "Alice".to_string(),
368 };
369
370 cache.set("user:1", &user, None).await;
371
372 let retrieved: Option<User> = cache.get("user:1").await;
373 assert_eq!(retrieved, Some(user));
374 }
375
376 #[tokio::test]
377 async fn test_memory_cache_builder_default_ttl() {
378 let cache = MemoryCache::builder()
379 .default_ttl(Duration::from_millis(1))
380 .build();
381
382 cache.set("key", &"value", None).await;
384
385 tokio::time::sleep(Duration::from_millis(10)).await;
387
388 let value: Option<String> = cache.get("key").await;
389 assert_eq!(value, None);
390 }
391}