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}