f2e745a
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<RedisError> for CacheError {
fn from(err: RedisError) -> Self {
CacheError::Backend(err)
}
}
impl From<serde_json::Error> 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<V>(&self, key: &str) -> Result<Option<V>, 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<V>(
&self,
key: &str,
value: &V,
ttl: Option<Duration>,
) -> 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<bool, CacheError> {
Ok(redis::exists(&self.redis, key).await?)
}
pub async fn get_bytes(&self, key: &str) -> Result<Option<Vec<u8>>, 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<u8>,
ttl: Option<Duration>,
) -> 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(())
}
}