cache_kit/backend/mod.rs
1//! Cache backend implementations.
2
3use crate::error::Result;
4use std::time::Duration;
5
6pub mod inmemory;
7#[cfg(feature = "memcached")]
8pub mod memcached;
9#[cfg(feature = "redis")]
10pub mod redis;
11
12pub use inmemory::InMemoryBackend;
13#[cfg(feature = "memcached")]
14pub use memcached::{MemcachedBackend, MemcachedConfig};
15#[cfg(feature = "redis")]
16pub use redis::{PoolStats, RedisBackend, RedisConfig};
17
18/// Trait for cache backend implementations.
19///
20/// Abstracts storage operations, allowing swappable backends.
21/// Implementations: InMemory (default), Redis, Memcached, RocksDB, Database, S3, etc.
22///
23/// **IMPORTANT:** All methods use `&self` instead of `&mut self` to allow concurrent access.
24/// Backend implementations should use interior mutability (RwLock, Mutex, or external storage).
25///
26/// **ASYNC:** All methods are async and must be awaited.
27#[allow(async_fn_in_trait)]
28pub trait CacheBackend: Send + Sync + Clone {
29 /// Retrieve value from cache by key.
30 ///
31 /// # Returns
32 /// - `Ok(Some(bytes))` - Value found in cache
33 /// - `Ok(None)` - Cache miss (key not found)
34 ///
35 /// # Errors
36 /// Returns `Err` if backend error occurs (connection lost, etc.)
37 async fn get(&self, key: &str) -> Result<Option<Vec<u8>>>;
38
39 /// Store value in cache with optional TTL.
40 ///
41 /// # Arguments
42 /// - `key`: Cache key
43 /// - `value`: Serialized entity bytes
44 /// - `ttl`: Time-to-live. None = use backend default or infinite
45 ///
46 /// # Errors
47 /// Returns `Err` if backend error occurs
48 async fn set(&self, key: &str, value: Vec<u8>, ttl: Option<Duration>) -> Result<()>;
49
50 /// Remove value from cache.
51 ///
52 /// # Errors
53 /// Returns `Err` if backend error occurs
54 async fn delete(&self, key: &str) -> Result<()>;
55
56 /// Check if key exists in cache (optional optimization).
57 ///
58 /// # Errors
59 /// Returns `Err` if backend error occurs
60 async fn exists(&self, key: &str) -> Result<bool> {
61 Ok(self.get(key).await?.is_some())
62 }
63
64 /// Bulk get operation (optional optimization).
65 ///
66 /// Default implementation calls `get()` for each key.
67 /// Override for batch efficiency (e.g., Redis MGET).
68 ///
69 /// # Errors
70 /// Returns `Err` if backend error occurs
71 async fn mget(&self, keys: &[&str]) -> Result<Vec<Option<Vec<u8>>>> {
72 let mut results = Vec::with_capacity(keys.len());
73 for key in keys {
74 results.push(self.get(key).await?);
75 }
76 Ok(results)
77 }
78
79 /// Bulk delete operation (optional optimization).
80 ///
81 /// Default implementation calls `delete()` for each key.
82 /// Override for batch efficiency (e.g., Redis DEL).
83 ///
84 /// # Errors
85 /// Returns `Err` if backend error occurs
86 async fn mdelete(&self, keys: &[&str]) -> Result<()> {
87 for key in keys {
88 self.delete(key).await?;
89 }
90 Ok(())
91 }
92
93 /// Health check - verify backend is accessible.
94 ///
95 /// Used for readiness probes, circuit breakers, etc.
96 ///
97 /// # Errors
98 /// Returns `Err` if backend is not accessible
99 async fn health_check(&self) -> Result<bool> {
100 Ok(true)
101 }
102
103 /// Optional: Clear all cache (use with caution).
104 ///
105 /// # Errors
106 /// Returns `Err` if operation is not implemented or fails
107 async fn clear_all(&self) -> Result<()> {
108 Err(crate::error::Error::NotImplemented(
109 "clear_all not implemented for this backend".to_string(),
110 ))
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[tokio::test]
119 async fn test_backend_exists_default() {
120 let backend = InMemoryBackend::new();
121 backend
122 .set("key", vec![1, 2, 3], None)
123 .await
124 .expect("Failed to set key");
125 assert!(backend.exists("key").await.expect("Failed to check exists"));
126 assert!(!backend
127 .exists("nonexistent")
128 .await
129 .expect("Failed to check exists"));
130 }
131}