use super::{Cache, CacheGetOrInsert};
use std::hash::Hash;
#[async_trait::async_trait]
impl<K, V, C> CacheGetOrInsert<K, V> for C
where
K: Hash + Eq + Send + Sync + std::fmt::Debug + Clone + 'static,
V: Clone + Send + Sync + 'static,
C: Cache<K, V>,
{
async fn get_or_insert(
&self,
key: K,
value: V,
size: usize,
) -> Result<V, Box<dyn std::error::Error + Send + Sync>> {
if let Some(v) = self.get(&key).await {
return Ok(v);
}
self.put(key.clone(), value, size).await?;
Ok(self.get(&key).await.ok_or("Value not found after insert")?)
}
}
#[cfg(test)]
mod tests {
use crate::cache::{Cache, CacheGetOrInsert, CacheStats};
use std::collections::HashMap;
use std::sync::Mutex;
struct MockCache {
entries: Mutex<HashMap<String, (String, usize)>>,
}
impl MockCache {
fn new() -> Self {
Self {
entries: Mutex::new(HashMap::new()),
}
}
}
#[async_trait::async_trait]
impl Cache<String, String> for MockCache {
async fn get(&self, key: &String) -> Option<String> {
self.entries
.lock()
.unwrap()
.get(key)
.map(|(v, _)| v.clone())
}
async fn put(&self, key: String, value: String, size: usize) -> Result<(), String> {
self.entries.lock().unwrap().insert(key, (value, size));
Ok(())
}
async fn remove(&self, key: &String) -> bool {
self.entries.lock().unwrap().remove(key).is_some()
}
async fn clear(&self) {
self.entries.lock().unwrap().clear();
}
async fn len(&self) -> usize {
self.entries.lock().unwrap().len()
}
async fn is_empty(&self) -> bool {
self.entries.lock().unwrap().is_empty()
}
async fn stats(&self) -> CacheStats {
CacheStats::default()
}
async fn warm_up(&self, entries: HashMap<String, (String, usize)>) -> Result<(), String> {
self.entries.lock().unwrap().extend(entries);
Ok(())
}
}
#[tokio::test]
async fn test_get_or_insert_inserts_when_missing() {
let cache = MockCache::new();
let result = cache
.get_or_insert("k1".to_string(), "v1".to_string(), 2)
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "v1");
assert_eq!(cache.get(&"k1".to_string()).await, Some("v1".to_string()));
}
#[tokio::test]
async fn test_get_or_insert_returns_existing_when_present() {
let cache = MockCache::new();
cache
.put("k1".to_string(), "existing".to_string(), 8)
.await
.unwrap();
let result = cache
.get_or_insert("k1".to_string(), "new_value".to_string(), 9)
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "existing");
}
#[tokio::test]
async fn test_get_or_insert_multiple_distinct_keys() {
let cache = MockCache::new();
for i in 0..3 {
let key = format!("k{}", i);
let val = format!("v{}", i);
let r = cache.get_or_insert(key.clone(), val.clone(), i + 1).await;
assert!(r.is_ok());
assert_eq!(r.unwrap(), val);
}
assert_eq!(cache.len().await, 3);
}
#[tokio::test]
async fn test_get_or_insert_returns_inserted_size() {
let cache = MockCache::new();
cache
.get_or_insert("k".to_string(), "v".to_string(), 42)
.await
.unwrap();
let snapshot = cache.entries.lock().unwrap().clone();
assert_eq!(snapshot.get("k").unwrap().1, 42);
}
}