use std::sync::Arc; use std::time::Duration; use cache::redis::RedisCache; use uuid::Uuid; use crate::error::{RepoError, RepoResult}; pub const IMMUTABLE_TTL: Duration = Duration::from_secs(300); pub const MUTABLE_TTL: Duration = Duration::from_secs(60); pub const NEG_TTL: Duration = Duration::from_secs(10); const KEY_PREFIX: &str = "repo.cache"; #[derive(Clone)] pub struct RepoCache { inner: Arc, } impl RepoCache { pub fn new(inner: Arc) -> Self { Self { inner } } pub fn raw(&self) -> &Arc { &self.inner } fn scoped(&self, repo_id: Uuid, kind: &str, key: &str) -> String { format!("{KEY_PREFIX}:{{{{{repo_id}}}}}:{kind}:{key}") } pub async fn get_string( &self, repo_id: Uuid, kind: &str, key: &str, ) -> RepoResult> { let k = self.scoped(repo_id, kind, key); let bytes = self .inner .get_bytes(&k) .await .map_err(|e| RepoError::Internal(format!("cache get: {e}")))?; Ok(bytes.map(|b| String::from_utf8_lossy(&b).to_string())) } pub async fn set_string( &self, repo_id: Uuid, kind: &str, key: &str, value: &str, ttl: Duration, ) -> RepoResult<()> { let k = self.scoped(repo_id, kind, key); self.inner .set_bytes(&k, value.as_bytes().to_vec(), ttl.as_secs()) .await .map_err(|e| RepoError::Internal(format!("cache set: {e}"))) } pub async fn delete(&self, repo_id: Uuid, kind: &str, key: &str) -> RepoResult<()> { let k = self.scoped(repo_id, kind, key); self.inner .delete(&k) .await .map_err(|e| RepoError::Internal(format!("cache del: {e}"))) } pub async fn invalidate_repo(&self, repo_id: Uuid) -> RepoResult<()> { let _ = repo_id; Ok(()) } } pub trait ObjectCache { type Value; async fn fetch( &self, repo_id: Uuid, kind: &str, key: &str, ttl: Duration, loader: F, ) -> RepoResult where F: FnOnce() -> Fut, Fut: std::future::Future>; } impl ObjectCache for RepoCache { type Value = String; async fn fetch( &self, repo_id: Uuid, kind: &str, key: &str, ttl: Duration, loader: F, ) -> RepoResult where F: FnOnce() -> Fut, Fut: std::future::Future>, { if let Some(v) = self.get_string(repo_id, kind, key).await? { return Ok(v); } let fresh = loader().await?; self.set_string(repo_id, kind, key, &fresh, ttl).await?; Ok(fresh) } }