mytheclipse_cache/
cache_aside.rs1use std::time::Duration;
9
10use crate::traits::{Cache, CacheError};
11
12#[derive(Clone)]
18pub struct CacheAside<C, F> {
19 cache: C,
20 fetcher: F,
21 ttl: Option<Duration>,
22}
23
24impl<C, F, Fut> CacheAside<C, F>
25where
26 C: Cache,
27 F: Fn(String) -> Fut + Send + Sync,
28 Fut: std::future::Future<Output = Option<Vec<u8>>> + Send,
29{
30 pub fn new(cache: C, fetcher: F) -> Self {
33 Self {
34 cache,
35 fetcher,
36 ttl: None,
37 }
38 }
39
40 pub fn with_ttl(mut self, ttl: Duration) -> Self {
42 self.ttl = Some(ttl);
43 self
44 }
45
46 pub async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, CacheError> {
49 if let Some(value) = self.cache.get(key).await? {
50 return Ok(Some(value));
51 }
52 if let Some(value) = (self.fetcher)(key.to_string()).await {
53 self.cache.set(key, value.clone(), self.ttl).await?;
54 Ok(Some(value))
55 } else {
56 Ok(None)
57 }
58 }
59
60 pub async fn invalidate(&self, key: &str) -> Result<(), CacheError> {
62 self.cache.invalidate(key).await
63 }
64
65 pub fn cache(&self) -> &C {
67 &self.cache
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::memory::MemoryCache;
75 use std::sync::atomic::{AtomicU64, Ordering};
76 use std::sync::Arc;
77
78 fn fetcher(
79 hits: Arc<AtomicU64>,
80 ) -> impl Fn(String) -> std::future::Ready<Option<Vec<u8>>> + Send + Sync {
81 move |_key: String| {
82 let n = hits.fetch_add(1, Ordering::SeqCst) + 1;
83 std::future::ready(Some(format!("fetched-{n}").into_bytes()))
84 }
85 }
86
87 #[tokio::test]
88 async fn miss_reads_through_and_caches() {
89 let hits = Arc::new(AtomicU64::new(0));
90 let aside = CacheAside::new(MemoryCache::new(), fetcher(hits.clone()));
91
92 let first = aside.get("k").await.unwrap().unwrap();
93 let second = aside.get("k").await.unwrap().unwrap();
94 assert_eq!(first, b"fetched-1");
95 assert_eq!(second, b"fetched-1");
97 assert_eq!(hits.load(Ordering::SeqCst), 1);
98 }
99
100 #[tokio::test]
101 async fn invalidate_forces_refetch() {
102 let hits = Arc::new(AtomicU64::new(0));
103 let aside = CacheAside::new(MemoryCache::new(), fetcher(hits.clone()));
104 let _ = aside.get("k").await.unwrap();
105 aside.invalidate("k").await.unwrap();
106 let again = aside.get("k").await.unwrap().unwrap();
107 assert_eq!(again, b"fetched-2");
108 assert_eq!(hits.load(Ordering::SeqCst), 2);
109 }
110
111 #[tokio::test]
112 async fn ttl_applies_to_writes() {
113 let aside = CacheAside::new(MemoryCache::new(), fetcher(Arc::new(AtomicU64::new(0))))
114 .with_ttl(Duration::from_millis(30));
115 let _ = aside.get("k").await.unwrap();
116 assert_eq!(
117 aside.cache().get("k").await.unwrap(),
118 Some(b"fetched-1".to_vec())
119 );
120 tokio::time::sleep(Duration::from_millis(60)).await;
121 assert_eq!(aside.cache().get("k").await.unwrap(), None);
122 }
123}