use chrono::{DateTime, Utc}; use futures::Future; use std::{ collections::HashMap, sync::{Arc, RwLock}, }; #[derive(Clone)] pub struct CacheRecord<T> { pub timeout: DateTime<Utc>, pub value: T, } pub struct Cache<T>(Arc<RwLock<HashMap<String, CacheRecord<T>>>>); impl<T: Clone> Cache<T> { pub fn new() -> Cache<T> { Cache(Arc::new(RwLock::new(HashMap::new()))) } pub async fn find(&self, key: &str, f: impl Future<Output = (DateTime<Utc>, T)>) -> T { let val = { let cache = self.0.read().unwrap(); cache.get(key).cloned() }; match val { Some(ref val) if val.timeout > Utc::now() => val.value.clone(), _ => { let response = f.await; let mut cache = self.0.write().unwrap(); let record = CacheRecord { timeout: response.0, value: response.1.clone(), }; cache .entry(key.to_owned()) .and_modify(|rec| *rec = record.clone()) .or_insert(record); response.1 } } } } #[cfg(test)] mod tests { use super::*; use chrono::Duration; use std::sync::{Arc, RwLock}; #[derive(Clone, Debug, PartialEq)] struct Value(i64); #[tokio::test] async fn it_calls_function_when_element_does_not_exist() { let cache = Cache::new(); let value = cache .find("my_key", async { (Utc::now(), Value(15)) }) .await; assert_eq!(value, Value(15)); } #[tokio::test] async fn it_calls_function_when_element_is_old() { let calls = Arc::new(RwLock::new(false)); let cache = Cache::new(); let _ = cache .find("my_key", async { (Utc::now() - Duration::seconds(10), Value(15)) }) .await; let value = cache .find("my_key", async { *calls.write().unwrap() = true; (Utc::now(), Value(16)) }) .await; assert_eq!(value, Value(16)); assert_eq!(*calls.read().unwrap(), true); } #[tokio::test] async fn it_does_not_call_function_when_element_is_not_old() { let calls = Arc::new(RwLock::new(false)); let cache = Cache::new(); let _ = cache .find("my_key", async { (Utc::now() + Duration::seconds(10), Value(15)) }) .await; let value = cache .find("my_key", async { *calls.write().unwrap() = true; (Utc::now(), Value(16)) }) .await; assert_eq!(value, Value(15)); assert_eq!(*calls.read().unwrap(), false); } }