use std::future::Future;
use serde::Serialize;
use sha2::{Digest, Sha256};
use crate::error::Result;
use super::manager::CacheManager;
use super::{get_cache_manager, get_or_init_manager};
pub async fn cached<T, F, Fut>(key: &str, ttl_secs: Option<u64>, fetch: F) -> Result<T>
where
T: Serialize + for<'de> serde::Deserialize<'de>,
F: FnOnce() -> Fut,
Fut: Future<Output = Result<T>>,
{
let Some(manager) = get_cache_manager() else {
return fetch().await;
};
manager.get_or_set(key, ttl_secs, &[], fetch).await
}
pub async fn cached_with<T, F, Fut>(
manager: &CacheManager,
key: &str,
ttl_secs: Option<u64>,
fetch: F,
) -> Result<T>
where
T: Serialize + for<'de> serde::Deserialize<'de>,
F: FnOnce() -> Fut,
Fut: Future<Output = Result<T>>,
{
manager.get_or_set(key, ttl_secs, &[], fetch).await
}
pub fn cache_key_for<T: Serialize + ?Sized>(module: &str, name: &str, args: &T) -> Result<String> {
let args_json = serde_json::to_string(args)?;
let mut hasher = Sha256::new();
hasher.update(module.as_bytes());
hasher.update(b".");
hasher.update(name.as_bytes());
hasher.update(b"(");
hasher.update(args_json.as_bytes());
hasher.update(b")");
let digest = hasher.finalize();
let hex: String = digest.iter().take(8).fold(String::new(), |mut acc, byte| {
use std::fmt::Write;
let _ = write!(acc, "{byte:02x}");
acc
});
Ok(format!("{module}.{name}:{hex}"))
}
pub async fn is_cached(key: &str) -> Result<bool> {
match get_cache_manager() {
Some(m) => m.exists(key).await,
None => Ok(false),
}
}
#[allow(dead_code)]
pub(crate) fn ensure_default_manager() -> CacheManager {
get_or_init_manager()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cache_key_for_is_stable() {
let k1 = cache_key_for("mod", "fun", &("a", 1)).unwrap();
let k2 = cache_key_for("mod", "fun", &("a", 1)).unwrap();
assert_eq!(k1, k2);
}
#[test]
fn cache_key_for_differs_on_args() {
let k1 = cache_key_for("mod", "fun", &("a", 1)).unwrap();
let k2 = cache_key_for("mod", "fun", &("a", 2)).unwrap();
assert_ne!(k1, k2);
}
#[test]
fn cache_key_for_format() {
let k = cache_key_for("mymod", "myfn", &serde_json::json!({})).unwrap();
assert!(k.starts_with("mymod.myfn:"));
let hash = k.split(':').nth(1).unwrap();
assert_eq!(hash.len(), 16);
}
}