use crate::cache::lru::LruTtlCache; use crate::cache::redis::AppRedis; use crate::config::AppConfig; use crate::error::AppResult; use ::redis::Cmd; use serde::Serialize; use serde::de::DeserializeOwned; use std::time::Duration; pub mod lru; pub mod redis; pub struct AppCache { l1: LruTtlCache, l2: AppRedis, key_prefix: String, default_ttl: Duration, } impl AppCache { pub async fn from_config(config: &AppConfig) -> AppResult { let cap = config.lru_default_capacity()?; let ttl = Duration::from_secs(config.lru_default_ttl_secs()?); let l2 = AppRedis::from_config(config).await?; let key_prefix = config.redis_key_prefix()?; Ok(Self { l1: LruTtlCache::new(cap, ttl), l2, key_prefix, default_ttl: ttl, }) } pub async fn get(&self, key: &str) -> Option { if let Some(json) = self.l1.get(&key.to_string()) { return serde_json::from_str(&json).ok(); } let full_key = self.full_key(key); let mut conn = self.l2.get_connection(); let json: String = Cmd::new() .arg("GET") .arg(&full_key) .query_async::>(&mut conn) .await .ok()??; let value: T = serde_json::from_str(&json).ok()?; self.l1.insert(key.to_string(), json); Some(value) } pub async fn get_l2_only(&self, key: &str) -> Option { let full_key = self.full_key(key); let mut conn = self.l2.get_connection(); let json: String = Cmd::new() .arg("GET") .arg(&full_key) .query_async::>(&mut conn) .await .ok()??; serde_json::from_str(&json).ok() } pub async fn set( &self, key: &str, value: &T, ttl: Option, ) -> AppResult<()> { let json = serde_json::to_string(value)?; let full_key = self.full_key(key); let ttl_duration = ttl.unwrap_or(self.default_ttl); let ttl_secs = ttl_duration.as_secs() as usize; let mut conn = self.l2.get_connection(); Cmd::new() .arg("SETEX") .arg(&full_key) .arg(ttl_secs) .arg(&json) .query_async::<()>(&mut conn) .await?; self.l1.insert_with_ttl(key.to_string(), json, ttl_duration); Ok(()) } pub async fn set_l2_only( &self, key: &str, value: &T, ttl: Option, ) -> AppResult<()> { let json = serde_json::to_string(value)?; let full_key = self.full_key(key); let ttl_duration = ttl.unwrap_or(self.default_ttl); let ttl_secs = ttl_duration.as_secs() as usize; let mut conn = self.l2.get_connection(); Cmd::new() .arg("SETEX") .arg(&full_key) .arg(ttl_secs) .arg(&json) .query_async::<()>(&mut conn) .await?; Ok(()) } pub async fn delete(&self, key: &str) -> AppResult<()> { self.l1.remove(&key.to_string()); let full_key = self.full_key(key); let mut conn = self.l2.get_connection(); Cmd::new() .arg("DEL") .arg(&full_key) .query_async::<()>(&mut conn) .await?; Ok(()) } pub async fn exists(&self, key: &str) -> bool { if self.l1.get(&key.to_string()).is_some() { return true; } let full_key = self.full_key(key); let mut conn = self.l2.get_connection(); Cmd::new() .arg("EXISTS") .arg(&full_key) .query_async::(&mut conn) .await .unwrap_or(false) } fn full_key(&self, key: &str) -> String { format!("{}{}", self.key_prefix, key) } }