use std::hash::Hash;
use std::marker::PhantomData;
use cachet_tier::{CacheEntry, CacheTier, Error, SizeError};
use layered::Service;
use crate::{CacheOperation, CacheResponse, GetRequest, InsertRequest, InvalidateRequest};
#[derive(Debug, Clone)]
pub struct ServiceAdapter<K, V, S> {
service: S,
_phantom: PhantomData<(K, V)>,
}
impl<K, V, S> ServiceAdapter<K, V, S> {
#[must_use]
pub fn new(service: S) -> Self {
Self {
service,
_phantom: PhantomData,
}
}
#[must_use]
pub fn inner(&self) -> &S {
&self.service
}
}
impl<K, V, S> CacheTier<K, V> for ServiceAdapter<K, V, S>
where
K: Clone + Eq + Hash + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
S: Service<CacheOperation<K, V>, Out = Result<CacheResponse<V>, Error>> + Send + Sync,
{
async fn get(&self, key: &K) -> Result<Option<CacheEntry<V>>, Error> {
let request = CacheOperation::Get(GetRequest::new(key.clone()));
match self.service.execute(request).await? {
CacheResponse::Get(entry) => Ok(entry),
_ => Err(Error::from_message("unexpected response type for get")),
}
}
async fn insert(&self, key: K, entry: CacheEntry<V>) -> Result<(), Error> {
let request = CacheOperation::Insert(InsertRequest::new(key.clone(), entry));
match self.service.execute(request).await? {
CacheResponse::Insert => Ok(()),
_ => Err(Error::from_message("unexpected response type for insert")),
}
}
async fn invalidate(&self, key: &K) -> Result<(), Error> {
let request = CacheOperation::Invalidate(InvalidateRequest::new(key.clone()));
match self.service.execute(request).await? {
CacheResponse::Invalidate => Ok(()),
_ => Err(Error::from_message("unexpected response type for invalidate")),
}
}
async fn clear(&self) -> Result<(), Error> {
match self.service.execute(CacheOperation::Clear).await? {
CacheResponse::Clear => Ok(()),
_ => Err(Error::from_message("unexpected response type for clear")),
}
}
async fn len(&self) -> Result<u64, SizeError> {
Err(SizeError::unsupported())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone)]
struct MockService;
impl Service<CacheOperation<String, i32>> for MockService {
type Out = Result<CacheResponse<i32>, Error>;
async fn execute(&self, input: CacheOperation<String, i32>) -> Self::Out {
match input {
CacheOperation::Get(req) => {
if req.key == "existing" {
Ok(CacheResponse::Get(Some(CacheEntry::new(42))))
} else {
Ok(CacheResponse::Get(None))
}
}
CacheOperation::Insert(_) => Ok(CacheResponse::Insert),
CacheOperation::Invalidate(_) => Ok(CacheResponse::Invalidate),
CacheOperation::Clear => Ok(CacheResponse::Clear),
}
}
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_get_existing() {
let adapter = ServiceAdapter::new(MockService);
let result = adapter.get(&"existing".to_string()).await;
assert!(result.is_ok());
assert_eq!(*result.unwrap().unwrap().value(), 42);
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_get_missing() {
let adapter = ServiceAdapter::new(MockService);
let result = adapter.get(&"missing".to_string()).await;
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_insert_returns_ok() {
let adapter = ServiceAdapter::new(MockService);
let result = adapter.insert("key".to_string(), CacheEntry::new(100)).await;
assert!(result.is_ok(), "insert should succeed");
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_invalidate_returns_ok() {
let adapter = ServiceAdapter::new(MockService);
let result = adapter.invalidate(&"key".to_string()).await;
assert!(result.is_ok(), "invalidate should succeed");
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_clear_returns_ok() {
let adapter = ServiceAdapter::new(MockService);
let result = adapter.clear().await;
assert!(result.is_ok(), "clear should succeed");
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_len() {
use cachet_tier::SizeErrorKind;
let adapter = ServiceAdapter::<String, i32, _>::new(MockService);
let err = adapter.len().await.unwrap_err();
assert_eq!(err.kind, SizeErrorKind::Unsupported);
}
#[test]
fn adapter_inner_returns_reference() {
let adapter = ServiceAdapter::<String, i32, _>::new(MockService);
let _inner: &MockService = adapter.inner();
}
#[derive(Debug, Clone)]
struct WrongResponseService;
impl Service<CacheOperation<String, i32>> for WrongResponseService {
type Out = Result<CacheResponse<i32>, Error>;
async fn execute(&self, input: CacheOperation<String, i32>) -> Self::Out {
match input {
CacheOperation::Insert(_) => Ok(CacheResponse::Clear),
CacheOperation::Get(_) | CacheOperation::Invalidate(_) => Ok(CacheResponse::Insert),
CacheOperation::Clear => Ok(CacheResponse::Get(None)),
}
}
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_get_wrong_response_returns_error() {
let adapter = ServiceAdapter::new(WrongResponseService);
let result = adapter.get(&"key".to_string()).await;
result.unwrap_err();
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_insert_wrong_response_returns_error() {
let adapter = ServiceAdapter::new(WrongResponseService);
let result = adapter.insert("key".to_string(), CacheEntry::new(42)).await;
result.unwrap_err();
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_invalidate_wrong_response_returns_error() {
let adapter = ServiceAdapter::new(WrongResponseService);
let result = adapter.invalidate(&"key".to_string()).await;
result.unwrap_err();
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn adapter_clear_wrong_response_returns_error() {
let adapter = ServiceAdapter::new(WrongResponseService);
let result = adapter.clear().await;
result.unwrap_err();
}
}