use std::collections::HashMap;
use std::sync::Mutex;
use cachet_service::{CacheOperation, CacheResponse, GetRequest, InsertRequest, InvalidateRequest, ServiceAdapter};
use cachet_tier::{CacheEntry, CacheTier, Error};
use layered::Service;
#[derive(Debug)]
struct InMemoryCacheService<K, V> {
data: Mutex<HashMap<K, CacheEntry<V>>>,
}
impl<K, V> InMemoryCacheService<K, V> {
fn new() -> Self {
Self {
data: Mutex::new(HashMap::new()),
}
}
}
impl<K, V> Service<CacheOperation<K, V>> for InMemoryCacheService<K, V>
where
K: Clone + Eq + std::hash::Hash + Send + Sync,
V: Clone + Send + Sync,
{
type Out = Result<CacheResponse<V>, Error>;
async fn execute(&self, input: CacheOperation<K, V>) -> Self::Out {
match input {
CacheOperation::Get(req) => {
let data = self.data.lock().expect("lock poisoned");
Ok(CacheResponse::Get(data.get(&req.key).cloned()))
}
CacheOperation::Insert(req) => {
let mut data = self.data.lock().expect("lock poisoned");
data.insert(req.key, req.entry);
Ok(CacheResponse::Insert)
}
CacheOperation::Invalidate(req) => {
let mut data = self.data.lock().expect("lock poisoned");
data.remove(&req.key);
Ok(CacheResponse::Invalidate)
}
CacheOperation::Clear => {
let mut data = self.data.lock().expect("lock poisoned");
data.clear();
Ok(CacheResponse::Clear)
}
}
}
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_integrates_with_cache_tier_trait() {
let service = InMemoryCacheService::<String, i32>::new();
let adapter = ServiceAdapter::new(service);
assert!(adapter.get(&"key".to_string()).await.unwrap().is_none());
adapter.insert("key".to_string(), CacheEntry::new(42)).await.unwrap();
let result = adapter.get(&"key".to_string()).await;
assert!(result.is_ok());
assert_eq!(*result.unwrap().unwrap().value(), 42);
adapter.invalidate(&"key".to_string()).await.unwrap();
assert!(adapter.get(&"key".to_string()).await.unwrap().is_none());
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_operations_return_ok() {
let service = InMemoryCacheService::<String, i32>::new();
let adapter = ServiceAdapter::new(service);
let result = adapter.get(&"key".to_string()).await;
assert!(result.is_ok());
assert!(result.unwrap().is_none());
adapter.insert("key".to_string(), CacheEntry::new(42)).await.unwrap();
let result = adapter.get(&"key".to_string()).await.unwrap();
assert!(result.is_some());
adapter.invalidate(&"key".to_string()).await.unwrap();
adapter.clear().await.unwrap();
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_clear_removes_all_entries() {
let service = InMemoryCacheService::<String, i32>::new();
let adapter = ServiceAdapter::new(service);
adapter.insert("key1".to_string(), CacheEntry::new(1)).await.unwrap();
adapter.insert("key2".to_string(), CacheEntry::new(2)).await.unwrap();
adapter.insert("key3".to_string(), CacheEntry::new(3)).await.unwrap();
adapter.clear().await.unwrap();
assert!(adapter.get(&"key1".to_string()).await.unwrap().is_none());
assert!(adapter.get(&"key2".to_string()).await.unwrap().is_none());
assert!(adapter.get(&"key3".to_string()).await.unwrap().is_none());
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_len_returns_unsupported() {
use cachet_tier::SizeErrorKind;
let service = InMemoryCacheService::<String, i32>::new();
let adapter = ServiceAdapter::new(service);
let err = adapter.len().await.unwrap_err();
assert_eq!(err.kind, SizeErrorKind::Unsupported);
}
#[test]
fn get_request_holds_key() {
let req = GetRequest::new("test-key".to_string());
assert_eq!(req.key, "test-key");
}
#[test]
fn insert_request_holds_key_and_entry() {
let entry = CacheEntry::new(42);
let req = InsertRequest::new("test-key".to_string(), entry);
assert_eq!(req.key, "test-key");
assert_eq!(*req.entry.value(), 42);
}
#[test]
fn invalidate_request_holds_key() {
let req = InvalidateRequest::new("test-key".to_string());
assert_eq!(req.key, "test-key");
}
#[test]
fn cache_response_is_hit_for_some() {
let response = CacheResponse::Get(Some(CacheEntry::new(42)));
assert!(response.is_hit());
assert!(!response.is_miss());
}
#[test]
fn cache_response_is_miss_for_none() {
let response: CacheResponse<i32> = CacheResponse::Get(None);
assert!(response.is_miss());
assert!(!response.is_hit());
}
#[test]
fn cache_response_into_entry_extracts_value() {
let response = CacheResponse::Get(Some(CacheEntry::new(42)));
let entry = response.into_entry();
assert!(entry.is_some());
assert_eq!(*entry.unwrap().value(), 42);
}
#[test]
fn cache_response_into_entry_returns_none_for_non_get() {
let response: CacheResponse<i32> = CacheResponse::Insert;
assert!(response.into_entry().is_none());
let response: CacheResponse<i32> = CacheResponse::Invalidate;
assert!(response.into_entry().is_none());
let response: CacheResponse<i32> = CacheResponse::Clear;
assert!(response.into_entry().is_none());
}