Something went wrong. Try again.
A fork of @slices.network/slices forked from slices.network/slices
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336//! Cache module providing flexible caching with Redis and in-memory backends.//!//! This module provides a unified caching interface with automatic fallback//! from Redis to in-memory caching when Redis is unavailable.
mod in_memory;mod redis;
use anyhow::Result;use async_trait::async_trait;use serde::{Deserialize, Serialize};use std::collections::HashSet;use tracing::{info, warn};
pub use in_memory::InMemoryCache;pub use redis::RedisCache;
/// Generic cache trait implemented by all cache backends.////// Provides async methods for get, set, delete, and bulk operations.#[async_trait]pub trait Cache: Send + Sync { /// Get a value from cache, returning None if not found or expired. async fn get<T>(&mut self, key: &str) -> Result<Option<T>> where T: for<'de> Deserialize<'de> + Send;
/// Set a value in cache with optional TTL in seconds. async fn set<T>(&mut self, key: &str, value: &T, ttl_seconds: Option<u64>) -> Result<()> where T: Serialize + Send + Sync;
/// Delete a key from cache. async fn delete(&mut self, key: &str) -> Result<()>;
/// Set multiple key-value pairs efficiently (uses pipelining for Redis). async fn set_multiple<T>(&mut self, items: Vec<(&str, &T, Option<u64>)>) -> Result<()> where T: Serialize + Send + Sync;
/// Test cache connection/health. async fn ping(&mut self) -> Result<bool>;
/// Get cache info/statistics for monitoring. async fn get_info(&mut self) -> Result<String>;}
/// Cache backend implementation enum to avoid trait object overhead.pub enum CacheBackendImpl { InMemory(InMemoryCache), Redis(RedisCache),}
impl CacheBackendImpl { pub async fn get<T>(&mut self, key: &str) -> Result<Option<T>> where T: for<'de> Deserialize<'de> + Send, { match self { CacheBackendImpl::InMemory(cache) => cache.get(key).await, CacheBackendImpl::Redis(cache) => cache.get(key).await, } }
pub async fn set<T>(&mut self, key: &str, value: &T, ttl_seconds: Option<u64>) -> Result<()> where T: Serialize + Send + Sync, { match self { CacheBackendImpl::InMemory(cache) => cache.set(key, value, ttl_seconds).await, CacheBackendImpl::Redis(cache) => cache.set(key, value, ttl_seconds).await, } }
pub async fn delete(&mut self, key: &str) -> Result<()> { match self { CacheBackendImpl::InMemory(cache) => cache.delete(key).await, CacheBackendImpl::Redis(cache) => cache.delete(key).await, } }
pub async fn set_multiple<T>(&mut self, items: Vec<(&str, &T, Option<u64>)>) -> Result<()> where T: Serialize + Send + Sync, { match self { CacheBackendImpl::InMemory(cache) => cache.set_multiple(items).await, CacheBackendImpl::Redis(cache) => cache.set_multiple(items).await, } }
pub async fn ping(&mut self) -> Result<bool> { match self { CacheBackendImpl::InMemory(cache) => cache.ping().await, CacheBackendImpl::Redis(cache) => cache.ping().await, } }
pub async fn get_info(&mut self) -> Result<String> { match self { CacheBackendImpl::InMemory(cache) => cache.get_info().await, CacheBackendImpl::Redis(cache) => cache.get_info().await, } }}
/// Domain-specific cache wrapper with convenience methods for Slices operations.////// Provides typed methods for actors, lexicons, domains, collections, auth, and DID resolution.pub struct SliceCache { cache: CacheBackendImpl,}
impl SliceCache { pub fn new(cache: CacheBackendImpl) -> Self { Self { cache } }
/// Actor cache methods (permanent cache - no TTL) pub async fn is_actor(&mut self, did: &str, slice_uri: &str) -> Result<Option<bool>> { let key = format!("actor:{}:{}", did, slice_uri); self.cache.get::<bool>(&key).await }
pub async fn cache_actor_exists(&mut self, did: &str, slice_uri: &str) -> Result<()> { let key = format!("actor:{}:{}", did, slice_uri); self.cache.set(&key, &true, None).await }
pub async fn remove_actor(&mut self, did: &str, slice_uri: &str) -> Result<()> { let key = format!("actor:{}:{}", did, slice_uri); self.cache.delete(&key).await }
pub async fn preload_actors(&mut self, actors: Vec<(String, String)>) -> Result<()> { if actors.is_empty() { return Ok(()); }
let items: Vec<(String, bool, Option<u64>)> = actors .into_iter() .map(|(did, slice_uri)| (format!("actor:{}:{}", did, slice_uri), true, None)) .collect();
let items_ref: Vec<(&str, &bool, Option<u64>)> = items .iter() .map(|(key, value, ttl)| (key.as_str(), value, *ttl)) .collect();
self.cache.set_multiple(items_ref).await }
/// Lexicon cache methods (2 hour TTL) pub async fn cache_lexicons( &mut self, slice_uri: &str, lexicons: &Vec<serde_json::Value>, ) -> Result<()> { let key = format!("lexicons:{}", slice_uri); self.cache.set(&key, lexicons, Some(7200)).await }
pub async fn get_lexicons( &mut self, slice_uri: &str, ) -> Result<Option<Vec<serde_json::Value>>> { let key = format!("lexicons:{}", slice_uri); self.cache.get::<Vec<serde_json::Value>>(&key).await }
/// Domain cache methods (4 hour TTL) pub async fn cache_slice_domain(&mut self, slice_uri: &str, domain: &str) -> Result<()> { let key = format!("domain:{}", slice_uri); self.cache.set(&key, &domain.to_string(), Some(14400)).await }
pub async fn get_slice_domain(&mut self, slice_uri: &str) -> Result<Option<String>> { let key = format!("domain:{}", slice_uri); self.cache.get::<String>(&key).await }
/// Collections cache methods (2 hour TTL) pub async fn cache_slice_collections( &mut self, slice_uri: &str, collections: &HashSet<String>, ) -> Result<()> { let key = format!("collections:{}", slice_uri); self.cache.set(&key, collections, Some(7200)).await }
pub async fn get_slice_collections( &mut self, slice_uri: &str, ) -> Result<Option<HashSet<String>>> { let key = format!("collections:{}", slice_uri); self.cache.get::<HashSet<String>>(&key).await }
/// Auth cache methods (5 minute TTL) pub async fn get_cached_oauth_userinfo( &mut self, token: &str, ) -> Result<Option<serde_json::Value>> { let key = format!("oauth_userinfo:{}", token); self.cache.get(&key).await }
pub async fn cache_oauth_userinfo( &mut self, token: &str, userinfo: &serde_json::Value, ttl_seconds: u64, ) -> Result<()> { let key = format!("oauth_userinfo:{}", token); self.cache.set(&key, userinfo, Some(ttl_seconds)).await }
pub async fn get_cached_atproto_session( &mut self, token: &str, ) -> Result<Option<serde_json::Value>> { let key = format!("atproto_session:{}", token); self.cache.get(&key).await }
pub async fn cache_atproto_session( &mut self, token: &str, session: &serde_json::Value, ttl_seconds: u64, ) -> Result<()> { let key = format!("atproto_session:{}", token); self.cache.set(&key, session, Some(ttl_seconds)).await }
/// DID resolution cache methods (24 hour TTL) pub async fn get_cached_did_resolution( &mut self, did: &str, ) -> Result<Option<serde_json::Value>> { let key = format!("did_resolution:{}", did); self.cache.get(&key).await }
pub async fn cache_did_resolution( &mut self, did: &str, actor_data: &serde_json::Value, ) -> Result<()> { let key = format!("did_resolution:{}", did); self.cache.set(&key, actor_data, Some(86400)).await }
pub async fn invalidate_did_resolution(&mut self, did: &str) -> Result<()> { let key = format!("did_resolution:{}", did); self.cache.delete(&key).await }
/// Generic get/set for custom caching needs pub async fn get<T>(&mut self, key: &str) -> Result<Option<T>> where T: for<'de> Deserialize<'de> + Send, { self.cache.get(key).await }
pub async fn set<T>(&mut self, key: &str, value: &T, ttl_seconds: Option<u64>) -> Result<()> where T: Serialize + Send + Sync, { self.cache.set(key, value, ttl_seconds).await }
/// Utility methods pub async fn ping(&mut self) -> Result<bool> { self.cache.ping().await }
pub async fn get_info(&mut self) -> Result<String> { self.cache.get_info().await }}
/// Cache backend configuration enum.#[derive(Debug, Clone)]pub enum CacheBackend { InMemory { ttl_seconds: Option<u64>, }, Redis { url: String, ttl_seconds: Option<u64>, },}
/// Factory for creating cache instances with automatic Redis fallback.pub struct CacheFactory;
impl CacheFactory { /// Create a cache backend, falling back to in-memory if Redis fails. pub async fn create_cache(backend: CacheBackend) -> Result<CacheBackendImpl> { match backend { CacheBackend::InMemory { ttl_seconds } => { let ttl_display = ttl_seconds .map(|t| format!("{}s", t)) .unwrap_or_else(|| "default".to_string()); info!("Creating in-memory cache with TTL: {}", ttl_display); Ok(CacheBackendImpl::InMemory(InMemoryCache::new(ttl_seconds))) } CacheBackend::Redis { url, ttl_seconds } => { info!("Attempting to create Redis cache at: {}", url); match RedisCache::new(&url, ttl_seconds).await { Ok(redis_cache) => { info!("Created Redis cache successfully"); Ok(CacheBackendImpl::Redis(redis_cache)) } Err(e) => { warn!( error = ?e, "Failed to create Redis cache, falling back to in-memory" ); Ok(CacheBackendImpl::InMemory(InMemoryCache::new(ttl_seconds))) } } } } }
/// Create a SliceCache with the specified backend. pub async fn create_slice_cache(backend: CacheBackend) -> Result<SliceCache> { let cache = Self::create_cache(backend).await?; Ok(SliceCache::new(cache)) }}