kit_rs/cache/
mod.rs

1//! Cache module for Kit framework
2//!
3//! Provides a Redis-backed cache with automatic in-memory fallback.
4//!
5//! # Quick Start
6//!
7//! The cache is automatically initialized when the server starts. If Redis is
8//! available (via `REDIS_URL`), it uses Redis. Otherwise, it falls back to
9//! an in-memory cache.
10//!
11//! ```rust,ignore
12//! use kit::Cache;
13//! use std::time::Duration;
14//!
15//! // Store a value with 1 hour TTL
16//! Cache::put("user:1", &user, Some(Duration::from_secs(3600))).await?;
17//!
18//! // Retrieve it
19//! let cached: Option<User> = Cache::get("user:1").await?;
20//!
21//! // Check if exists
22//! if Cache::has("user:1").await? {
23//!     // ...
24//! }
25//!
26//! // Remove it
27//! Cache::forget("user:1").await?;
28//!
29//! // Clear all cache
30//! Cache::flush().await?;
31//! ```
32
33pub mod config;
34pub mod memory;
35pub mod redis;
36pub mod store;
37
38pub use config::{CacheConfig, CacheConfigBuilder};
39pub use memory::InMemoryCache;
40pub use redis::RedisCache;
41pub use store::CacheStore;
42
43use crate::config::Config;
44use crate::container::App;
45use crate::error::FrameworkError;
46use serde::{de::DeserializeOwned, Serialize};
47use std::sync::Arc;
48use std::time::Duration;
49
50/// Cache facade - main entry point for cache operations
51///
52/// Provides static methods for accessing the cache. The cache store
53/// is automatically initialized when the server starts.
54///
55/// # Example
56///
57/// ```rust,ignore
58/// use kit::Cache;
59/// use std::time::Duration;
60///
61/// // Store with TTL
62/// Cache::put("key", &value, Some(Duration::from_secs(3600))).await?;
63///
64/// // Store forever (no expiration)
65/// Cache::forever("key", &value).await?;
66///
67/// // Retrieve
68/// let value: Option<MyType> = Cache::get("key").await?;
69///
70/// // Get or compute (remember pattern)
71/// let value = Cache::remember("key", Some(Duration::from_secs(3600)), || async {
72///     expensive_computation().await
73/// }).await?;
74/// ```
75pub struct Cache;
76
77impl Cache {
78    /// Bootstrap the cache system
79    ///
80    /// Tries to connect to Redis first. If Redis is unavailable,
81    /// falls back to in-memory cache automatically.
82    ///
83    /// This is called automatically by `Server::run()`.
84    pub(crate) async fn bootstrap() {
85        let config = Config::get::<CacheConfig>().unwrap_or_default();
86
87        // Try Redis first
88        match RedisCache::connect(&config).await {
89            Ok(redis_cache) => {
90                App::bind::<dyn CacheStore>(Arc::new(redis_cache));
91            }
92            Err(_) => {
93                // Fallback to in-memory
94                let memory_cache = InMemoryCache::with_prefix(&config.prefix);
95                App::bind::<dyn CacheStore>(Arc::new(memory_cache));
96            }
97        }
98    }
99
100    /// Get the underlying cache store
101    pub fn store() -> Result<Arc<dyn CacheStore>, FrameworkError> {
102        App::resolve_make::<dyn CacheStore>()
103    }
104
105    /// Check if the cache is initialized
106    pub fn is_initialized() -> bool {
107        App::has_binding::<dyn CacheStore>()
108    }
109
110    // =========================================================================
111    // Main cache operations
112    // =========================================================================
113
114    /// Retrieve an item from the cache
115    ///
116    /// Returns `None` if the key doesn't exist or has expired.
117    ///
118    /// # Example
119    ///
120    /// ```rust,ignore
121    /// let user: Option<User> = Cache::get("user:1").await?;
122    /// ```
123    pub async fn get<T: DeserializeOwned>(key: &str) -> Result<Option<T>, FrameworkError> {
124        let store = Self::store()?;
125        match store.get_raw(key).await? {
126            Some(json) => {
127                let value = serde_json::from_str(&json).map_err(|e| {
128                    FrameworkError::internal(format!("Cache deserialize error: {}", e))
129                })?;
130                Ok(Some(value))
131            }
132            None => Ok(None),
133        }
134    }
135
136    /// Store an item in the cache
137    ///
138    /// If `ttl` is `None`, uses the default TTL from config (or no expiration if 0).
139    ///
140    /// # Example
141    ///
142    /// ```rust,ignore
143    /// Cache::put("user:1", &user, Some(Duration::from_secs(3600))).await?;
144    /// ```
145    pub async fn put<T: Serialize>(
146        key: &str,
147        value: &T,
148        ttl: Option<Duration>,
149    ) -> Result<(), FrameworkError> {
150        let store = Self::store()?;
151        let json = serde_json::to_string(value).map_err(|e| {
152            FrameworkError::internal(format!("Cache serialize error: {}", e))
153        })?;
154        store.put_raw(key, &json, ttl).await
155    }
156
157    /// Store an item forever (no expiration)
158    ///
159    /// # Example
160    ///
161    /// ```rust,ignore
162    /// Cache::forever("config:settings", &settings).await?;
163    /// ```
164    pub async fn forever<T: Serialize>(key: &str, value: &T) -> Result<(), FrameworkError> {
165        Self::put(key, value, None).await
166    }
167
168    /// Check if a key exists in the cache
169    ///
170    /// # Example
171    ///
172    /// ```rust,ignore
173    /// if Cache::has("user:1").await? {
174    ///     println!("User is cached");
175    /// }
176    /// ```
177    pub async fn has(key: &str) -> Result<bool, FrameworkError> {
178        let store = Self::store()?;
179        store.has(key).await
180    }
181
182    /// Remove an item from the cache
183    ///
184    /// Returns `true` if the item existed and was removed.
185    ///
186    /// # Example
187    ///
188    /// ```rust,ignore
189    /// Cache::forget("user:1").await?;
190    /// ```
191    pub async fn forget(key: &str) -> Result<bool, FrameworkError> {
192        let store = Self::store()?;
193        store.forget(key).await
194    }
195
196    /// Remove all items from the cache
197    ///
198    /// # Example
199    ///
200    /// ```rust,ignore
201    /// Cache::flush().await?;
202    /// ```
203    pub async fn flush() -> Result<(), FrameworkError> {
204        let store = Self::store()?;
205        store.flush().await
206    }
207
208    /// Increment a numeric value
209    ///
210    /// If the key doesn't exist, it's initialized to 0 before incrementing.
211    /// Returns the new value.
212    ///
213    /// # Example
214    ///
215    /// ```rust,ignore
216    /// let count = Cache::increment("visits", 1).await?;
217    /// ```
218    pub async fn increment(key: &str, amount: i64) -> Result<i64, FrameworkError> {
219        let store = Self::store()?;
220        store.increment(key, amount).await
221    }
222
223    /// Decrement a numeric value
224    ///
225    /// If the key doesn't exist, it's initialized to 0 before decrementing.
226    /// Returns the new value.
227    ///
228    /// # Example
229    ///
230    /// ```rust,ignore
231    /// let remaining = Cache::decrement("quota", 1).await?;
232    /// ```
233    pub async fn decrement(key: &str, amount: i64) -> Result<i64, FrameworkError> {
234        let store = Self::store()?;
235        store.decrement(key, amount).await
236    }
237
238    /// Get an item or store a default value if it doesn't exist
239    ///
240    /// If the key exists, returns the cached value.
241    /// If not, calls the closure to compute the value, stores it, and returns it.
242    ///
243    /// # Example
244    ///
245    /// ```rust,ignore
246    /// let user = Cache::remember("user:1", Some(Duration::from_secs(3600)), || async {
247    ///     User::find(1).await
248    /// }).await?;
249    /// ```
250    pub async fn remember<T, F, Fut>(
251        key: &str,
252        ttl: Option<Duration>,
253        default: F,
254    ) -> Result<T, FrameworkError>
255    where
256        T: Serialize + DeserializeOwned,
257        F: FnOnce() -> Fut,
258        Fut: std::future::Future<Output = Result<T, FrameworkError>>,
259    {
260        // Try to get from cache first
261        if let Some(cached) = Self::get::<T>(key).await? {
262            return Ok(cached);
263        }
264
265        // Compute the value
266        let value = default().await?;
267
268        // Store it
269        Self::put(key, &value, ttl).await?;
270
271        Ok(value)
272    }
273
274    /// Get an item or store a default value forever
275    ///
276    /// Same as `remember` but with no expiration.
277    pub async fn remember_forever<T, F, Fut>(key: &str, default: F) -> Result<T, FrameworkError>
278    where
279        T: Serialize + DeserializeOwned,
280        F: FnOnce() -> Fut,
281        Fut: std::future::Future<Output = Result<T, FrameworkError>>,
282    {
283        Self::remember(key, None, default).await
284    }
285}