use crate::error::CacheResult;
use crate::traits::CacheStore;
use serde::{Serialize, de::DeserializeOwned};
use std::sync::Arc;
use std::time::Duration;
pub struct CacheManager<S: CacheStore> {
store: Arc<S>,
}
impl<S: CacheStore> CacheManager<S> {
pub fn new(store: S) -> Self {
Self {
store: Arc::new(store),
}
}
pub async fn get<T: DeserializeOwned>(&self, key: &str) -> CacheResult<Option<T>> {
if let Some(json) = self.store.get_json(key).await? {
let value: T = serde_json::from_str(&json)
.map_err(|e| crate::error::CacheError::Deserialization(e.to_string()))?;
Ok(Some(value))
} else {
Ok(None)
}
}
pub async fn set<T: Serialize>(
&self,
key: &str,
value: &T,
ttl: Option<Duration>,
) -> CacheResult<()> {
let json = serde_json::to_string(value)
.map_err(|e| crate::error::CacheError::Serialization(e.to_string()))?;
self.store.set_json(key, json, ttl).await
}
pub async fn get_or_set<T, F, Fut>(
&self,
key: &str,
ttl: Option<Duration>,
factory: F,
) -> CacheResult<T>
where
T: Serialize + DeserializeOwned,
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = CacheResult<T>>,
{
if let Some(value) = self.get(key).await? {
return Ok(value);
}
let value = factory().await?;
self.set(key, &value, ttl).await?;
Ok(value)
}
pub async fn delete(&self, key: &str) -> CacheResult<()> {
self.store.delete(key).await
}
pub async fn exists(&self, key: &str) -> CacheResult<bool> {
self.store.exists(key).await
}
pub async fn clear(&self) -> CacheResult<()> {
self.store.clear().await
}
pub async fn ttl(&self, key: &str) -> CacheResult<Option<Duration>> {
self.store.ttl(key).await
}
pub async fn expire(&self, key: &str, ttl: Duration) -> CacheResult<()> {
self.store.expire(key, ttl).await
}
pub fn namespace(&self, prefix: &str) -> NamespacedCache<S> {
NamespacedCache {
store: self.store.clone(),
prefix: prefix.to_string(),
}
}
}
pub struct NamespacedCache<S: CacheStore> {
store: Arc<S>,
prefix: String,
}
impl<S: CacheStore> NamespacedCache<S> {
fn build_key(&self, key: &str) -> String {
format!("{}:{}", self.prefix, key)
}
pub async fn get<T: DeserializeOwned>(&self, key: &str) -> CacheResult<Option<T>> {
let key = self.build_key(key);
if let Some(json) = self.store.get_json(&key).await? {
let value: T = serde_json::from_str(&json)
.map_err(|e| crate::error::CacheError::Deserialization(e.to_string()))?;
Ok(Some(value))
} else {
Ok(None)
}
}
pub async fn set<T: Serialize>(
&self,
key: &str,
value: &T,
ttl: Option<Duration>,
) -> CacheResult<()> {
let key = self.build_key(key);
let json = serde_json::to_string(value)
.map_err(|e| crate::error::CacheError::Serialization(e.to_string()))?;
self.store.set_json(&key, json, ttl).await
}
pub async fn delete(&self, key: &str) -> CacheResult<()> {
let key = self.build_key(key);
self.store.delete(&key).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_namespace_build_key() {
struct MockStore;
#[async_trait::async_trait]
impl CacheStore for MockStore {
async fn get_json(&self, _key: &str) -> CacheResult<Option<String>> {
Ok(None)
}
async fn set_json(
&self,
_key: &str,
_value: String,
_ttl: Option<Duration>,
) -> CacheResult<()> {
Ok(())
}
async fn delete(&self, _key: &str) -> CacheResult<()> {
Ok(())
}
async fn exists(&self, _key: &str) -> CacheResult<bool> {
Ok(false)
}
async fn clear(&self) -> CacheResult<()> {
Ok(())
}
async fn ttl(&self, _key: &str) -> CacheResult<Option<Duration>> {
Ok(None)
}
async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
Ok(())
}
async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
Ok(0)
}
async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
Ok(0)
}
}
let namespaced = NamespacedCache {
store: Arc::new(MockStore),
prefix: "users".to_string(),
};
assert_eq!(namespaced.build_key("123"), "users:123");
}
#[test]
fn test_namespace_build_key_empty() {
struct MockStore;
#[async_trait::async_trait]
impl CacheStore for MockStore {
async fn get_json(&self, _key: &str) -> CacheResult<Option<String>> {
Ok(None)
}
async fn set_json(
&self,
_key: &str,
_value: String,
_ttl: Option<Duration>,
) -> CacheResult<()> {
Ok(())
}
async fn delete(&self, _key: &str) -> CacheResult<()> {
Ok(())
}
async fn exists(&self, _key: &str) -> CacheResult<bool> {
Ok(false)
}
async fn clear(&self) -> CacheResult<()> {
Ok(())
}
async fn ttl(&self, _key: &str) -> CacheResult<Option<Duration>> {
Ok(None)
}
async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
Ok(())
}
async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
Ok(0)
}
async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
Ok(0)
}
}
let namespaced = NamespacedCache {
store: Arc::new(MockStore),
prefix: "app".to_string(),
};
assert_eq!(namespaced.build_key(""), "app:");
}
#[test]
fn test_namespace_build_key_with_colons() {
struct MockStore;
#[async_trait::async_trait]
impl CacheStore for MockStore {
async fn get_json(&self, _key: &str) -> CacheResult<Option<String>> {
Ok(None)
}
async fn set_json(
&self,
_key: &str,
_value: String,
_ttl: Option<Duration>,
) -> CacheResult<()> {
Ok(())
}
async fn delete(&self, _key: &str) -> CacheResult<()> {
Ok(())
}
async fn exists(&self, _key: &str) -> CacheResult<bool> {
Ok(false)
}
async fn clear(&self) -> CacheResult<()> {
Ok(())
}
async fn ttl(&self, _key: &str) -> CacheResult<Option<Duration>> {
Ok(None)
}
async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
Ok(())
}
async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
Ok(0)
}
async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
Ok(0)
}
}
let namespaced = NamespacedCache {
store: Arc::new(MockStore),
prefix: "app".to_string(),
};
assert_eq!(namespaced.build_key("user:123"), "app:user:123");
}
#[test]
fn test_namespace_multiple_prefixes() {
struct MockStore;
#[async_trait::async_trait]
impl CacheStore for MockStore {
async fn get_json(&self, _key: &str) -> CacheResult<Option<String>> {
Ok(None)
}
async fn set_json(
&self,
_key: &str,
_value: String,
_ttl: Option<Duration>,
) -> CacheResult<()> {
Ok(())
}
async fn delete(&self, _key: &str) -> CacheResult<()> {
Ok(())
}
async fn exists(&self, _key: &str) -> CacheResult<bool> {
Ok(false)
}
async fn clear(&self) -> CacheResult<()> {
Ok(())
}
async fn ttl(&self, _key: &str) -> CacheResult<Option<Duration>> {
Ok(None)
}
async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
Ok(())
}
async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
Ok(0)
}
async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
Ok(0)
}
}
let ns1 = NamespacedCache {
store: Arc::new(MockStore),
prefix: "app1".to_string(),
};
let ns2 = NamespacedCache {
store: Arc::new(MockStore),
prefix: "app2".to_string(),
};
assert_eq!(ns1.build_key("key"), "app1:key");
assert_eq!(ns2.build_key("key"), "app2:key");
assert_ne!(ns1.build_key("key"), ns2.build_key("key"));
}
}