use std::any::TypeId;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use crate::kit::{AsyncAutoBuilder, AsyncKit, ModuleMeta};
use super::capability::CacheStore;
#[derive(Debug, thiserror::Error)]
pub enum CacheError {
#[error("cache config error: {0}")]
Config(String),
#[error("cache build failed: {0}")]
BuildFailed(String),
}
#[derive(Debug, Clone)]
pub struct CacheConfig {
pub capacity: u64,
}
impl Default for CacheConfig {
fn default() -> Self {
Self { capacity: 10_000 }
}
}
impl CacheConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_capacity(mut self, capacity: u64) -> Self {
self.capacity = capacity;
self
}
}
pub struct CacheModule;
impl ModuleMeta for CacheModule {
const NAME: &'static str = "cache";
fn dependencies() -> &'static [(&'static str, TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for CacheModule {
type Capability = Arc<dyn CacheStore>;
type Error = CacheError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move {
let config = kit
.config::<CacheConfig>()
.map_err(|e| CacheError::Config(e.to_string()))?;
Self::build_cap(&config).await
})
}
}
impl CacheModule {
pub(crate) async fn build_cap(config: &CacheConfig) -> Result<Arc<dyn CacheStore>, CacheError> {
let cache = oxcache::Cache::builder()
.capacity(config.capacity)
.sync_mode(true)
.build()
.await
.map_err(|e| CacheError::BuildFailed(e.to_string()))?;
Ok(Arc::new(OxcacheStore::new(cache)))
}
}
struct OxcacheStore {
inner: oxcache::Cache<String, Vec<u8>>,
}
impl OxcacheStore {
fn new(cache: oxcache::Cache<String, Vec<u8>>) -> Self {
Self { inner: cache }
}
}
impl CacheStore for OxcacheStore {
fn get(&self, key: &str) -> Option<Vec<u8>> {
match self.inner.get_bytes_sync(key) {
Ok(val) => val,
Err(e) => {
tracing::warn!(key = %key, error = %e, "cache get failed");
None
}
}
}
fn set(&self, key: &str, val: Vec<u8>) {
if let Err(e) = self.inner.set_bytes_sync(key, val, None) {
tracing::warn!(key = %key, error = %e, "cache set failed");
}
}
fn invalidate_all(&self) {
if let Err(e) = self.inner.clear_sync() {
tracing::warn!(error = %e, "cache invalidate_all failed");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kit::{AsyncKit, CacheModule};
async fn build_cache() -> Arc<dyn CacheStore> {
CacheModule::build_cap(&CacheConfig::default())
.await
.expect("CacheModule::build_cap")
}
#[test]
fn cache_config_default_capacity() {
let config = CacheConfig::default();
assert_eq!(config.capacity, 10_000);
}
#[test]
fn cache_config_with_capacity_overrides_default() {
let config = CacheConfig::new().with_capacity(5_000);
assert_eq!(config.capacity, 5_000);
}
#[tokio::test(flavor = "multi_thread")]
async fn build_returns_send_sync_capability() {
let cap = build_cache().await;
fn _assert_send_sync<T: Send + Sync>(_: &T) {}
_assert_send_sync(&cap);
}
#[tokio::test(flavor = "multi_thread")]
async fn capability_get_miss_returns_none() {
let cap = build_cache().await;
assert!(cap.get("nonexistent").is_none());
}
#[tokio::test(flavor = "multi_thread")]
async fn capability_set_then_get_returns_value() {
let cap = build_cache().await;
cap.set("key1", b"value1".to_vec());
assert_eq!(cap.get("key1"), Some(b"value1".to_vec()));
}
#[tokio::test(flavor = "multi_thread")]
async fn capability_set_overwrites_existing() {
let cap = build_cache().await;
cap.set("k", b"v1".to_vec());
cap.set("k", b"v2".to_vec());
assert_eq!(cap.get("k"), Some(b"v2".to_vec()));
}
#[tokio::test(flavor = "multi_thread")]
async fn capability_invalidate_all_clears_entries() {
let cap = build_cache().await;
cap.set("k1", b"v1".to_vec());
cap.set("k2", b"v2".to_vec());
cap.invalidate_all();
assert!(cap.get("k1").is_none());
assert!(cap.get("k2").is_none());
}
#[tokio::test(flavor = "multi_thread")]
async fn capability_set_empty_value() {
let cap = build_cache().await;
let empty: Vec<u8> = vec![];
cap.set("empty", empty.clone());
assert_eq!(cap.get("empty"), Some(empty));
}
#[tokio::test(flavor = "multi_thread")]
async fn capability_set_large_value() {
let cap = build_cache().await;
let large = vec![0xAB; 1024 * 100]; cap.set("large", large.clone());
assert_eq!(cap.get("large"), Some(large));
}
#[tokio::test(flavor = "multi_thread")]
async fn kit_registration_flow() {
let mut kit = AsyncKit::new();
kit.set_config(CacheConfig::default());
kit.register::<CacheModule>()
.expect("register::<CacheModule>");
let kit = kit.build().await.expect("build");
assert!(kit.contains::<CacheModule>(), "CacheModule missing");
let cache = kit
.require::<CacheModule>()
.expect("require::<CacheModule>");
cache.set("k", b"v".to_vec());
assert_eq!(cache.get("k"), Some(b"v".to_vec()));
}
}