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}