#![forbid(unsafe_code)]
#![warn(missing_docs)]
pub mod cache;
pub mod error;
pub mod options;
pub mod metadata;
pub mod validation;
pub use cache::Cache;
#[cfg(feature = "moka")]
pub use cache::MokaCache;
#[cfg(feature = "redis")]
pub use cache::RedisCache;
pub use error::{CachifiedError, Result};
pub use options::{CachifiedOptions, CachifiedOptionsBuilder};
pub use metadata::{CacheMetadata, CacheEntry};
pub use validation::CheckValue;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::future::Future;
pub async fn cachified<T, F, Fut, C>(options: CachifiedOptions<T, F, C>) -> Result<T>
where
T: Clone + Send + Sync + 'static,
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = Result<T>> + Send + 'static,
C: Cache<T> + Clone + 'static,
{
let CachifiedOptions {
cache,
key,
ttl,
stale_while_revalidate,
force_fresh,
fallback_to_cache,
check_value,
get_fresh_value,
} = options;
let now = current_time();
if !force_fresh {
if let Some(entry) = cache.get(&key).await {
if !is_expired(&entry.metadata, now) {
if let Some(ref validator) = check_value {
if validator.check(&entry.value).is_ok() {
return Ok(entry.value);
}
} else {
return Ok(entry.value);
}
} else if let Some(swr_duration) = stale_while_revalidate {
let stale_until = entry.metadata.created_time +
entry.metadata.ttl.unwrap_or(Duration::ZERO) + swr_duration;
if now < stale_until {
let cache_clone = cache.clone();
let key_clone = key.clone();
let fresh_value_future = get_fresh_value();
tokio::spawn(async move {
if let Ok(fresh_value) = fresh_value_future.await {
let metadata = CacheMetadata {
created_time: current_time(),
ttl,
};
let entry = CacheEntry {
value: fresh_value,
metadata,
};
let _ = cache_clone.set(&key_clone, entry).await;
}
});
if let Some(ref validator) = check_value {
if validator.check(&entry.value).is_ok() {
return Ok(entry.value);
}
} else {
return Ok(entry.value);
}
}
}
}
}
match get_fresh_value().await {
Ok(fresh_value) => {
if let Some(ref validator) = check_value {
validator.check(&fresh_value)?;
}
if let Some(ttl_duration) = ttl {
if ttl_duration > Duration::ZERO {
let metadata = CacheMetadata {
created_time: now,
ttl,
};
let entry = CacheEntry {
value: fresh_value.clone(),
metadata,
};
if cache.set(&key, entry).await.is_err() {
}
}
}
Ok(fresh_value)
}
Err(e) => {
if fallback_to_cache {
if let Some(entry) = cache.get(&key).await {
if let Some(ref validator) = check_value {
if validator.check(&entry.value).is_ok() {
return Ok(entry.value);
}
} else {
return Ok(entry.value);
}
}
}
Err(e)
}
}
}
fn current_time() -> Duration {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
}
fn is_expired(metadata: &CacheMetadata, now: Duration) -> bool {
if let Some(ttl) = metadata.ttl {
now >= metadata.created_time + ttl
} else {
false }
}