use crate::error::Result;
use crate::value::ConfigValue;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use std::time::Duration;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CacheKey {
pub key: String,
pub namespace: Option<String>,
}
impl CacheKey {
pub fn new(key: impl Into<String>) -> Self {
Self {
key: key.into(),
namespace: None,
}
}
}
#[derive(Clone, Debug)]
pub struct CacheEntry {
pub value: ConfigValue,
pub expires_at: DateTime<Utc>,
}
impl CacheEntry {
#[must_use]
pub fn with_ttl(value: ConfigValue, ttl: Duration) -> Self {
let expires_at = Utc::now()
+ chrono::Duration::from_std(ttl).unwrap_or_else(|_| chrono::Duration::seconds(0));
Self { value, expires_at }
}
#[must_use]
pub fn is_fresh(&self) -> bool {
self.expires_at > Utc::now()
}
}
#[async_trait]
pub trait Cache: Send + Sync {
async fn get(&self, key: &CacheKey) -> Result<Option<ConfigValue>>;
async fn put(&self, key: &CacheKey, value: ConfigValue, ttl: Duration) -> Result<()>;
async fn invalidate(&self, key: &CacheKey) -> Result<()>;
async fn clear(&self) -> Result<()>;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NoopCache;
#[async_trait]
impl Cache for NoopCache {
async fn get(&self, _key: &CacheKey) -> Result<Option<ConfigValue>> {
Ok(None)
}
async fn put(&self, _key: &CacheKey, _value: ConfigValue, _ttl: Duration) -> Result<()> {
Ok(())
}
async fn invalidate(&self, _key: &CacheKey) -> Result<()> {
Ok(())
}
async fn clear(&self) -> Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cache_entry_freshness() {
let fresh = CacheEntry::with_ttl(ConfigValue::string("x"), Duration::from_secs(60));
assert!(fresh.is_fresh());
let expired = CacheEntry {
value: ConfigValue::string("x"),
expires_at: Utc::now() - chrono::Duration::seconds(1),
};
assert!(!expired.is_fresh());
}
}