use std::error::Error; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::Mutex; pub trait Fetcher { fn fetch(&self) -> impl Future>>; } #[derive(Debug)] struct ExpiringValue { value: T, expires: Instant, } impl ExpiringValue { fn get(&self, now: Instant) -> Option { if now <= self.expires { log::trace!("returning val (fresh for {:?})", self.expires - now); Some(self.value.clone()) } else { log::trace!("hiding expired val"); None } } } // TODO: generic over the fetcher's actual error type #[derive(Clone)] pub struct CachedValue> { latest: Arc>>>, fetcher: F, validitiy: Duration, } impl> CachedValue { pub fn new(f: F, validitiy: Duration) -> Self { Self { latest: Default::default(), fetcher: f, validitiy, } } pub async fn get(&self) -> Result> { let now = Instant::now(); return self.get_impl(now).await; } async fn get_impl(&self, now: Instant) -> Result> { let mut val = self.latest.lock().await; if let Some(v) = val.as_ref().and_then(|v| v.get(now)) { return Ok(v); } log::debug!( "value {}, fetching...", if val.is_some() { "expired" } else { "not present" } ); let new = self .fetcher .fetch() .await .inspect_err(|e| log::warn!("value fetch failed, next access will retry: {e}"))?; log::debug!("fetched ok, saving a copy for cache."); *val = Some(ExpiringValue { value: new.clone(), expires: now + self.validitiy, }); Ok(new) } }