armature_cache/
redis_cache.rs1use crate::config::CacheConfig;
4use crate::error::{CacheError, CacheResult};
5use crate::traits::CacheStore;
6use armature_log::{debug, trace};
7use async_trait::async_trait;
8use redis::{AsyncCommands, Client, aio::ConnectionManager};
9use std::time::Duration;
10
11#[derive(Clone)]
13pub struct RedisCache {
14 connection: ConnectionManager,
15 config: CacheConfig,
16}
17
18impl RedisCache {
19 pub async fn new(config: CacheConfig) -> CacheResult<Self> {
38 debug!("Connecting to Redis cache: {}", config.url);
39 let client =
40 Client::open(config.url.as_str()).map_err(|e| CacheError::Connection(e.to_string()))?;
41
42 let connection = ConnectionManager::new(client)
43 .await
44 .map_err(|e| CacheError::Connection(e.to_string()))?;
45
46 debug!("Redis cache connection established");
47 Ok(Self { connection, config })
48 }
49
50 pub fn connection(&self) -> &ConnectionManager {
52 &self.connection
53 }
54
55 fn build_key(&self, key: &str) -> String {
57 self.config.build_key(key)
58 }
59}
60
61#[async_trait]
62impl CacheStore for RedisCache {
63 async fn get_json(&self, key: &str) -> CacheResult<Option<String>> {
64 let key = self.build_key(key);
65 trace!("Cache GET: {}", key);
66 let mut conn = self.connection.clone();
67
68 let value: Option<String> = conn.get(&key).await?;
69 trace!(
70 "Cache {} for: {}",
71 if value.is_some() { "HIT" } else { "MISS" },
72 key
73 );
74 Ok(value)
75 }
76
77 async fn set_json(&self, key: &str, value: String, ttl: Option<Duration>) -> CacheResult<()> {
78 let key = self.build_key(key);
79 trace!("Cache SET: {} (ttl: {:?})", key, ttl);
80 let mut conn = self.connection.clone();
81
82 let ttl = ttl.or(self.config.default_ttl);
83
84 if let Some(ttl) = ttl {
85 let ttl_seconds = ttl.as_secs();
86 let _: () = conn.set_ex(&key, value, ttl_seconds).await?;
87 } else {
88 let _: () = conn.set(&key, value).await?;
89 }
90
91 Ok(())
92 }
93
94 async fn delete(&self, key: &str) -> CacheResult<()> {
95 let key = self.build_key(key);
96 let mut conn = self.connection.clone();
97 let _: () = conn.del(&key).await?;
98 Ok(())
99 }
100
101 async fn exists(&self, key: &str) -> CacheResult<bool> {
102 let key = self.build_key(key);
103 let mut conn = self.connection.clone();
104 let exists: bool = conn.exists(&key).await?;
105 Ok(exists)
106 }
107
108 async fn clear(&self) -> CacheResult<()> {
109 let mut conn = self.connection.clone();
110 let _: () = redis::cmd("FLUSHDB").query_async(&mut conn).await?;
111 Ok(())
112 }
113
114 async fn ttl(&self, key: &str) -> CacheResult<Option<Duration>> {
115 let key = self.build_key(key);
116 let mut conn = self.connection.clone();
117
118 let ttl_seconds: i64 = conn.ttl(&key).await?;
119
120 match ttl_seconds {
121 -2 => Ok(None), -1 => Ok(None), seconds if seconds > 0 => Ok(Some(Duration::from_secs(seconds as u64))),
124 _ => Ok(None),
125 }
126 }
127
128 async fn expire(&self, key: &str, ttl: Duration) -> CacheResult<()> {
129 let key = self.build_key(key);
130 let mut conn = self.connection.clone();
131 let ttl_seconds = ttl.as_secs();
132 let _: () = conn.expire(&key, ttl_seconds as i64).await?;
133 Ok(())
134 }
135
136 async fn increment(&self, key: &str, delta: i64) -> CacheResult<i64> {
137 let key = self.build_key(key);
138 let mut conn = self.connection.clone();
139 let new_value: i64 = conn.incr(&key, delta).await?;
140 Ok(new_value)
141 }
142
143 async fn decrement(&self, key: &str, delta: i64) -> CacheResult<i64> {
144 let key = self.build_key(key);
145 let mut conn = self.connection.clone();
146 let new_value: i64 = conn.decr(&key, delta).await?;
147 Ok(new_value)
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_build_key() {
157 let config = CacheConfig::redis("redis://localhost:6379")
158 .unwrap()
159 .with_key_prefix("test");
160
161 assert_eq!(config.build_key("key"), "test:key");
164 }
165}