use std::sync::Arc;
use trait_kit::prelude::*;
use crate::backend::CacheBackend;
use crate::integrations::kit::OxcacheModule;
pub type CacheBackendDecorator =
Arc<dyn Fn(Arc<dyn CacheBackend + Send + Sync>) -> Arc<dyn CacheBackend + Send + Sync> + Send + Sync>;
pub fn register_cache_decorator(
kit: &AsyncKit,
decorator: impl Fn(Arc<dyn CacheBackend + Send + Sync>) -> Arc<dyn CacheBackend + Send + Sync> + Send + Sync + 'static,
) {
kit.decorate::<OxcacheModule>(decorator);
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::backend::{BackendKind, CacheConnector, CacheReader, CacheWriter};
struct DecoratorTestBackend;
#[async_trait::async_trait]
impl CacheReader for DecoratorTestBackend {
async fn get(&self, _key: &str) -> crate::error::OxCacheResult<Option<Vec<u8>>> {
Ok(Some(b"original".to_vec()))
}
async fn exists(&self, _key: &str) -> crate::error::OxCacheResult<bool> {
Ok(true)
}
async fn ttl(&self, _key: &str) -> crate::error::OxCacheResult<Option<std::time::Duration>> {
Ok(None)
}
async fn len(&self) -> crate::error::OxCacheResult<u64> {
Ok(0)
}
async fn capacity(&self) -> crate::error::OxCacheResult<u64> {
Ok(0)
}
async fn stats(&self) -> crate::error::OxCacheResult<HashMap<String, String>> {
Ok(HashMap::new())
}
}
#[async_trait::async_trait]
impl CacheWriter for DecoratorTestBackend {
async fn set(
&self,
_key: Arc<str>,
_value: Arc<Vec<u8>>,
_ttl: Option<std::time::Duration>,
) -> crate::error::OxCacheResult<()> {
Ok(())
}
async fn delete(&self, _key: &str) -> crate::error::OxCacheResult<()> {
Ok(())
}
async fn clear(&self) -> crate::error::OxCacheResult<()> {
Ok(())
}
async fn expire(&self, _key: &str, _ttl: std::time::Duration) -> crate::error::OxCacheResult<bool> {
Ok(false)
}
}
#[async_trait::async_trait]
impl CacheConnector for DecoratorTestBackend {
async fn health_check(&self) -> crate::error::OxCacheResult<()> {
Ok(())
}
async fn shutdown(&self) {}
fn backend_kind(&self) -> BackendKind {
BackendKind::Mock
}
}
struct CountingDecorator {
inner: Arc<dyn CacheBackend + Send + Sync>,
count: Arc<AtomicUsize>,
}
#[async_trait::async_trait]
impl CacheReader for CountingDecorator {
async fn get(&self, key: &str) -> crate::error::OxCacheResult<Option<Vec<u8>>> {
self.count.fetch_add(1, Ordering::SeqCst);
self.inner.get(key).await
}
async fn exists(&self, key: &str) -> crate::error::OxCacheResult<bool> {
self.count.fetch_add(1, Ordering::SeqCst);
self.inner.exists(key).await
}
async fn ttl(&self, key: &str) -> crate::error::OxCacheResult<Option<std::time::Duration>> {
self.inner.ttl(key).await
}
async fn len(&self) -> crate::error::OxCacheResult<u64> {
self.inner.len().await
}
async fn capacity(&self) -> crate::error::OxCacheResult<u64> {
self.inner.capacity().await
}
async fn stats(&self) -> crate::error::OxCacheResult<HashMap<String, String>> {
self.inner.stats().await
}
}
#[async_trait::async_trait]
impl CacheWriter for CountingDecorator {
async fn set(
&self,
key: Arc<str>,
value: Arc<Vec<u8>>,
ttl: Option<std::time::Duration>,
) -> crate::error::OxCacheResult<()> {
self.count.fetch_add(1, Ordering::SeqCst);
self.inner.set(key, value, ttl).await
}
async fn delete(&self, key: &str) -> crate::error::OxCacheResult<()> {
self.count.fetch_add(1, Ordering::SeqCst);
self.inner.delete(key).await
}
async fn clear(&self) -> crate::error::OxCacheResult<()> {
self.count.fetch_add(1, Ordering::SeqCst);
self.inner.clear().await
}
async fn expire(&self, key: &str, ttl: std::time::Duration) -> crate::error::OxCacheResult<bool> {
self.inner.expire(key, ttl).await
}
}
#[async_trait::async_trait]
impl CacheConnector for CountingDecorator {
async fn health_check(&self) -> crate::error::OxCacheResult<()> {
self.inner.health_check().await
}
async fn shutdown(&self) {
self.inner.shutdown().await;
}
fn backend_kind(&self) -> BackendKind {
self.inner.backend_kind()
}
}
#[tokio::test]
async fn decorator_is_applied_on_build() {
let count = Arc::new(AtomicUsize::new(0));
let count_clone = Arc::clone(&count);
let decorator = move |backend: Arc<dyn CacheBackend + Send + Sync>| -> Arc<dyn CacheBackend + Send + Sync> {
Arc::new(CountingDecorator {
inner: backend,
count: Arc::clone(&count_clone),
})
};
let original: Arc<dyn CacheBackend + Send + Sync> = Arc::new(DecoratorTestBackend);
let decorated = decorator(original);
assert_eq!(count.load(Ordering::SeqCst), 0);
let _ = decorated.get("key").await;
assert_eq!(count.load(Ordering::SeqCst), 1);
let _ = decorated.get("key").await;
assert_eq!(count.load(Ordering::SeqCst), 2);
}
#[tokio::test]
async fn register_cache_decorator_does_not_panic() {
let mut kit = AsyncKit::new();
kit.set_config(crate::integrations::kit::OxcacheConfig::default());
kit.register::<OxcacheModule>().expect("register OxcacheModule");
let count = Arc::new(AtomicUsize::new(0));
let count_clone = Arc::clone(&count);
register_cache_decorator(&kit, move |backend| {
Arc::new(CountingDecorator {
inner: backend,
count: Arc::clone(&count_clone),
})
});
let built = kit.build().await.expect("kit build");
let backend = built.require::<OxcacheModule>().expect("require module");
let _ = backend.get("test").await;
}
#[tokio::test]
async fn decorator_is_applied_through_kit_build() {
use std::any::TypeId;
use std::future::Future;
use std::pin::Pin;
#[derive(Debug, Clone)]
struct TestCap {
val: String,
}
struct TestModule;
impl trait_kit::core::ModuleMeta for TestModule {
const NAME: &'static str = "test-dec";
fn dependencies() -> &'static [(&'static str, TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for TestModule {
type Capability = Arc<TestCap>;
type Error = crate::error::OxCacheError;
fn build<'a>(
_kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Arc<TestCap>, crate::error::OxCacheError>> + Send + 'a>>
{
Box::pin(async { Ok(Arc::new(TestCap { val: "base".into() })) })
}
}
let mut kit = AsyncKit::new();
kit.register::<TestModule>().expect("register TestModule");
kit.decorate::<TestModule>(|cap: Arc<TestCap>| {
Arc::new(TestCap {
val: format!("{}+wrapped", cap.val),
})
});
let built = kit.build().await.expect("kit build");
let cap = built.require::<TestModule>().expect("require TestModule");
assert_eq!(cap.val, "base+wrapped", "decorator should modify the capability");
}
}