armature_cache/
manager.rs

1//! High-level cache manager with convenience methods.
2
3use crate::error::CacheResult;
4use crate::traits::CacheStore;
5use serde::{Serialize, de::DeserializeOwned};
6use std::sync::Arc;
7use std::time::Duration;
8
9/// High-level cache manager with type-safe operations.
10pub struct CacheManager<S: CacheStore> {
11    store: Arc<S>,
12}
13
14impl<S: CacheStore> CacheManager<S> {
15    /// Create a new cache manager.
16    pub fn new(store: S) -> Self {
17        Self {
18            store: Arc::new(store),
19        }
20    }
21
22    /// Get a typed value from the cache.
23    pub async fn get<T: DeserializeOwned>(&self, key: &str) -> CacheResult<Option<T>> {
24        if let Some(json) = self.store.get_json(key).await? {
25            let value: T = serde_json::from_str(&json)
26                .map_err(|e| crate::error::CacheError::Deserialization(e.to_string()))?;
27            Ok(Some(value))
28        } else {
29            Ok(None)
30        }
31    }
32
33    /// Set a typed value in the cache.
34    pub async fn set<T: Serialize>(
35        &self,
36        key: &str,
37        value: &T,
38        ttl: Option<Duration>,
39    ) -> CacheResult<()> {
40        let json = serde_json::to_string(value)
41            .map_err(|e| crate::error::CacheError::Serialization(e.to_string()))?;
42        self.store.set_json(key, json, ttl).await
43    }
44
45    /// Get or set a value using a factory function.
46    ///
47    /// If the key exists, returns the cached value.
48    /// If not, calls the factory function, caches the result, and returns it.
49    pub async fn get_or_set<T, F, Fut>(
50        &self,
51        key: &str,
52        ttl: Option<Duration>,
53        factory: F,
54    ) -> CacheResult<T>
55    where
56        T: Serialize + DeserializeOwned,
57        F: FnOnce() -> Fut,
58        Fut: std::future::Future<Output = CacheResult<T>>,
59    {
60        if let Some(value) = self.get(key).await? {
61            return Ok(value);
62        }
63
64        let value = factory().await?;
65        self.set(key, &value, ttl).await?;
66        Ok(value)
67    }
68
69    /// Delete a key from the cache.
70    pub async fn delete(&self, key: &str) -> CacheResult<()> {
71        self.store.delete(key).await
72    }
73
74    /// Check if a key exists.
75    pub async fn exists(&self, key: &str) -> CacheResult<bool> {
76        self.store.exists(key).await
77    }
78
79    /// Clear all keys.
80    pub async fn clear(&self) -> CacheResult<()> {
81        self.store.clear().await
82    }
83
84    /// Get the TTL of a key.
85    pub async fn ttl(&self, key: &str) -> CacheResult<Option<Duration>> {
86        self.store.ttl(key).await
87    }
88
89    /// Set the expiration of a key.
90    pub async fn expire(&self, key: &str, ttl: Duration) -> CacheResult<()> {
91        self.store.expire(key, ttl).await
92    }
93
94    /// Create a namespaced cache manager.
95    pub fn namespace(&self, prefix: &str) -> NamespacedCache<S> {
96        NamespacedCache {
97            store: self.store.clone(),
98            prefix: prefix.to_string(),
99        }
100    }
101}
102
103/// Namespaced cache manager that automatically prefixes all keys.
104pub struct NamespacedCache<S: CacheStore> {
105    store: Arc<S>,
106    prefix: String,
107}
108
109impl<S: CacheStore> NamespacedCache<S> {
110    fn build_key(&self, key: &str) -> String {
111        format!("{}:{}", self.prefix, key)
112    }
113
114    /// Get a typed value from the cache.
115    pub async fn get<T: DeserializeOwned>(&self, key: &str) -> CacheResult<Option<T>> {
116        let key = self.build_key(key);
117        if let Some(json) = self.store.get_json(&key).await? {
118            let value: T = serde_json::from_str(&json)
119                .map_err(|e| crate::error::CacheError::Deserialization(e.to_string()))?;
120            Ok(Some(value))
121        } else {
122            Ok(None)
123        }
124    }
125
126    /// Set a typed value in the cache.
127    pub async fn set<T: Serialize>(
128        &self,
129        key: &str,
130        value: &T,
131        ttl: Option<Duration>,
132    ) -> CacheResult<()> {
133        let key = self.build_key(key);
134        let json = serde_json::to_string(value)
135            .map_err(|e| crate::error::CacheError::Serialization(e.to_string()))?;
136        self.store.set_json(&key, json, ttl).await
137    }
138
139    /// Delete a key from the cache.
140    pub async fn delete(&self, key: &str) -> CacheResult<()> {
141        let key = self.build_key(key);
142        self.store.delete(&key).await
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_namespace_build_key() {
152        struct MockStore;
153
154        #[async_trait::async_trait]
155        impl CacheStore for MockStore {
156            async fn get_json(&self, _key: &str) -> CacheResult<Option<String>> {
157                Ok(None)
158            }
159            async fn set_json(
160                &self,
161                _key: &str,
162                _value: String,
163                _ttl: Option<Duration>,
164            ) -> CacheResult<()> {
165                Ok(())
166            }
167            async fn delete(&self, _key: &str) -> CacheResult<()> {
168                Ok(())
169            }
170            async fn exists(&self, _key: &str) -> CacheResult<bool> {
171                Ok(false)
172            }
173            async fn clear(&self) -> CacheResult<()> {
174                Ok(())
175            }
176            async fn ttl(&self, _key: &str) -> CacheResult<Option<Duration>> {
177                Ok(None)
178            }
179            async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
180                Ok(())
181            }
182            async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
183                Ok(0)
184            }
185            async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
186                Ok(0)
187            }
188        }
189
190        let namespaced = NamespacedCache {
191            store: Arc::new(MockStore),
192            prefix: "users".to_string(),
193        };
194
195        assert_eq!(namespaced.build_key("123"), "users:123");
196    }
197
198    #[test]
199    fn test_namespace_build_key_empty() {
200        struct MockStore;
201
202        #[async_trait::async_trait]
203        impl CacheStore for MockStore {
204            async fn get_json(&self, _key: &str) -> CacheResult<Option<String>> {
205                Ok(None)
206            }
207            async fn set_json(
208                &self,
209                _key: &str,
210                _value: String,
211                _ttl: Option<Duration>,
212            ) -> CacheResult<()> {
213                Ok(())
214            }
215            async fn delete(&self, _key: &str) -> CacheResult<()> {
216                Ok(())
217            }
218            async fn exists(&self, _key: &str) -> CacheResult<bool> {
219                Ok(false)
220            }
221            async fn clear(&self) -> CacheResult<()> {
222                Ok(())
223            }
224            async fn ttl(&self, _key: &str) -> CacheResult<Option<Duration>> {
225                Ok(None)
226            }
227            async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
228                Ok(())
229            }
230            async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
231                Ok(0)
232            }
233            async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
234                Ok(0)
235            }
236        }
237
238        let namespaced = NamespacedCache {
239            store: Arc::new(MockStore),
240            prefix: "app".to_string(),
241        };
242
243        assert_eq!(namespaced.build_key(""), "app:");
244    }
245
246    #[test]
247    fn test_namespace_build_key_with_colons() {
248        struct MockStore;
249
250        #[async_trait::async_trait]
251        impl CacheStore for MockStore {
252            async fn get_json(&self, _key: &str) -> CacheResult<Option<String>> {
253                Ok(None)
254            }
255            async fn set_json(
256                &self,
257                _key: &str,
258                _value: String,
259                _ttl: Option<Duration>,
260            ) -> CacheResult<()> {
261                Ok(())
262            }
263            async fn delete(&self, _key: &str) -> CacheResult<()> {
264                Ok(())
265            }
266            async fn exists(&self, _key: &str) -> CacheResult<bool> {
267                Ok(false)
268            }
269            async fn clear(&self) -> CacheResult<()> {
270                Ok(())
271            }
272            async fn ttl(&self, _key: &str) -> CacheResult<Option<Duration>> {
273                Ok(None)
274            }
275            async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
276                Ok(())
277            }
278            async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
279                Ok(0)
280            }
281            async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
282                Ok(0)
283            }
284        }
285
286        let namespaced = NamespacedCache {
287            store: Arc::new(MockStore),
288            prefix: "app".to_string(),
289        };
290
291        assert_eq!(namespaced.build_key("user:123"), "app:user:123");
292    }
293
294    #[test]
295    fn test_namespace_multiple_prefixes() {
296        struct MockStore;
297
298        #[async_trait::async_trait]
299        impl CacheStore for MockStore {
300            async fn get_json(&self, _key: &str) -> CacheResult<Option<String>> {
301                Ok(None)
302            }
303            async fn set_json(
304                &self,
305                _key: &str,
306                _value: String,
307                _ttl: Option<Duration>,
308            ) -> CacheResult<()> {
309                Ok(())
310            }
311            async fn delete(&self, _key: &str) -> CacheResult<()> {
312                Ok(())
313            }
314            async fn exists(&self, _key: &str) -> CacheResult<bool> {
315                Ok(false)
316            }
317            async fn clear(&self) -> CacheResult<()> {
318                Ok(())
319            }
320            async fn ttl(&self, _key: &str) -> CacheResult<Option<Duration>> {
321                Ok(None)
322            }
323            async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
324                Ok(())
325            }
326            async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
327                Ok(0)
328            }
329            async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
330                Ok(0)
331            }
332        }
333
334        let ns1 = NamespacedCache {
335            store: Arc::new(MockStore),
336            prefix: "app1".to_string(),
337        };
338
339        let ns2 = NamespacedCache {
340            store: Arc::new(MockStore),
341            prefix: "app2".to_string(),
342        };
343
344        assert_eq!(ns1.build_key("key"), "app1:key");
345        assert_eq!(ns2.build_key("key"), "app2:key");
346        assert_ne!(ns1.build_key("key"), ns2.build_key("key"));
347    }
348}