use std::{sync::Arc, time::Duration}; use moka::future::{Cache, CacheBuilder}; use serde::{Serialize, de::DeserializeOwned}; use crate::{CacheError, CacheResult}; const DEFAULT_LOCAL_MAX_CAPACITY: u64 = 10_000; #[derive(Clone, Debug)] pub struct LocalCacheConfig { pub max_capacity: u64, pub time_to_live: Option, pub time_to_idle: Option, } impl Default for LocalCacheConfig { fn default() -> Self { Self { max_capacity: DEFAULT_LOCAL_MAX_CAPACITY, time_to_live: Some(Duration::from_secs(300)), time_to_idle: None, } } } #[derive(Clone)] pub struct MokaCache { pub inner: Cache, Arc<[u8]>>, } impl MokaCache { pub fn init() -> Self { Self::with_config(LocalCacheConfig::default()) } pub fn with_config(config: LocalCacheConfig) -> Self { let mut builder = CacheBuilder::new(config.max_capacity); if let Some(time_to_live) = config.time_to_live { builder = builder.time_to_live(time_to_live); } if let Some(time_to_idle) = config.time_to_idle { builder = builder.time_to_idle(time_to_idle); } Self { inner: builder.build(), } } pub async fn get(&self, key: &str) -> CacheResult> where T: DeserializeOwned, { match self.inner.get(key).await { Some(value) => serde_json::from_slice(value.as_ref()) .map(Some) .map_err(CacheError::Serialize), None => Ok(None), } } pub async fn get_json( &self, key: &str, ) -> CacheResult> { self.get(key).await } pub async fn set(&self, key: &str, value: &T) -> CacheResult<()> where T: Serialize + ?Sized, { let value = serde_json::to_vec(value).map_err(CacheError::Serialize)?; self.inner .insert(Arc::::from(key), Arc::<[u8]>::from(value)) .await; Ok(()) } pub async fn remove(&self, key: &str) { self.inner.remove(key).await; } pub async fn contains_key(&self, key: &str) -> bool { self.inner.contains_key(key) } pub async fn invalidate_all(&self) { self.inner.invalidate_all(); } pub fn invalidate_entries_if(&self, predicate: F) where F: Fn(&str) -> bool + Send + Sync + 'static, { let _ = self .inner .invalidate_entries_if(move |key, _| predicate(key)); } } #[cfg(test)] mod tests { use std::time::Duration; use serde::{Deserialize, Serialize}; use super::{LocalCacheConfig, MokaCache}; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] struct User { id: u64, name: String, } #[tokio::test] async fn stores_and_reads_typed_values() { let cache = MokaCache::init(); let user = User { id: 7, name: "alice".to_string(), }; cache.set("user:7", &user).await.unwrap(); assert_eq!(cache.get::("user:7").await.unwrap(), Some(user)); } #[tokio::test] async fn expires_values_by_ttl() { let cache = MokaCache::with_config(LocalCacheConfig { max_capacity: 16, time_to_live: Some(Duration::from_millis(25)), time_to_idle: None, }); cache.set("short", &"value").await.unwrap(); tokio::time::sleep(Duration::from_millis(60)).await; assert_eq!(cache.get::("short").await.unwrap(), None); } }