diff --git a/cachememory/Cargo.toml b/cachememory/Cargo.toml new file mode 100644 index 0000000..f7ed6c6 --- /dev/null +++ b/cachememory/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "cachememory" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { version = "0.4" } +futures = { version = "0.3" } +serde_derive = { version = "1" } +serde = { version = "1" } + +#[dev-dependencies] +tokio = { version = "1", features = ["full"] } + diff --git a/cachememory/src/lib.rs b/cachememory/src/lib.rs new file mode 100644 index 0000000..2861608 --- /dev/null +++ b/cachememory/src/lib.rs @@ -0,0 +1,100 @@ +use chrono::{DateTime, Utc}; +use futures::Future; +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; + +#[derive(Clone)] +pub struct CacheRecord { + pub timeout: DateTime, + pub value: T, +} + +pub struct Cache(Arc>>>); + +impl Cache { + pub fn new() -> Cache { + Cache(Arc::new(RwLock::new(HashMap::new()))) + } + + pub async fn find(&self, key: &str, f: impl Future, 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); + } +}