use redis::AsyncCommands; use std::sync::Arc; use tokio::sync::Mutex; use tracing::warn; #[derive(Clone)] pub struct CacheClient { inner: Option>>, } impl CacheClient { pub async fn new(redis_url: &str) -> Self { if redis_url.is_empty() { warn!("REDIS_URL not set \u{2014} cache disabled"); return Self { inner: None }; } match redis::Client::open(redis_url) { Ok(client) => match client.get_connection_manager().await { Ok(conn) => { tracing::info!("Redis connected"); Self { inner: Some(Arc::new(Mutex::new(conn))) } } Err(e) => { warn!("Redis connection failed (cache disabled): {e}"); Self { inner: None } } }, Err(e) => { warn!("Redis URL invalid (cache disabled): {e}"); Self { inner: None } } } } pub async fn get_string(&self, key: &str) -> Option { let inner = self.inner.as_ref()?; let mut conn = inner.lock().await; conn.get(key).await.ok() } pub async fn set_string(&self, key: &str, val: &str, ttl_secs: u64) { let Some(inner) = &self.inner else { return }; let mut conn = inner.lock().await; let _ = conn.set_ex::<_, _, ()>(key, val, ttl_secs).await; } pub async fn get_json(&self, key: &str) -> Option { let s = self.get_string(key).await?; serde_json::from_str(&s).ok() } pub async fn set_json(&self, key: &str, val: &serde_json::Value, ttl_secs: u64) { if let Ok(s) = serde_json::to_string(val) { self.set_string(key, &s, ttl_secs).await; } } pub async fn del(&self, key: &str) { let Some(inner) = &self.inner else { return }; let mut conn = inner.lock().await; let _ = conn.del::<_, ()>(key).await; } pub async fn del_pattern(&self, pattern: &str) { let Some(inner) = &self.inner else { return }; let mut conn = inner.lock().await; let keys: Vec = conn.keys(pattern).await.unwrap_or_default(); if !keys.is_empty() { let _ = conn.del::<_, ()>(keys).await; } } }