use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use oxcache::backend::CacheBackend;
use crate::domain::DbCacheProvider;
use crate::foundation::DbError;
#[derive(Clone)]
pub struct OxcacheDbCacheAdapter {
cache: Arc<dyn CacheBackend + Send + Sync>,
}
impl OxcacheDbCacheAdapter {
#[must_use]
pub fn new(cache: Arc<dyn CacheBackend + Send + Sync>) -> Self {
Self { cache }
}
}
impl DbCacheProvider for OxcacheDbCacheAdapter {
fn get<'a>(&'a self, key: &'a str) -> Pin<Box<dyn Future<Output = Result<Option<Vec<u8>>, DbError>> + Send + 'a>> {
Box::pin(async move {
self.cache
.get(key)
.await
.map_err(|e| DbError::Config(format!("cache get failed: {e}")))
})
}
fn set<'a>(
&'a self,
key: &'a str,
value: Vec<u8>,
ttl: Option<std::time::Duration>,
) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
self.cache
.set(key, value, ttl)
.await
.map_err(|e| DbError::Config(format!("cache set failed: {e}")))
})
}
fn delete<'a>(&'a self, key: &'a str) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
self.cache
.delete(key)
.await
.map_err(|e| DbError::Config(format!("cache delete failed: {e}")))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use oxcache::backend::MokaMemoryBackend;
fn make_adapter() -> OxcacheDbCacheAdapter {
let backend = MokaMemoryBackend::builder().capacity(100).build();
let cache: Arc<dyn CacheBackend + Send + Sync> = Arc::new(backend);
OxcacheDbCacheAdapter::new(cache)
}
#[test]
fn adapter_is_db_cache_provider() {
fn assert_impl<T: DbCacheProvider + ?Sized>() {}
assert_impl::<OxcacheDbCacheAdapter>();
let adapter = make_adapter();
let _dyn: Arc<dyn DbCacheProvider + Send + Sync> = Arc::new(adapter);
}
#[tokio::test]
async fn adapter_set_get_round_trip() {
let adapter = make_adapter();
adapter
.set("key1", b"value1".to_vec(), None)
.await
.expect("set should succeed");
let got = adapter.get("key1").await.expect("get should succeed");
assert_eq!(got, Some(b"value1".to_vec()));
}
#[tokio::test]
async fn adapter_get_absent_returns_none() {
let adapter = make_adapter();
let got = adapter.get("nonexistent").await.expect("get should succeed");
assert!(got.is_none(), "absent key should return Ok(None)");
}
#[tokio::test]
async fn adapter_delete_removes_value() {
let adapter = make_adapter();
adapter.set("doomed", b"x".to_vec(), None).await.expect("set");
adapter.delete("doomed").await.expect("delete");
let gone = adapter.get("doomed").await.expect("get after delete");
assert!(gone.is_none());
}
#[tokio::test]
async fn adapter_set_with_ttl() {
let adapter = make_adapter();
adapter
.set("ttl_key", b"ttl_val".to_vec(), Some(Duration::from_secs(60)))
.await
.expect("set with ttl");
let got = adapter.get("ttl_key").await.expect("get");
assert_eq!(got, Some(b"ttl_val".to_vec()));
}
#[test]
fn adapter_error_mapping_uses_db_error_config() {
let err = DbError::Config("cache get failed: simulated".to_string());
let msg = err.message();
assert!(
msg.starts_with("cache get failed:"),
"error message should start with cache prefix, got: {msg}"
);
}
#[tokio::test]
async fn adapter_dyn_dispatch() {
let adapter: Arc<dyn DbCacheProvider + Send + Sync> = Arc::new(make_adapter());
adapter
.set("dyn_key", b"dyn_val".to_vec(), None)
.await
.expect("set via dyn");
let got = adapter.get("dyn_key").await.expect("get via dyn");
assert_eq!(got, Some(b"dyn_val".to_vec()));
adapter.delete("dyn_key").await.expect("delete via dyn");
let gone = adapter.get("dyn_key").await.expect("get after delete via dyn");
assert!(gone.is_none());
}
}