use std::fmt; use std::time::Duration; use ::redis::RedisError; use serde::Serialize; use serde::de::DeserializeOwned; use crate::redis::{self, AppRedis, RedisConnection}; #[derive(Debug)] pub enum CacheError { Serialization(String), Backend(RedisError), } impl fmt::Display for CacheError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { CacheError::Serialization(msg) => write!(f, "cache serialization error: {msg}"), CacheError::Backend(err) => write!(f, "cache backend error: {err}"), } } } impl std::error::Error for CacheError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { CacheError::Backend(err) => Some(err), _ => None, } } } impl From for CacheError { fn from(err: RedisError) -> Self { CacheError::Backend(err) } } impl From for CacheError { fn from(err: serde_json::Error) -> Self { CacheError::Serialization(err.to_string()) } } #[derive(Clone)] pub struct CacheClient { redis: AppRedis, } impl CacheClient { pub fn new(redis: AppRedis) -> Self { Self { redis } } pub fn raw(&self) -> &AppRedis { &self.redis } pub async fn get(&self, key: &str) -> Result, CacheError> where V: DeserializeOwned, { self.get_bytes(key) .await? .map(|bytes| serde_json::from_slice(&bytes).map_err(CacheError::from)) .transpose() } pub async fn set( &self, key: &str, value: &V, ttl: Option, ) -> Result<(), CacheError> where V: Serialize, { self.set_bytes(key, serde_json::to_vec(value)?, ttl).await } pub async fn delete(&self, key: &str) -> Result<(), CacheError> { Ok(redis::delete(&self.redis, key).await?) } pub async fn exists(&self, key: &str) -> Result { Ok(redis::exists(&self.redis, key).await?) } pub async fn get_bytes(&self, key: &str) -> Result>, CacheError> { let mut conn = self.redis.connection_clone(); let mut cmd = ::redis::cmd("GET"); cmd.arg(key); let value = match &mut conn { RedisConnection::Standalone(conn) => cmd.query_async(conn).await?, RedisConnection::Cluster(conn) => cmd.query_async(conn).await?, }; Ok(value) } pub async fn set_bytes( &self, key: &str, value: Vec, ttl: Option, ) -> Result<(), CacheError> { if ttl.is_some_and(|ttl| ttl.is_zero()) { return self.delete(key).await; } let mut cmd = ::redis::cmd("SET"); cmd.arg(key).arg(value); if let Some(ttl) = ttl { let millis = ttl.as_millis().max(1).min(u64::MAX as u128) as u64; cmd.arg("PX").arg(millis); } let mut conn = self.redis.connection_clone(); match &mut conn { RedisConnection::Standalone(conn) => cmd.exec_async(conn).await?, RedisConnection::Cluster(conn) => cmd.exec_async(conn).await?, } Ok(()) } }