allframe_core/cache/
traits.rs

1//! Cache traits and abstractions
2
3use std::{future::Future, pin::Pin, time::Duration};
4
5use serde::{de::DeserializeOwned, Serialize};
6
7/// A cache backend that can store and retrieve values
8///
9/// This trait defines the core operations for any cache implementation.
10/// All operations are async to support both local and remote cache backends.
11pub trait Cache: Send + Sync {
12    /// Get a value from the cache
13    ///
14    /// Returns `None` if the key doesn't exist or has expired.
15    fn get<T: DeserializeOwned + Send>(
16        &self,
17        key: &str,
18    ) -> Pin<Box<dyn Future<Output = Option<T>> + Send + '_>>;
19
20    /// Set a value in the cache with an optional TTL
21    ///
22    /// If `ttl` is `None`, the cache's default TTL will be used (if any),
23    /// otherwise the entry won't expire.
24    fn set<T: Serialize + Send + Sync>(
25        &self,
26        key: &str,
27        value: &T,
28        ttl: Option<Duration>,
29    ) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
30
31    /// Delete a value from the cache
32    ///
33    /// Returns `true` if the key existed and was deleted.
34    fn delete(&self, key: &str) -> Pin<Box<dyn Future<Output = bool> + Send + '_>>;
35
36    /// Check if a key exists in the cache
37    fn exists(&self, key: &str) -> Pin<Box<dyn Future<Output = bool> + Send + '_>>;
38
39    /// Get multiple values from the cache
40    ///
41    /// Returns a vector of optional values in the same order as the keys.
42    fn get_many<T: DeserializeOwned + Send>(
43        &self,
44        keys: &[&str],
45    ) -> Pin<Box<dyn Future<Output = Vec<Option<T>>> + Send + '_>> {
46        let keys: Vec<String> = keys.iter().map(|k| k.to_string()).collect();
47        Box::pin(async move {
48            let mut results = Vec::with_capacity(keys.len());
49            for key in keys {
50                results.push(self.get(&key).await);
51            }
52            results
53        })
54    }
55
56    /// Set multiple values in the cache
57    ///
58    /// Default implementation calls `set` for each entry sequentially.
59    /// Backends may override this with a more efficient batch operation.
60    fn set_many<'a, T: Serialize + Send + Sync + 'a>(
61        &'a self,
62        entries: Vec<(String, T)>,
63        ttl: Option<Duration>,
64    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
65        Box::pin(async move {
66            for (key, value) in entries {
67                self.set(&key, &value, ttl).await;
68            }
69        })
70    }
71
72    /// Delete multiple keys from the cache
73    ///
74    /// Returns the number of keys that were deleted.
75    fn delete_many(&self, keys: &[&str]) -> Pin<Box<dyn Future<Output = usize> + Send + '_>> {
76        let keys: Vec<String> = keys.iter().map(|k| k.to_string()).collect();
77        Box::pin(async move {
78            let mut count = 0;
79            for key in keys {
80                if self.delete(&key).await {
81                    count += 1;
82                }
83            }
84            count
85        })
86    }
87
88    /// Clear all entries from the cache
89    fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
90
91    /// Get the number of entries in the cache (if supported)
92    ///
93    /// Returns `None` if the cache doesn't support this operation.
94    fn len(&self) -> Pin<Box<dyn Future<Output = Option<usize>> + Send + '_>> {
95        Box::pin(async { None })
96    }
97
98    /// Check if the cache is empty
99    fn is_empty(&self) -> Pin<Box<dyn Future<Output = bool> + Send + '_>> {
100        Box::pin(async move { self.len().await.map(|n| n == 0).unwrap_or(true) })
101    }
102}
103
104/// Extension trait for cache operations with typed keys
105pub trait CacheExt: Cache {
106    /// Get a value using a typed cache key
107    fn get_keyed<K: super::CacheKey, T: DeserializeOwned + Send>(
108        &self,
109        key: &K,
110    ) -> Pin<Box<dyn Future<Output = Option<T>> + Send + '_>> {
111        let key = key.cache_key();
112        Box::pin(async move { self.get(&key).await })
113    }
114
115    /// Set a value using a typed cache key
116    fn set_keyed<'a, K: super::CacheKey, T: Serialize + Send + Sync + 'a>(
117        &'a self,
118        key: &K,
119        value: T,
120        ttl: Option<Duration>,
121    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
122        let key = key.cache_key();
123        Box::pin(async move { self.set(&key, &value, ttl).await })
124    }
125
126    /// Delete a value using a typed cache key
127    fn delete_keyed<K: super::CacheKey>(
128        &self,
129        key: &K,
130    ) -> Pin<Box<dyn Future<Output = bool> + Send + '_>> {
131        let key = key.cache_key();
132        Box::pin(async move { self.delete(&key).await })
133    }
134}
135
136// Blanket implementation for all Cache types
137impl<C: Cache + ?Sized> CacheExt for C {}
138
139/// Result type for cache operations that can fail
140pub type CacheResult<T> = Result<T, CacheError>;
141
142/// Errors that can occur during cache operations
143#[derive(Debug, Clone)]
144pub enum CacheError {
145    /// Serialization error
146    Serialization(String),
147    /// Deserialization error
148    Deserialization(String),
149    /// Connection error
150    Connection(String),
151    /// Operation timed out
152    Timeout,
153    /// Other error
154    Other(String),
155}
156
157impl std::fmt::Display for CacheError {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        match self {
160            CacheError::Serialization(msg) => write!(f, "Serialization error: {}", msg),
161            CacheError::Deserialization(msg) => write!(f, "Deserialization error: {}", msg),
162            CacheError::Connection(msg) => write!(f, "Connection error: {}", msg),
163            CacheError::Timeout => write!(f, "Operation timed out"),
164            CacheError::Other(msg) => write!(f, "Cache error: {}", msg),
165        }
166    }
167}
168
169impl std::error::Error for CacheError {}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn test_cache_error_display() {
177        let err = CacheError::Connection("refused".into());
178        assert!(err.to_string().contains("refused"));
179    }
180}