pub mod driver;
pub mod drivers;
mod cache;
mod error;
#[cfg(feature = "axum")]
mod cache_layer;
pub use cache::{scope_cache, Cache};
pub use driver::{build_driver, CacheHandle, Driver};
pub use error::CacheError;
#[cfg(feature = "axum")]
pub use cache_layer::CacheLayer;
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::time::Duration;
use crate::driver::{CacheHandle, Driver};
use crate::drivers::MemoryDriver;
use crate::CacheError;
use crate::{scope_cache, Cache};
fn handle(prefix: &str) -> Arc<CacheHandle> {
Arc::new(CacheHandle::new(
Driver::Memory(MemoryDriver::new()),
prefix,
))
}
#[tokio::test]
async fn set_and_get_string() {
scope_cache(handle(""), async {
Cache::set("greeting", &"hello", None).await.unwrap();
let v: Option<String> = Cache::get("greeting").await.unwrap();
assert_eq!(v.as_deref(), Some("hello"));
})
.await;
}
#[tokio::test]
async fn get_missing_returns_none() {
scope_cache(handle(""), async {
let v: Option<i32> = Cache::get("nothing").await.unwrap();
assert_eq!(v, None);
})
.await;
}
#[tokio::test]
async fn set_and_get_struct() {
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct User {
name: String,
age: u32,
}
scope_cache(handle(""), async {
let u = User {
name: "Alice".into(),
age: 30,
};
Cache::set("user", &u, None).await.unwrap();
let got: Option<User> = Cache::get("user").await.unwrap();
assert_eq!(
got,
Some(User {
name: "Alice".into(),
age: 30
})
);
})
.await;
}
#[tokio::test]
async fn forget_removes_key() {
scope_cache(handle(""), async {
Cache::set("tmp", &99i32, None).await.unwrap();
Cache::forget("tmp").await.unwrap();
let v: Option<i32> = Cache::get("tmp").await.unwrap();
assert_eq!(v, None);
})
.await;
}
#[tokio::test]
async fn flush_clears_everything() {
scope_cache(handle(""), async {
Cache::set("a", &1i32, None).await.unwrap();
Cache::set("b", &2i32, None).await.unwrap();
Cache::flush().await.unwrap();
let a: Option<i32> = Cache::get("a").await.unwrap();
let b: Option<i32> = Cache::get("b").await.unwrap();
assert!(a.is_none() && b.is_none());
})
.await;
}
#[tokio::test]
async fn ttl_expiry_returns_none_after_expiry() {
scope_cache(handle(""), async {
Cache::set("exp", &"value", Some(Duration::from_millis(50)))
.await
.unwrap();
tokio::time::sleep(Duration::from_millis(100)).await;
let v: Option<String> = Cache::get("exp").await.unwrap();
assert_eq!(v, None, "expired key should return None");
})
.await;
}
#[tokio::test]
async fn no_expiry_persists() {
scope_cache(handle(""), async {
Cache::set("perm", &"stay", None).await.unwrap();
tokio::time::sleep(Duration::from_millis(20)).await;
let v: Option<String> = Cache::get("perm").await.unwrap();
assert_eq!(v.as_deref(), Some("stay"));
})
.await;
}
#[tokio::test]
async fn namespace_isolation() {
let h_a = handle("ns_a:");
let h_b = handle("ns_b:");
scope_cache(Arc::clone(&h_a), async {
Cache::set("key", &"from_a", None).await.unwrap();
})
.await;
scope_cache(Arc::clone(&h_b), async {
let v: Option<String> = Cache::get("key").await.unwrap();
assert_eq!(v, None, "namespace B should not see A's keys");
})
.await;
}
#[tokio::test]
async fn remember_calls_fetcher_once() {
use std::sync::atomic::{AtomicU32, Ordering};
let calls = Arc::new(AtomicU32::new(0));
let c = Arc::clone(&calls);
scope_cache(handle(""), async move {
for _ in 0..4 {
let _: String = Cache::remember("expensive", Duration::from_secs(60), || {
let c = Arc::clone(&c);
async move {
c.fetch_add(1, Ordering::SeqCst);
Ok::<_, String>("computed".to_string())
}
})
.await
.unwrap();
}
})
.await;
assert_eq!(calls.load(std::sync::atomic::Ordering::SeqCst), 1);
}
#[tokio::test]
async fn get_without_layer_returns_not_configured() {
let res: Result<Option<String>, CacheError> = Cache::get("k").await;
assert!(matches!(res, Err(CacheError::NotConfigured)));
}
}