1use async_trait::async_trait;
30use dashmap::DashMap;
31use std::sync::{
32 Arc,
33 atomic::{AtomicUsize, Ordering},
34};
35use std::time::Instant;
36
37mod tenant;
38pub use tenant::TenantCache;
39
40#[derive(Debug)]
44#[non_exhaustive]
45pub enum CacheError {
46 Driver(String),
48 Serialization(String),
50 InvalidKey(String),
52 InspectionUnsupported,
54 InvalidInspectionLimit,
56}
57
58impl std::fmt::Display for CacheError {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 match self {
61 CacheError::Driver(msg) => write!(f, "Cache driver error: {}", msg),
62 CacheError::Serialization(msg) => write!(f, "Cache serialization error: {}", msg),
63 CacheError::InvalidKey(msg) => write!(f, "Invalid cache key: {}", msg),
64 CacheError::InspectionUnsupported => {
65 write!(f, "Cache metadata inspection is unsupported by this driver")
66 }
67 CacheError::InvalidInspectionLimit => write!(
68 f,
69 "Cache inspection limit must be between 1 and {MAX_CACHE_INSPECTION_ENTRIES}"
70 ),
71 }
72 }
73}
74
75impl std::error::Error for CacheError {}
76
77#[async_trait]
84pub trait CacheDriver: Send + Sync {
85 async fn get(&self, key: &str) -> Result<Option<Arc<String>>, CacheError>;
87 async fn put(&self, key: &str, value: &str, ttl_secs: Option<u64>) -> Result<(), CacheError>;
89 async fn forget(&self, key: &str) -> Result<(), CacheError>;
91 async fn flush(&self) -> Result<(), CacheError>;
93 async fn has(&self, key: &str) -> Result<bool, CacheError>;
95 async fn inspect(&self, _limit: usize) -> Result<CacheInspection, CacheError> {
97 Err(CacheError::InspectionUnsupported)
98 }
99}
100
101#[path = "cache/inspection.rs"]
102mod inspection;
103pub use inspection::{CacheEntryMetadata, CacheInspection, MAX_CACHE_INSPECTION_ENTRIES};
104
105#[derive(Clone)]
109struct CacheEntry {
110 value: Arc<String>,
111 expires_at: Option<Instant>,
112}
113
114pub struct MemoryDriver {
119 store: DashMap<String, CacheEntry>,
120 operations_since_cleanup: AtomicUsize,
121}
122
123impl MemoryDriver {
124 pub fn new() -> Self {
126 Self {
127 store: DashMap::new(),
128 operations_since_cleanup: AtomicUsize::new(0),
129 }
130 }
131
132 fn cleanup_if_due(&self) {
135 const CLEANUP_INTERVAL_OPERATIONS: usize = 256;
136 let previous = self
137 .operations_since_cleanup
138 .fetch_add(1, Ordering::Relaxed);
139 if previous > 0 && previous.is_multiple_of(CLEANUP_INTERVAL_OPERATIONS) {
140 let now = Instant::now();
141 self.store
142 .retain(|_, entry| entry.expires_at.is_none_or(|expires_at| now < expires_at));
143 }
144 }
145
146 fn get_sync(&self, key: &str) -> Option<Arc<String>> {
147 self.cleanup_if_due();
148 let entry = self.store.get(key)?;
149 if entry
150 .expires_at
151 .is_some_and(|expires_at| Instant::now() >= expires_at)
152 {
153 drop(entry);
154 self.store.remove(key);
155 return None;
156 }
157 Some(Arc::clone(&entry.value))
158 }
159
160 fn put_sync(&self, key: &str, value: &str, ttl_secs: Option<u64>) {
161 self.cleanup_if_due();
162 let expires_at = ttl_secs.map(|secs| Instant::now() + std::time::Duration::from_secs(secs));
163 self.store.insert(
164 key.to_string(),
165 CacheEntry {
166 value: Arc::new(value.to_string()),
167 expires_at,
168 },
169 );
170 }
171}
172
173impl Default for MemoryDriver {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179#[async_trait]
180impl CacheDriver for MemoryDriver {
181 #[cfg_attr(mutants, mutants::skip)]
182 async fn get(&self, key: &str) -> Result<Option<Arc<String>>, CacheError> {
183 Ok(self.get_sync(key))
184 }
185
186 async fn put(&self, key: &str, value: &str, ttl_secs: Option<u64>) -> Result<(), CacheError> {
187 self.put_sync(key, value, ttl_secs);
188 Ok(())
189 }
190
191 async fn forget(&self, key: &str) -> Result<(), CacheError> {
192 self.store.remove(key);
193 Ok(())
194 }
195
196 async fn flush(&self) -> Result<(), CacheError> {
197 self.store.clear();
198 Ok(())
199 }
200
201 async fn has(&self, key: &str) -> Result<bool, CacheError> {
202 Ok(self.get(key).await?.is_some())
203 }
204
205 async fn inspect(&self, limit: usize) -> Result<CacheInspection, CacheError> {
206 inspection::validate_inspection_limit(limit)?;
207 self.cleanup_if_due();
208 let now = Instant::now();
209 let mut entries: Vec<_> = self
210 .store
211 .iter()
212 .filter(|entry| entry.expires_at.is_none_or(|expires_at| expires_at > now))
213 .map(|entry| {
214 CacheEntryMetadata::new(
215 entry.key().clone(),
216 entry.value.len(),
217 entry
218 .expires_at
219 .map(|expires_at| expires_at.saturating_duration_since(now).as_millis())
220 .and_then(|ttl| u64::try_from(ttl).ok()),
221 )
222 })
223 .collect();
224 entries.sort_by(|left, right| left.logical_key.cmp(&right.logical_key));
225 let truncated = entries.len() > limit;
226 entries.truncate(limit);
227 Ok(CacheInspection::new(entries, truncated))
228 }
229}
230
231pub mod memory {
235 use super::MemoryDriver;
236 use std::sync::OnceLock;
237
238 static GLOBAL_MEMO_CACHE: OnceLock<MemoryDriver> = OnceLock::new();
239
240 fn get_cache() -> &'static MemoryDriver {
241 GLOBAL_MEMO_CACHE.get_or_init(MemoryDriver::new)
242 }
243
244 pub fn get(key: &str) -> Option<String> {
246 get_cache().get_sync(key).map(|value| value.to_string())
247 }
248
249 pub fn set(key: &str, value: &str) {
251 get_cache().put_sync(key, value, Some(3600));
252 }
253}
254
255#[cfg(feature = "cache-redis")]
258#[path = "cache/redis_driver.rs"]
259pub mod redis_driver;
260
261#[derive(Clone)]
271pub struct Cache {
272 driver: Arc<Box<dyn CacheDriver>>,
273}
274
275impl Cache {
276 pub fn memory() -> Self {
280 Self {
281 driver: Arc::new(Box::new(MemoryDriver::new())),
282 }
283 }
284
285 #[cfg(feature = "cache-redis")]
289 #[cfg_attr(mutants, mutants::skip)]
290 pub fn redis(redis_url: impl Into<String>) -> Result<Self, CacheError> {
291 let driver = redis_driver::RedisDriver::new(redis_url)?;
292 Ok(Self {
293 driver: Arc::new(Box::new(driver)),
294 })
295 }
296
297 pub fn custom(driver: Box<dyn CacheDriver>) -> Self {
299 Self {
300 driver: Arc::new(driver),
301 }
302 }
303
304 pub async fn get(&self, key: &str) -> Result<Option<Arc<String>>, CacheError> {
306 self.driver.get(key).await
307 }
308
309 pub async fn put(
313 &self,
314 key: &str,
315 value: &str,
316 ttl_secs: Option<u64>,
317 ) -> Result<(), CacheError> {
318 self.driver.put(key, value, ttl_secs).await
319 }
320
321 pub async fn forget(&self, key: &str) -> Result<(), CacheError> {
323 self.driver.forget(key).await
324 }
325
326 pub async fn flush(&self) -> Result<(), CacheError> {
328 self.driver.flush().await
329 }
330
331 pub async fn has(&self, key: &str) -> Result<bool, CacheError> {
333 self.driver.has(key).await
334 }
335
336 pub async fn inspect(&self, limit: usize) -> Result<CacheInspection, CacheError> {
338 self.driver.inspect(limit).await
339 }
340
341 pub async fn remember<F, Fut>(
357 &self,
358 key: &str,
359 ttl_secs: u64,
360 f: F,
361 ) -> Result<Arc<String>, CacheError>
362 where
363 F: FnOnce() -> Fut,
364 Fut: std::future::Future<Output = Result<String, CacheError>>,
365 {
366 if let Some(cached) = self.get(key).await? {
368 return Ok(cached);
369 }
370 let value = f().await?;
372 self.put(key, &value, Some(ttl_secs)).await?;
374 Ok(Arc::new(value))
375 }
376}
377
378#[cfg(test)]
379mod tests;
380
381#[cfg(test)]
382#[path = "cache_contract_tests.rs"]
383mod contract_tests;