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}