1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use std::fmt::Debug;
4use std::time::Duration;
5use thiserror::Error;
6use uuid;
7
8pub mod file;
10pub mod in_memory;
11pub mod memcached;
12pub mod patterns;
13pub mod proxy_service;
14pub mod redis;
15pub mod tests;
16
17pub use file::FileCache;
19pub use in_memory::InMemoryCache;
20#[cfg(feature = "memcached")]
21pub use memcached::MemcachedCache;
22pub use redis::RedisCache;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26pub enum CachePolicy {
27 LRU,
29 FIFO,
31 MRU,
33 LFU,
35 None,
37}
38
39impl Default for CachePolicy {
40 fn default() -> Self {
41 Self::LRU
42 }
43}
44
45#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
47pub enum CacheType {
48 #[default]
50 InMemory,
51 Redis,
53 Memcached,
55 File,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct CacheConfig {
62 pub cache_type: CacheType,
64 pub policy: CachePolicy,
66 pub max_size_bytes: Option<usize>,
68
69 pub default_ttl_seconds: u64,
74 pub default_ttl: Option<Duration>,
76
77 pub hosts: Vec<String>,
81 pub port: Option<u16>,
82 pub connection_timeout_seconds: u64,
84 pub operation_timeout_seconds: u64,
86
87 pub username: Option<String>,
91 pub password: Option<String>,
92 pub tls_enabled: bool,
94
95 pub database: Option<u8>,
99 pub file_path: Option<String>,
100 pub namespace: Option<String>,
102
103 #[cfg(feature = "database")]
107 pub pool: crate::data::database::PoolConfig,
108 #[cfg(not(feature = "database"))]
109 pub pool: DefaultPoolConfig,
110
111 pub extra_params: std::collections::HashMap<String, String>,
113}
114
115#[cfg(not(feature = "database"))]
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct DefaultPoolConfig {
119 pub max_connections: u32,
120 pub min_connections: u32,
121 pub idle_timeout_seconds: u64,
122 pub max_lifetime_seconds: u64,
123 pub acquire_timeout_seconds: u64,
124}
125
126#[cfg(not(feature = "database"))]
127impl Default for DefaultPoolConfig {
128 fn default() -> Self {
129 Self {
130 max_connections: 10,
131 min_connections: 1,
132 idle_timeout_seconds: 300,
133 max_lifetime_seconds: 1800,
134 acquire_timeout_seconds: 30,
135 }
136 }
137}
138
139impl CacheConfig {
140 pub fn get_default_ttl(&self) -> Duration {
142 self.default_ttl
143 .unwrap_or_else(|| Duration::from_secs(self.default_ttl_seconds))
144 }
145}
146
147impl Default for CacheConfig {
148 fn default() -> Self {
149 Self {
150 cache_type: CacheType::InMemory,
151 policy: CachePolicy::default(),
152 max_size_bytes: Some(1024 * 1024 * 10), default_ttl_seconds: 300,
154 default_ttl: None,
155 hosts: vec!["localhost".to_string()],
156 port: None,
157 connection_timeout_seconds: 5,
158 operation_timeout_seconds: 2,
159 username: None,
160 password: None,
161 tls_enabled: false,
162 database: None,
163 file_path: None,
164 namespace: None,
165 #[cfg(feature = "database")]
166 pool: crate::data::database::PoolConfig::default(),
167 #[cfg(not(feature = "database"))]
168 pool: DefaultPoolConfig::default(),
169 extra_params: std::collections::HashMap::new(),
170 }
171 }
172}
173
174#[derive(Debug, Error)]
176pub enum CacheError {
177 #[error("connection error: {0}")]
179 Connection(String),
180
181 #[error("set error: {0}")]
183 Set(String),
184
185 #[error("get error: {0}")]
187 Get(String),
188
189 #[error("delete error: {0}")]
191 Delete(String),
192
193 #[error("flush error: {0}")]
195 Flush(String),
196
197 #[error("configuration error: {0}")]
199 Configuration(String),
200
201 #[error("serialization error: {0}")]
203 Serialization(String),
204
205 #[error("IPC error: {0}")]
207 Ipc(String),
208
209 #[error("operation error: {0}")]
211 Operation(String),
212
213 #[error("internal error: {0}")]
215 Internal(String),
216}
217
218pub type CacheResult<T> = std::result::Result<T, CacheError>;
220
221#[derive(Debug, Clone, Default, Serialize, Deserialize)]
223pub struct CacheStats {
224 pub hits: Option<u64>,
226 pub misses: Option<u64>,
228 pub sets: Option<u64>,
230 pub deletes: Option<u64>,
232 pub item_count: Option<u64>,
234 pub memory_used_bytes: Option<u64>,
236 pub additional_metrics: std::collections::HashMap<String, String>,
238}
239
240#[async_trait]
242pub trait CacheService: Send + Sync {
243 async fn get(&self, key: &str) -> CacheResult<Option<Vec<u8>>>;
245
246 async fn set(&self, key: &str, value: &[u8], ttl: Option<Duration>) -> CacheResult<()>;
248
249 async fn delete(&self, key: &str) -> CacheResult<bool>;
251
252 async fn flush(&self) -> CacheResult<()>;
254
255 async fn stats(&self) -> CacheResult<CacheStats>;
257
258 async fn ping(&self) -> CacheResult<()>;
260
261 async fn close(&self) -> CacheResult<()>;
263
264 async fn exists(&self, key: &str) -> CacheResult<bool> {
270 Ok(self.get(key).await?.is_some())
272 }
273
274 async fn set_nx(&self, key: &str, value: &[u8], ttl: Option<Duration>) -> CacheResult<bool> {
276 if self.exists(key).await? {
278 Ok(false)
279 } else {
280 self.set(key, value, ttl).await?;
281 Ok(true)
282 }
283 }
284
285 async fn get_set(&self, key: &str, value: &[u8]) -> CacheResult<Option<Vec<u8>>> {
287 let old_value = self.get(key).await?;
289 self.set(key, value, None).await?;
290 Ok(old_value)
291 }
292
293 async fn increment(&self, key: &str, delta: i64) -> CacheResult<i64> {
295 let current_value = match self.get(key).await? {
297 Some(bytes) => {
298 let value_str = String::from_utf8(bytes)
299 .map_err(|e| CacheError::Operation(format!("Invalid UTF-8: {}", e)))?;
300 value_str.parse::<i64>()
301 .map_err(|e| CacheError::Operation(format!("Invalid integer: {}", e)))?
302 }
303 None => 0,
304 };
305
306 let new_value = current_value + delta;
307 self.set(key, new_value.to_string().as_bytes(), None).await?;
308 Ok(new_value)
309 }
310
311 async fn decrement(&self, key: &str, delta: i64) -> CacheResult<i64> {
313 self.increment(key, -delta).await
314 }
315
316 async fn set_many(
318 &self,
319 items: &std::collections::HashMap<String, Vec<u8>>,
320 ttl: Option<Duration>,
321 ) -> CacheResult<()> {
322 for (key, value) in items {
324 self.set(key, value, ttl).await?;
325 }
326 Ok(())
327 }
328
329 async fn get_many(
331 &self,
332 keys: &[String],
333 ) -> CacheResult<std::collections::HashMap<String, Vec<u8>>> {
334 let mut result = std::collections::HashMap::new();
336 for key in keys {
337 if let Some(value) = self.get(key).await? {
338 result.insert(key.clone(), value);
339 }
340 }
341 Ok(result)
342 }
343
344 async fn delete_many(&self, keys: &[String]) -> CacheResult<u64> {
346 let mut count = 0;
348 for key in keys {
349 if self.delete(key).await? {
350 count += 1;
351 }
352 }
353 Ok(count)
354 }
355
356 async fn clear(&self, namespace: Option<&str>) -> CacheResult<()> {
358 if namespace.is_none() {
361 self.flush().await
362 } else {
363 Err(CacheError::Operation(
364 "Namespace-specific clear not implemented for this backend. Use flush() to clear all.".to_string(),
365 ))
366 }
367 }
368
369 async fn lock(&self, key: &str, ttl: Duration) -> CacheResult<Option<String>> {
371 let lock_key = format!("lock:{}", key);
373 let token = uuid::Uuid::new_v4().to_string();
374
375 if self.set_nx(&lock_key, token.as_bytes(), Some(ttl)).await? {
376 Ok(Some(token))
377 } else {
378 Ok(None)
379 }
380 }
381
382 async fn unlock(&self, key: &str, token: &str) -> CacheResult<bool> {
384 let lock_key = format!("lock:{}", key);
386
387 if let Some(stored_token_bytes) = self.get(&lock_key).await? {
389 let stored_token = String::from_utf8(stored_token_bytes)
390 .map_err(|e| CacheError::Operation(format!("Invalid UTF-8 in stored token: {}", e)))?;
391
392 if stored_token == token {
393 self.delete(&lock_key).await?;
394 Ok(true)
395 } else {
396 Ok(false) }
398 } else {
399 Ok(false) }
401 }
402
403 fn get_cache_type(&self) -> CacheType {
405 CacheType::InMemory
406 }
407
408 fn get_default_ttl(&self) -> Duration {
410 Duration::from_secs(0)
411 }
412
413 async fn get_string(&self, key: &str) -> CacheResult<Option<String>> {
415 match self.get(key).await? {
416 Some(bytes) => match String::from_utf8(bytes) {
417 Ok(s) => Ok(Some(s)),
418 Err(e) => Err(CacheError::Get(format!("Invalid UTF-8: {}", e))),
419 },
420 None => Ok(None),
421 }
422 }
423
424 async fn set_string(&self, key: &str, value: &str, ttl: Option<Duration>) -> CacheResult<()> {
426 self.set(key, value.as_bytes(), ttl).await
427 }
428}
429
430pub async fn create_cache_service(config: &CacheConfig) -> CacheResult<Box<dyn CacheService>> {
432 match config.cache_type {
433 CacheType::InMemory => {
434 let cache = InMemoryCache::new(config);
435 Ok(Box::new(cache) as Box<dyn CacheService>)
436 }
437 CacheType::Redis => {
438 #[cfg(feature = "redis_cache")]
439 {
440 let cache = RedisCache::connect(config).await?;
441 Ok(Box::new(cache) as Box<dyn CacheService>)
442 }
443 #[cfg(not(feature = "redis_cache"))]
444 {
445 Err(CacheError::Configuration(
446 "Redis cache support is not enabled. Enable the 'redis_cache' feature."
447 .to_string(),
448 ))
449 }
450 }
451 CacheType::Memcached => {
452 #[cfg(feature = "memcached")]
453 {
454 let cache = MemcachedCache::connect(config).await?;
455 Ok(Box::new(cache) as Box<dyn CacheService>)
456 }
457 #[cfg(not(feature = "memcached"))]
458 {
459 Err(CacheError::Configuration(
460 "Memcached support is not enabled. Enable the 'memcached' feature.".to_string(),
461 ))
462 }
463 }
464 CacheType::File => {
465 #[cfg(feature = "file_cache")]
466 {
467 let cache = FileCache::new(config).await?;
468 Ok(Box::new(cache) as Box<dyn CacheService>)
469 }
470 #[cfg(not(feature = "file_cache"))]
471 {
472 Err(CacheError::Configuration(
473 "File cache support is not enabled. Enable the 'file_cache' feature."
474 .to_string(),
475 ))
476 }
477 }
478 }
479}