use std::future::Future;
use std::pin::Pin;
use std::time::Duration;
use crate::foundation::error::DbError;
pub trait DbCacheProvider: Send + Sync {
#[allow(
clippy::type_complexity,
reason = "Pin<Box<dyn Future + Send>> is the canonical dyn-compatible async trait dispatch type"
)]
fn get<'a>(&'a self, key: &'a str) -> Pin<Box<dyn Future<Output = Result<Option<Vec<u8>>, DbError>> + Send + 'a>>;
#[allow(
clippy::type_complexity,
reason = "Pin<Box<dyn Future + Send>> is the canonical dyn-compatible async trait dispatch type"
)]
fn set<'a>(
&'a self,
key: &'a str,
value: Vec<u8>,
ttl: Option<Duration>,
) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>>;
#[allow(
clippy::type_complexity,
reason = "Pin<Box<dyn Future + Send>> is the canonical dyn-compatible async trait dispatch type"
)]
fn delete<'a>(&'a self, key: &'a str) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>>;
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
struct MockCacheProvider {
inner: Mutex<HashMap<String, Vec<u8>>>,
}
impl MockCacheProvider {
fn new() -> Self {
Self {
inner: Mutex::new(HashMap::new()),
}
}
}
impl DbCacheProvider for MockCacheProvider {
fn get<'a>(
&'a self,
key: &'a str,
) -> Pin<Box<dyn Future<Output = Result<Option<Vec<u8>>, DbError>> + Send + 'a>> {
Box::pin(async move {
let map = self.inner.lock().expect("mock cache lock poisoned");
Ok(map.get(key).cloned())
})
}
fn set<'a>(
&'a self,
key: &'a str,
value: Vec<u8>,
_ttl: Option<Duration>,
) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
let mut map = self.inner.lock().expect("mock cache lock poisoned");
map.insert(key.to_string(), value);
Ok(())
})
}
fn delete<'a>(&'a self, key: &'a str) -> Pin<Box<dyn Future<Output = Result<(), DbError>> + Send + 'a>> {
Box::pin(async move {
let mut map = self.inner.lock().expect("mock cache lock poisoned");
map.remove(key);
Ok(())
})
}
}
#[test]
fn db_cache_provider_is_object_safe() {
fn assert_dyn_compatible(_: Arc<dyn DbCacheProvider + Send + Sync>) {}
let mock = Arc::new(MockCacheProvider::new());
assert_dyn_compatible(mock);
}
#[tokio::test]
async fn db_cache_provider_get_returns_pin_box_future() {
let cache = MockCacheProvider::new();
cache.set("k", b"v".to_vec(), None).await.expect("set");
let got = cache.get("k").await.expect("get should succeed");
assert_eq!(got, Some(b"v".to_vec()));
}
#[tokio::test]
async fn db_cache_provider_get_absent_key_returns_none() {
let cache = MockCacheProvider::new();
let got = cache.get("missing").await.expect("get should succeed");
assert!(got.is_none(), "absent key must return Ok(None)");
}
#[tokio::test]
async fn db_cache_provider_set_stores_value() {
let cache = MockCacheProvider::new();
cache
.set("key1", vec![1, 2, 3], Some(Duration::from_secs(60)))
.await
.expect("set");
let got = cache.get("key1").await.expect("get");
assert_eq!(got, Some(vec![1, 2, 3]));
}
#[tokio::test]
async fn db_cache_provider_delete_removes_value() {
let cache = MockCacheProvider::new();
cache.set("doomed", b"x".to_vec(), None).await.expect("set");
cache.delete("doomed").await.expect("delete");
let got = cache.get("doomed").await.expect("get after delete");
assert!(got.is_none(), "deleted key must return Ok(None)");
cache.delete("doomed").await.expect("idempotent delete");
}
#[tokio::test]
async fn db_cache_provider_set_overwrites() {
let cache = MockCacheProvider::new();
cache.set("k", b"old".to_vec(), None).await.expect("set old");
cache.set("k", b"new".to_vec(), None).await.expect("set new");
let got = cache.get("k").await.expect("get");
assert_eq!(got, Some(b"new".to_vec()));
}
#[tokio::test]
async fn db_cache_provider_dyn_dispatch_works() {
let cache: Arc<dyn DbCacheProvider + Send + Sync> = Arc::new(MockCacheProvider::new());
cache.set("dyn", b"works".to_vec(), None).await.expect("set");
let got = cache.get("dyn").await.expect("get");
assert_eq!(got, Some(b"works".to_vec()));
cache.delete("dyn").await.expect("delete");
let gone = cache.get("dyn").await.expect("get after delete");
assert!(gone.is_none());
}
}