pub mod backend;
pub mod config;
pub mod decorator;
pub mod manager;
pub mod memory;
#[cfg(feature = "cache-redis")]
pub mod redis;
pub mod stats;
use std::sync::{Arc, OnceLock, RwLock};
pub use backend::CacheBackend;
pub use config::{CacheBackendKind, CacheConfig, CacheConfigBuilder, CacheOptions};
pub use decorator::{cache_key_for, cached, cached_with, is_cached};
pub use manager::CacheManager;
pub use memory::MemoryCache;
#[cfg(feature = "cache-redis")]
pub use redis::RedisCache;
pub use stats::{CacheStats, CacheStatsSnapshot};
use crate::error::Result;
static MANAGER: OnceLock<RwLock<Option<CacheManager>>> = OnceLock::new();
fn slot() -> &'static RwLock<Option<CacheManager>> {
MANAGER.get_or_init(|| RwLock::new(None))
}
pub fn configure_cache(config: CacheConfig) -> Result<CacheManager> {
let manager = CacheManager::new(config)?;
let clone = manager.clone();
let mut guard = slot()
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*guard = Some(clone);
Ok(manager)
}
pub fn set_cache_manager(manager: CacheManager) {
let mut guard = slot()
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*guard = Some(manager);
}
pub fn get_cache_manager() -> Option<CacheManager> {
let guard = slot()
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.clone()
}
pub fn get_or_init_manager() -> CacheManager {
if let Some(m) = get_cache_manager() {
return m;
}
let m = CacheManager::new(CacheConfig::default()).expect("default cache manager");
set_cache_manager(m.clone());
m
}
pub async fn invalidate(
key: Option<&str>,
table: Option<&str>,
pattern: Option<&str>,
) -> Result<usize> {
let Some(manager) = get_cache_manager() else {
return Ok(0);
};
let mut total = 0usize;
if let Some(k) = key {
total += manager.invalidate_key(k).await?;
}
if let Some(t) = table {
total += manager.invalidate_table(t).await?;
}
if let Some(p) = pattern {
total += manager.invalidate_pattern(p).await?;
}
Ok(total)
}
pub async fn clear_cache() -> Result<usize> {
match get_cache_manager() {
Some(m) => m.clear().await,
None => Ok(0),
}
}
pub async fn close_cache() -> Result<()> {
let existing = {
let mut guard = slot()
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.take()
};
if let Some(m) = existing {
m.close().await?;
}
Ok(())
}
pub fn install_backend(config: CacheConfig, backend: Arc<dyn CacheBackend>) -> CacheManager {
let manager = CacheManager::with_backend(config, backend);
set_cache_manager(manager.clone());
manager
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::sync::Mutex;
static GLOBAL_LOCK: Mutex<()> = Mutex::const_new(());
#[tokio::test]
async fn configure_and_get() {
let _guard = GLOBAL_LOCK.lock().await;
let cfg = CacheConfig::builder().key_prefix("cgt:").build();
let m = configure_cache(cfg).unwrap();
let from_slot = get_cache_manager().unwrap();
assert_eq!(m.config().key_prefix, from_slot.config().key_prefix);
}
#[tokio::test]
async fn invalidate_by_pattern_via_global() {
let _guard = GLOBAL_LOCK.lock().await;
let cfg = CacheConfig::builder().key_prefix("inv:").build();
let m = configure_cache(cfg).unwrap();
m.clear().await.unwrap();
m.set("user:1", &1u32, None, &[]).await.unwrap();
m.set("user:2", &2u32, None, &[]).await.unwrap();
m.set("prod:1", &3u32, None, &[]).await.unwrap();
let n = invalidate(None, None, Some("user:*")).await.unwrap();
assert_eq!(n, 2);
}
#[tokio::test]
async fn close_cache_drops_manager() {
let _guard = GLOBAL_LOCK.lock().await;
let cfg = CacheConfig::builder().key_prefix("cls:").build();
let _ = configure_cache(cfg).unwrap();
assert!(get_cache_manager().is_some());
close_cache().await.unwrap();
assert!(get_cache_manager().is_none());
}
}