use async_trait::async_trait;
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;
use std::time::Duration;
pub trait CacheKey: Clone + Debug + Send + Sync {
fn to_string(&self) -> String;
}
impl CacheKey for String {
fn to_string(&self) -> String {
self.clone()
}
}
#[derive(Clone, Debug)]
pub struct CompoundKey {
pub primary: String,
pub secondary: Option<String>,
pub namespace: Option<String>,
}
impl CacheKey for CompoundKey {
fn to_string(&self) -> String {
let mut parts = vec![self.primary.clone()];
if let Some(ref secondary) = self.secondary {
parts.push(secondary.clone());
}
if let Some(ref namespace) = self.namespace {
parts.insert(0, namespace.clone());
}
parts.join(":")
}
}
#[derive(Debug, Clone, Default)]
pub struct CacheStats {
pub hits: u64,
pub misses: u64,
pub size: usize,
pub bytes_used: Option<usize>,
}
impl CacheStats {
pub fn hit_ratio(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
self.hits as f64 / total as f64
}
}
}
#[async_trait]
pub trait CacheProvider: Send + Sync {
async fn get<T>(&self, key: &impl CacheKey) -> Option<T>
where
T: DeserializeOwned + Send;
async fn set<T>(&self, key: &impl CacheKey, value: &T, ttl: Option<Duration>) -> bool
where
T: Serialize + Send + Sync;
async fn remove(&self, key: &impl CacheKey) -> bool;
async fn clear(&self) -> bool;
async fn exists(&self, key: &impl CacheKey) -> bool;
async fn stats(&self) -> CacheStats;
fn set_enabled(&mut self, enabled: bool);
fn is_enabled(&self) -> bool;
}
#[derive(Debug, Clone)]
pub struct MockCacheProvider {
pub should_succeed: bool,
pub enabled: bool,
pub hit_ratio: f64,
pub mock_stats: CacheStats,
}
impl Default for MockCacheProvider {
fn default() -> Self {
Self {
should_succeed: true,
enabled: true,
hit_ratio: 0.8, mock_stats: CacheStats {
hits: 80,
misses: 20,
size: 100,
bytes_used: Some(1024 * 10), },
}
}
}
#[async_trait]
impl CacheProvider for MockCacheProvider {
async fn get<T>(&self, _key: &impl CacheKey) -> Option<T>
where
T: DeserializeOwned + Send,
{
if !self.enabled || !self.should_succeed {
return None;
}
let random_hit = rand::random::<f64>() < self.hit_ratio;
if random_hit {
None
} else {
None
}
}
async fn set<T>(&self, _key: &impl CacheKey, _value: &T, _ttl: Option<Duration>) -> bool
where
T: Serialize + Send + Sync,
{
self.enabled && self.should_succeed
}
async fn remove(&self, _key: &impl CacheKey) -> bool {
self.enabled && self.should_succeed
}
async fn clear(&self) -> bool {
self.enabled && self.should_succeed
}
async fn exists(&self, _key: &impl CacheKey) -> bool {
self.enabled && self.should_succeed
}
async fn stats(&self) -> CacheStats {
self.mock_stats.clone()
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn is_enabled(&self) -> bool {
self.enabled
}
}
#[derive(Debug, Clone)]
pub struct NoOpCacheProvider;
#[async_trait]
impl CacheProvider for NoOpCacheProvider {
async fn get<T>(&self, _key: &impl CacheKey) -> Option<T>
where
T: DeserializeOwned + Send,
{
None
}
async fn set<T>(&self, _key: &impl CacheKey, _value: &T, _ttl: Option<Duration>) -> bool
where
T: Serialize + Send + Sync,
{
true }
async fn remove(&self, _key: &impl CacheKey) -> bool {
true
}
async fn clear(&self) -> bool {
true
}
async fn exists(&self, _key: &impl CacheKey) -> bool {
false }
async fn stats(&self) -> CacheStats {
CacheStats::default()
}
fn set_enabled(&mut self, _enabled: bool) {
}
fn is_enabled(&self) -> bool {
false }
}