use redis::{AsyncCommands, Client, aio::ConnectionManager};
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct RedisConfig {
pub url: String,
pub pool_size: u32,
pub timeout: Duration,
pub prefix: String,
}
impl Default for RedisConfig {
fn default() -> Self {
Self {
url: std::env::var("REDIS_URL")
.unwrap_or_else(|_| "redis://localhost:6379".to_string()),
pool_size: 5,
timeout: Duration::from_secs(5),
prefix: "kegani:".to_string(),
}
}
}
impl RedisConfig {
pub fn new(url: &str) -> Self {
Self {
url: url.to_string(),
..Default::default()
}
}
pub fn pool_size(mut self, size: u32) -> Self {
self.pool_size = size;
self
}
pub fn prefix(mut self, prefix: &str) -> Self {
self.prefix = prefix.to_string();
self
}
}
#[derive(Clone)]
pub struct RedisCache {
connection: ConnectionManager,
config: RedisConfig,
}
impl RedisCache {
pub async fn new(config: RedisConfig) -> Result<Self, redis::RedisError> {
let client = Client::open(config.url.clone())?;
let connection = ConnectionManager::new(client).await?;
Ok(Self { connection, config })
}
pub async fn connect(url: &str) -> Result<Self, redis::RedisError> {
Self::new(RedisConfig::new(url)).await
}
pub async fn get(&self, key: &str) -> Result<Option<String>, redis::RedisError> {
let mut conn = self.connection.clone();
let prefixed_key = format!("{}{}", self.config.prefix, key);
conn.get(&prefixed_key).await
}
pub async fn get_json<T: serde::de::DeserializeOwned>(&self, key: &str) -> Result<Option<T>, CacheError> {
match self.get(key).await {
Ok(Some(value)) => {
serde_json::from_str(&value).map_err(CacheError::Deserialize)
}
Ok(None) => Ok(None),
Err(e) => Err(CacheError::Redis(e)),
}
}
pub async fn set(&self, key: &str, value: &str, ttl: Option<Duration>) -> Result<(), redis::RedisError> {
let mut conn = self.connection.clone();
let prefixed_key = format!("{}{}", self.config.prefix, key);
if let Some(duration) = ttl {
conn.set_ex(&prefixed_key, value, duration.as_secs()).await
} else {
conn.set(&prefixed_key, value).await
}
}
pub async fn set_json<T: serde::Serialize>(&self, key: &str, value: &T, ttl: Option<Duration>) -> Result<(), CacheError> {
let json = serde_json::to_string(value).map_err(CacheError::Serialize)?;
self.set(key, &json, ttl).await.map_err(CacheError::Redis)
}
pub async fn delete(&self, key: &str) -> Result<(), redis::RedisError> {
let mut conn = self.connection.clone();
let prefixed_key = format!("{}{}", self.config.prefix, key);
conn.del(&prefixed_key).await
}
pub async fn delete_pattern(&self, pattern: &str) -> Result<u64, redis::RedisError> {
let mut conn = self.connection.clone();
let prefixed_pattern = format!("{}{}", self.config.prefix, pattern);
let keys: Vec<String> = conn.keys(&prefixed_pattern).await?;
if keys.is_empty() {
return Ok(0);
}
conn.del(keys).await
}
pub async fn exists(&self, key: &str) -> Result<bool, redis::RedisError> {
let mut conn = self.connection.clone();
let prefixed_key = format!("{}{}", self.config.prefix, key);
conn.exists(&prefixed_key).await
}
pub async fn expire(&self, key: &str, ttl: Duration) -> Result<(), redis::RedisError> {
let mut conn = self.connection.clone();
let prefixed_key = format!("{}{}", self.config.prefix, key);
conn.expire(&prefixed_key, ttl.as_secs() as i64).await
}
pub async fn incr(&self, key: &str) -> Result<i64, redis::RedisError> {
let mut conn = self.connection.clone();
let prefixed_key = format!("{}{}", self.config.prefix, key);
conn.incr(&prefixed_key, 1).await
}
pub async fn get_or_set<F, Fut, T>(&self, key: &str, ttl: Duration, fetch: F) -> Result<T, CacheError>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = Result<T, CacheError>>,
T: serde::de::DeserializeOwned + serde::Serialize,
{
if let Some(cached) = self.get_json::<T>(key).await? {
return Ok(cached);
}
let value = fetch().await?;
self.set_json(key, &value, Some(ttl)).await?;
Ok(value)
}
pub async fn ping(&self) -> Result<(), redis::RedisError> {
let mut conn = self.connection.clone();
redis::cmd("PING").query_async::<String>(&mut conn).await?;
Ok(())
}
}
#[derive(Debug)]
pub enum CacheError {
Redis(redis::RedisError),
Serialize(serde_json::Error),
Deserialize(serde_json::Error),
}
impl std::fmt::Display for CacheError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CacheError::Redis(e) => write!(f, "Redis error: {}", e),
CacheError::Serialize(e) => write!(f, "Serialization error: {}", e),
CacheError::Deserialize(e) => write!(f, "Deserialization error: {}", e),
}
}
}
impl std::error::Error for CacheError {}
impl From<redis::RedisError> for CacheError {
fn from(e: redis::RedisError) -> Self {
CacheError::Redis(e)
}
}
impl From<serde_json::Error> for CacheError {
fn from(e: serde_json::Error) -> Self {
CacheError::Serialize(e)
}
}