use crate::cache::CacheService;
use crate::error::StorageError;
use async_trait::async_trait;
use dashmap::DashMap;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Clone)]
struct CacheEntry {
value: String,
expiry: Option<u64>, }
impl CacheEntry {
fn new(value: String, ttl_seconds: Option<u64>) -> Self {
let expiry = ttl_seconds.map(|ttl| {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
now + ttl
});
CacheEntry { value, expiry }
}
fn is_expired(&self) -> bool {
if let Some(expiry) = self.expiry {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
now >= expiry
} else {
false
}
}
fn value(&self) -> Option<&str> {
if self.is_expired() {
None
} else {
Some(&self.value)
}
}
}
pub struct MemoryCache {
storage: DashMap<String, CacheEntry>,
default_ttl: Option<u64>,
}
impl MemoryCache {
pub fn new(default_ttl: Option<u64>) -> Self {
Self {
storage: DashMap::new(),
default_ttl,
}
}
pub fn with_ttl(ttl_seconds: u64) -> Self {
Self::new(Some(ttl_seconds))
}
pub fn no_expiry() -> Self {
Self::new(None)
}
fn get_raw(&self, key: &str) -> Option<String> {
if let Some(entry) = self.storage.get(key) {
if !entry.is_expired() {
Some(entry.value.clone())
} else {
self.storage.remove(key);
None
}
} else {
None
}
}
}
#[async_trait]
impl CacheService for MemoryCache {
async fn get(&self, key: &str) -> Result<Option<String>, StorageError> {
Ok(self.get_raw(key))
}
async fn set(&self, key: &str, value: &str, ttl: Option<u64>) -> Result<(), StorageError> {
let effective_ttl = ttl.or(self.default_ttl);
let entry = CacheEntry::new(value.to_string(), effective_ttl);
self.storage.insert(key.to_string(), entry);
Ok(())
}
async fn delete(&self, key: &str) -> Result<(), StorageError> {
self.storage.remove(key);
Ok(())
}
async fn set_with_ttl(&self, key: &str, value: &str, ttl: Duration) -> Result<(), StorageError> {
let ttl_seconds = Some(ttl.as_secs());
let entry = CacheEntry::new(value.to_string(), ttl_seconds);
self.storage.insert(key.to_string(), entry);
Ok(())
}
}
impl Default for MemoryCache {
fn default() -> Self {
Self::new(Some(300)) }
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::time::{sleep, Duration};
#[tokio::test]
async fn test_memory_cache_basic_operations() {
let cache = MemoryCache::default();
cache.set("key1", "value1", None).await.unwrap();
let result = cache.get("key1").await.unwrap();
assert_eq!(result, Some("value1".to_string()));
cache.delete("key1").await.unwrap();
let result = cache.get("key1").await.unwrap();
assert_eq!(result, None);
}
#[tokio::test]
async fn test_memory_cache_with_ttl() {
let cache = MemoryCache::new(Some(1));
cache.set("expiring_key", "expiring_value", None).await.unwrap();
let result = cache.get("expiring_key").await.unwrap();
assert_eq!(result, Some("expiring_value".to_string()));
sleep(Duration::from_secs(2)).await;
let result = cache.get("expiring_key").await.unwrap();
assert_eq!(result, None);
}
#[tokio::test]
async fn test_memory_cache_set_with_specific_ttl() {
let cache = MemoryCache::default();
cache
.set_with_ttl("specific_key", "specific_value", Duration::from_millis(500))
.await
.unwrap();
let result = cache.get("specific_key").await.unwrap();
assert_eq!(result, Some("specific_value".to_string()));
sleep(Duration::from_secs(1)).await;
let result = cache.get("specific_key").await.unwrap();
assert_eq!(result, None);
}
#[tokio::test]
async fn test_memory_cache_no_expiry() {
let cache = MemoryCache::no_expiry();
cache.set("permanent_key", "permanent_value", None).await.unwrap();
let result = cache.get("permanent_key").await.unwrap();
assert_eq!(result, Some("permanent_value".to_string()));
tokio::time::sleep(Duration::from_millis(100)).await;
let result = cache.get("permanent_key").await.unwrap();
assert_eq!(result, Some("permanent_value".to_string()));
}
}