use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use cratefield_core::{Config, KeyValue, KvError};
use redis::aio::ConnectionManager;
use crate::ports::rate_limit::RedisRateLimiter;
pub struct RedisKv(ConnectionManager);
impl RedisKv {
pub fn new(conn: ConnectionManager) -> Self {
Self(conn)
}
}
fn op(err: &redis::RedisError) -> KvError {
KvError::Operation(err.to_string())
}
#[async_trait]
impl KeyValue for RedisKv {
async fn get(&self, key: &str) -> Result<Option<String>, KvError> {
let mut conn = self.0.clone();
redis::cmd("GET")
.arg(format!("kv:{key}"))
.query_async(&mut conn)
.await
.map_err(|err| op(&err))
}
async fn put(&self, key: &str, value: &str, ttl: Option<Duration>) -> Result<(), KvError> {
let mut conn = self.0.clone();
let mut cmd = redis::cmd("SET");
cmd.arg(format!("kv:{key}")).arg(value);
if let Some(ttl) = ttl {
let millis = i64::try_from(ttl.as_millis()).unwrap_or(i64::MAX).max(1);
cmd.arg("PX").arg(millis);
}
cmd.query_async::<()>(&mut conn)
.await
.map_err(|err| op(&err))
}
async fn delete(&self, key: &str) -> Result<(), KvError> {
let mut conn = self.0.clone();
redis::cmd("DEL")
.arg(format!("kv:{key}"))
.query_async::<i64>(&mut conn)
.await
.map_err(|err| op(&err))?;
Ok(())
}
}
pub struct RedisBundle {
pub rate_limiter: Arc<RedisRateLimiter>,
pub kv: Arc<RedisKv>,
}
#[derive(Debug, thiserror::Error)]
pub enum RedisPortError {
#[error("redis port setup failed: {0}")]
Connection(String),
}
pub async fn redis_from_env(config: &dyn Config) -> Result<Option<RedisBundle>, RedisPortError> {
let Some(url) = config.get("REDIS_URL") else {
return Ok(None);
};
let client = redis::Client::open(url.as_str())
.map_err(|err| RedisPortError::Connection(err.to_string()))?;
let manager = client
.get_connection_manager()
.await
.map_err(|err| RedisPortError::Connection(err.to_string()))?;
let rate_limiter = Arc::new(RedisRateLimiter::from_env(manager.clone(), config));
let kv = Arc::new(RedisKv::new(manager));
Ok(Some(RedisBundle { rate_limiter, kv }))
}