pub use moka;
use std::{future::Future, hash::Hash, ops::Deref, sync::OnceLock, time::Duration};
pub struct ServerCache<K, V> {
ttl: Duration,
capacity: u64,
cache: OnceLock<moka::future::Cache<K, V>>,
}
impl<K, V> ServerCache<K, V>
where
K: Hash + Eq + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
pub const fn new(ttl: Duration, capacity: u64) -> Self {
Self {
ttl,
capacity,
cache: OnceLock::new(),
}
}
pub async fn get_or_fetch<E, Fut>(&self, key: K, fetch: Fut) -> Result<V, E>
where
Fut: Future<Output = Result<V, E>>,
E: Clone + Send + Sync + 'static,
{
self.try_get_with(key, fetch)
.await
.map_err(|err| E::clone(&err))
}
pub async fn get_or_insert<Fut>(&self, key: K, fetch: Fut) -> V
where
Fut: Future<Output = V>,
{
self.get_with(key, fetch).await
}
}
impl<K, V> Deref for ServerCache<K, V>
where
K: Hash + Eq + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
type Target = moka::future::Cache<K, V>;
fn deref(&self) -> &Self::Target {
self.cache.get_or_init(|| {
moka::future::Cache::builder()
.max_capacity(self.capacity)
.time_to_live(self.ttl)
.build()
})
}
}
#[doc(hidden)]
pub mod __private {
use serde::Serialize;
pub type SharedCache<T> = super::ServerCache<String, T>;
pub fn args_key<A: Serialize>(args: &A) -> String {
serde_json::to_string(args).unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::ServerCache;
use std::{
sync::atomic::{AtomicU32, Ordering},
time::Duration,
};
#[tokio::test]
async fn answers_are_shared_and_errors_are_not_kept() {
static CACHE: ServerCache<u32, u32> = ServerCache::new(Duration::from_secs(60), 100);
static CALLS: AtomicU32 = AtomicU32::new(0);
let fetch = || async {
CALLS.fetch_add(1, Ordering::SeqCst);
Ok::<_, String>(7)
};
assert_eq!(CACHE.get_or_fetch(1, fetch()).await, Ok(7));
assert_eq!(CACHE.get_or_fetch(1, fetch()).await, Ok(7));
assert_eq!(CALLS.load(Ordering::SeqCst), 1);
let failing = async { Err::<u32, _>("down".to_string()) };
assert_eq!(
CACHE.get_or_fetch(2, failing).await,
Err("down".to_string())
);
assert_eq!(CACHE.get_or_fetch(2, fetch()).await, Ok(7));
}
#[tokio::test]
async fn infallible_fetches_and_moka_calls_share_the_cache() {
static CACHE: ServerCache<&str, u32> = ServerCache::new(Duration::from_secs(60), 100);
assert_eq!(CACHE.get_or_insert("a", async { 1 }).await, 1);
assert_eq!(CACHE.get("a").await, Some(1));
CACHE.invalidate("a").await;
assert_eq!(CACHE.get_or_insert("a", async { 2 }).await, 2);
}
}