kit_rs/cache/
redis.rs

1//! Redis-backed cache implementation
2
3use async_trait::async_trait;
4use redis::{aio::ConnectionManager, AsyncCommands, Client};
5use std::time::Duration;
6
7use super::config::CacheConfig;
8use super::store::CacheStore;
9use crate::error::FrameworkError;
10
11/// Redis cache implementation
12///
13/// Uses redis-rs with async/tokio runtime for high-performance caching.
14pub struct RedisCache {
15    conn: ConnectionManager,
16    prefix: String,
17    default_ttl: Option<Duration>,
18}
19
20impl RedisCache {
21    /// Create a new Redis cache connection
22    pub async fn connect(config: &CacheConfig) -> Result<Self, FrameworkError> {
23        let client = Client::open(config.url.as_str()).map_err(|e| {
24            FrameworkError::internal(format!("Redis connection error: {}", e))
25        })?;
26
27        let conn = ConnectionManager::new(client).await.map_err(|e| {
28            FrameworkError::internal(format!("Redis connection manager error: {}", e))
29        })?;
30
31        let default_ttl = if config.default_ttl > 0 {
32            Some(Duration::from_secs(config.default_ttl))
33        } else {
34            None
35        };
36
37        Ok(Self {
38            conn,
39            prefix: config.prefix.clone(),
40            default_ttl,
41        })
42    }
43
44    fn prefixed_key(&self, key: &str) -> String {
45        format!("{}{}", self.prefix, key)
46    }
47}
48
49#[async_trait]
50impl CacheStore for RedisCache {
51    async fn get_raw(&self, key: &str) -> Result<Option<String>, FrameworkError> {
52        let mut conn = self.conn.clone();
53        let key = self.prefixed_key(key);
54
55        let value: Option<String> = conn.get(&key).await.map_err(|e| {
56            FrameworkError::internal(format!("Cache get error: {}", e))
57        })?;
58
59        Ok(value)
60    }
61
62    async fn put_raw(
63        &self,
64        key: &str,
65        value: &str,
66        ttl: Option<Duration>,
67    ) -> Result<(), FrameworkError> {
68        let mut conn = self.conn.clone();
69        let key = self.prefixed_key(key);
70
71        let effective_ttl = ttl.or(self.default_ttl);
72
73        if let Some(duration) = effective_ttl {
74            conn.set_ex::<_, _, ()>(&key, value, duration.as_secs())
75                .await
76                .map_err(|e| FrameworkError::internal(format!("Cache set error: {}", e)))?;
77        } else {
78            conn.set::<_, _, ()>(&key, value)
79                .await
80                .map_err(|e| FrameworkError::internal(format!("Cache set error: {}", e)))?;
81        }
82
83        Ok(())
84    }
85
86    async fn has(&self, key: &str) -> Result<bool, FrameworkError> {
87        let mut conn = self.conn.clone();
88        let key = self.prefixed_key(key);
89
90        let exists: bool = conn.exists(&key).await.map_err(|e| {
91            FrameworkError::internal(format!("Cache exists error: {}", e))
92        })?;
93
94        Ok(exists)
95    }
96
97    async fn forget(&self, key: &str) -> Result<bool, FrameworkError> {
98        let mut conn = self.conn.clone();
99        let key = self.prefixed_key(key);
100
101        let deleted: i64 = conn.del(&key).await.map_err(|e| {
102            FrameworkError::internal(format!("Cache delete error: {}", e))
103        })?;
104
105        Ok(deleted > 0)
106    }
107
108    async fn flush(&self) -> Result<(), FrameworkError> {
109        let mut conn = self.conn.clone();
110
111        // Use KEYS to find and delete all keys with our prefix
112        // Note: KEYS is O(N) and should be used carefully in production
113        let pattern = format!("{}*", self.prefix);
114        let keys: Vec<String> = redis::cmd("KEYS")
115            .arg(&pattern)
116            .query_async(&mut conn)
117            .await
118            .map_err(|e| FrameworkError::internal(format!("Cache flush scan error: {}", e)))?;
119
120        if !keys.is_empty() {
121            conn.del::<_, ()>(keys)
122                .await
123                .map_err(|e| FrameworkError::internal(format!("Cache flush delete error: {}", e)))?;
124        }
125
126        Ok(())
127    }
128
129    async fn increment(&self, key: &str, amount: i64) -> Result<i64, FrameworkError> {
130        let mut conn = self.conn.clone();
131        let key = self.prefixed_key(key);
132
133        let value: i64 = conn.incr(&key, amount).await.map_err(|e| {
134            FrameworkError::internal(format!("Cache increment error: {}", e))
135        })?;
136
137        Ok(value)
138    }
139
140    async fn decrement(&self, key: &str, amount: i64) -> Result<i64, FrameworkError> {
141        let mut conn = self.conn.clone();
142        let key = self.prefixed_key(key);
143
144        let value: i64 = conn.decr(&key, amount).await.map_err(|e| {
145            FrameworkError::internal(format!("Cache decrement error: {}", e))
146        })?;
147
148        Ok(value)
149    }
150}