mytheclipse_cache/
memory.rs1use std::collections::{HashMap, VecDeque};
8use std::sync::{Arc, Mutex};
9use std::time::{Duration, Instant};
10
11use async_trait::async_trait;
12
13use crate::traits::{Cache, CacheError};
14
15type Entry = (Vec<u8>, Option<Instant>);
17
18#[derive(Debug, Clone)]
25pub struct MemoryCache {
26 inner: Arc<Mutex<HashMap<String, Entry>>>,
27 max_entries: Option<usize>,
30 order: Arc<Mutex<VecDeque<String>>>,
32}
33
34impl Default for MemoryCache {
35 fn default() -> Self {
36 Self {
37 inner: Arc::new(Mutex::new(HashMap::new())),
38 max_entries: None,
39 order: Arc::new(Mutex::new(VecDeque::new())),
40 }
41 }
42}
43
44impl MemoryCache {
45 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn with_capacity(self, capacity: usize) -> Self {
52 self.inner.lock().unwrap().reserve(capacity);
53 self
54 }
55
56 pub fn with_max_entries(mut self, max: usize) -> Self {
63 assert!(max > 0, "mytheclipse-cache: with_max_entries must be > 0");
64 self.max_entries = Some(max);
65 self
66 }
67
68 pub fn max_entries(&self) -> Option<usize> {
70 self.max_entries
71 }
72}
73
74#[async_trait]
75impl Cache for MemoryCache {
76 async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError> {
77 let mut map = self.inner.lock().unwrap();
78 match map.get(key) {
79 Some((value, Some(expires))) if *expires <= Instant::now() => {
80 map.remove(key);
81 self.remove_order(key);
82 Ok(None)
83 }
84 Some((value, _)) => Ok(Some(value.clone())),
85 None => Ok(None),
86 }
87 }
88
89 async fn set(
90 &self,
91 key: &str,
92 value: Vec<u8>,
93 ttl: Option<Duration>,
94 ) -> Result<(), CacheError> {
95 let expires = ttl.map(|d| Instant::now() + d);
96 let mut map = self.inner.lock().unwrap();
97 let is_new = !map.contains_key(key);
98 map.insert(key.to_string(), (value, expires));
99 if is_new {
100 let mut order = self.order.lock().unwrap();
101 order.push_back(key.to_string());
102 if let Some(cap) = self.max_entries {
103 while order.len() > cap {
104 if let Some(oldest) = order.pop_front() {
105 map.remove(&oldest);
106 }
107 }
108 }
109 }
110 Ok(())
111 }
112
113 async fn invalidate(&self, key: &str) -> Result<(), CacheError> {
114 self.inner.lock().unwrap().remove(key);
115 self.remove_order(key);
116 Ok(())
117 }
118
119 async fn clear(&self) -> Result<(), CacheError> {
120 self.inner.lock().unwrap().clear();
121 self.order.lock().unwrap().clear();
122 Ok(())
123 }
124}
125
126impl MemoryCache {
127 fn remove_order(&self, key: &str) {
129 let mut order = self.order.lock().unwrap();
130 order.retain(|k| k != key);
131 }
132}
133
134#[cfg(feature = "cache-aside")]
138pub mod typed {
139 use serde::{de::DeserializeOwned, Serialize};
140
141 use super::*;
142
143 #[derive(Clone)]
145 pub struct TypedCache<C> {
146 inner: C,
147 }
148
149 impl<C: Cache> TypedCache<C> {
150 pub fn new(inner: C) -> Self {
152 Self { inner }
153 }
154
155 pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, CacheError> {
157 match self.inner.get(key).await? {
158 Some(bytes) => serde_json::from_slice(&bytes)
159 .map(Some)
160 .map_err(|e| CacheError::Serialization(e.to_string())),
161 None => Ok(None),
162 }
163 }
164
165 pub async fn set<T: Serialize>(
167 &self,
168 key: &str,
169 value: &T,
170 ttl: Option<Duration>,
171 ) -> Result<(), CacheError> {
172 let bytes =
173 serde_json::to_vec(value).map_err(|e| CacheError::Serialization(e.to_string()))?;
174 self.inner.set(key, bytes, ttl).await
175 }
176
177 pub fn into_inner(self) -> C {
179 self.inner
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[tokio::test]
189 async fn set_get_roundtrip() {
190 let c = MemoryCache::new();
191 c.set("k", b"v".to_vec(), None).await.unwrap();
192 assert_eq!(c.get("k").await.unwrap(), Some(b"v".to_vec()));
193 assert_eq!(c.get("missing").await.unwrap(), None);
194 }
195
196 #[tokio::test]
197 async fn ttl_expires_entry() {
198 let c = MemoryCache::new();
199 c.set("k", b"v".to_vec(), Some(Duration::from_millis(30)))
200 .await
201 .unwrap();
202 assert_eq!(c.get("k").await.unwrap(), Some(b"v".to_vec()));
203 tokio::time::sleep(Duration::from_millis(60)).await;
204 assert_eq!(c.get("k").await.unwrap(), None);
205 }
206
207 #[tokio::test]
208 async fn invalidate_and_clear() {
209 let c = MemoryCache::new();
210 c.set("a", b"1".to_vec(), None).await.unwrap();
211 c.set("b", b"2".to_vec(), None).await.unwrap();
212 c.invalidate("a").await.unwrap();
213 assert_eq!(c.get("a").await.unwrap(), None);
214 assert_eq!(c.get("b").await.unwrap(), Some(b"2".to_vec()));
215 c.clear().await.unwrap();
216 assert_eq!(c.get("b").await.unwrap(), None);
217 }
218
219 #[test]
222 #[should_panic(expected = "must be > 0")]
223 fn zero_max_panics() {
224 let _ = MemoryCache::new().with_max_entries(0);
225 }
226
227 #[tokio::test]
228 async fn bounded_cache_evicts_oldest() {
229 let c = MemoryCache::new().with_max_entries(2);
230 c.set("a", b"1".to_vec(), None).await.unwrap();
231 c.set("b", b"2".to_vec(), None).await.unwrap();
232 c.set("c", b"3".to_vec(), None).await.unwrap();
233 assert_eq!(c.get("a").await.unwrap(), None);
235 assert_eq!(c.get("b").await.unwrap(), Some(b"2".to_vec()));
236 assert_eq!(c.get("c").await.unwrap(), Some(b"3".to_vec()));
237 }
238
239 #[cfg(feature = "cache-aside")]
240 #[tokio::test]
241 async fn typed_cache_roundtrip() {
242 use typed::TypedCache;
243 #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
244 struct User {
245 id: u64,
246 name: String,
247 }
248 let typed = TypedCache::new(MemoryCache::new());
249 typed
250 .set(
251 "u",
252 &User {
253 id: 1,
254 name: "alice".into(),
255 },
256 None,
257 )
258 .await
259 .unwrap();
260 let got: User = typed.get("u").await.unwrap().unwrap();
261 assert_eq!(
262 got,
263 User {
264 id: 1,
265 name: "alice".into()
266 }
267 );
268 }
269}