Skip to main content

ferro_rs/cache/
mod.rs

1//! Cache module for Ferro 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 ferro_rs::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 ferro_rs::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)
152            .map_err(|e| FrameworkError::internal(format!("Cache serialize error: {e}")))?;
153        store.put_raw(key, &json, ttl).await
154    }
155
156    /// Store an item forever (no expiration)
157    ///
158    /// # Example
159    ///
160    /// ```rust,ignore
161    /// Cache::forever("config:settings", &settings).await?;
162    /// ```
163    pub async fn forever<T: Serialize>(key: &str, value: &T) -> Result<(), FrameworkError> {
164        Self::put(key, value, None).await
165    }
166
167    /// Check if a key exists in the cache
168    ///
169    /// # Example
170    ///
171    /// ```rust,ignore
172    /// if Cache::has("user:1").await? {
173    ///     println!("User is cached");
174    /// }
175    /// ```
176    pub async fn has(key: &str) -> Result<bool, FrameworkError> {
177        let store = Self::store()?;
178        store.has(key).await
179    }
180
181    /// Remove an item from the cache
182    ///
183    /// Returns `true` if the item existed and was removed.
184    ///
185    /// # Example
186    ///
187    /// ```rust,ignore
188    /// Cache::forget("user:1").await?;
189    /// ```
190    pub async fn forget(key: &str) -> Result<bool, FrameworkError> {
191        let store = Self::store()?;
192        store.forget(key).await
193    }
194
195    /// Remove all items from the cache
196    ///
197    /// # Example
198    ///
199    /// ```rust,ignore
200    /// Cache::flush().await?;
201    /// ```
202    pub async fn flush() -> Result<(), FrameworkError> {
203        let store = Self::store()?;
204        store.flush().await
205    }
206
207    /// Increment a numeric value
208    ///
209    /// If the key doesn't exist, it's initialized to 0 before incrementing.
210    /// Returns the new value.
211    ///
212    /// # Example
213    ///
214    /// ```rust,ignore
215    /// let count = Cache::increment("visits", 1).await?;
216    /// ```
217    pub async fn increment(key: &str, amount: i64) -> Result<i64, FrameworkError> {
218        let store = Self::store()?;
219        store.increment(key, amount).await
220    }
221
222    /// Decrement a numeric value
223    ///
224    /// If the key doesn't exist, it's initialized to 0 before decrementing.
225    /// Returns the new value.
226    ///
227    /// # Example
228    ///
229    /// ```rust,ignore
230    /// let remaining = Cache::decrement("quota", 1).await?;
231    /// ```
232    pub async fn decrement(key: &str, amount: i64) -> Result<i64, FrameworkError> {
233        let store = Self::store()?;
234        store.decrement(key, amount).await
235    }
236
237    /// Set a TTL on an existing key
238    ///
239    /// Returns `true` if the key existed and TTL was set, `false` if key not found.
240    ///
241    /// # Example
242    ///
243    /// ```rust,ignore
244    /// Cache::expire("counter", Duration::from_secs(60)).await?;
245    /// ```
246    pub async fn expire(key: &str, ttl: Duration) -> Result<bool, FrameworkError> {
247        let store = Self::store()?;
248        store.expire(key, ttl).await
249    }
250
251    /// Get an item or store a default value if it doesn't exist
252    ///
253    /// If the key exists, returns the cached value.
254    /// If not, calls the closure to compute the value, stores it, and returns it.
255    ///
256    /// # Example
257    ///
258    /// ```rust,ignore
259    /// let user = Cache::remember("user:1", Some(Duration::from_secs(3600)), || async {
260    ///     User::find(1).await
261    /// }).await?;
262    /// ```
263    pub async fn remember<T, F, Fut>(
264        key: &str,
265        ttl: Option<Duration>,
266        default: F,
267    ) -> Result<T, FrameworkError>
268    where
269        T: Serialize + DeserializeOwned,
270        F: FnOnce() -> Fut,
271        Fut: std::future::Future<Output = Result<T, FrameworkError>>,
272    {
273        // Try to get from cache first
274        if let Some(cached) = Self::get::<T>(key).await? {
275            return Ok(cached);
276        }
277
278        // Compute the value
279        let value = default().await?;
280
281        // Store it
282        Self::put(key, &value, ttl).await?;
283
284        Ok(value)
285    }
286
287    /// Get an item or store a default value forever
288    ///
289    /// Same as `remember` but with no expiration.
290    pub async fn remember_forever<T, F, Fut>(key: &str, default: F) -> Result<T, FrameworkError>
291    where
292        T: Serialize + DeserializeOwned,
293        F: FnOnce() -> Fut,
294        Fut: std::future::Future<Output = Result<T, FrameworkError>>,
295    {
296        Self::remember(key, None, default).await
297    }
298}