use serde::{Deserialize, Serialize};
use serde_json::json;
use crate::AppState;
use crate::api::health_contracts::ClusterHealthPayload;
const CLUSTER_CACHE_MAX_AGE_SECONDS: i64 = 15;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ClusterHealthCacheEntry {
cached_at_epoch_seconds: i64,
payload: ClusterHealthPayload,
}
pub(super) async fn read_fresh_cluster_payload(
app_state: &AppState,
cache_key: &str,
now_epoch_seconds: i64,
) -> Option<ClusterHealthPayload> {
let cached = app_state.cache.get(cache_key).await?;
let entry = serde_json::from_value::<ClusterHealthCacheEntry>(cached).ok()?;
if is_cache_entry_fresh(now_epoch_seconds, entry.cached_at_epoch_seconds) {
Some(entry.payload)
} else {
None
}
}
pub(super) async fn write_cluster_payload_cache(
app_state: &AppState,
cache_key: &str,
payload: &ClusterHealthPayload,
now_epoch_seconds: i64,
) {
app_state
.cache
.insert(
cache_key.to_string(),
json!(ClusterHealthCacheEntry {
cached_at_epoch_seconds: now_epoch_seconds,
payload: payload.clone(),
}),
)
.await;
}
fn is_cache_entry_fresh(now_epoch_seconds: i64, cached_at_epoch_seconds: i64) -> bool {
cache_age_seconds(now_epoch_seconds, cached_at_epoch_seconds) <= CLUSTER_CACHE_MAX_AGE_SECONDS
}
fn cache_age_seconds(now_epoch_seconds: i64, cached_at_epoch_seconds: i64) -> i64 {
if now_epoch_seconds <= cached_at_epoch_seconds {
0
} else {
now_epoch_seconds - cached_at_epoch_seconds
}
}
#[cfg(test)]
mod tests {
use super::cache_age_seconds;
use super::is_cache_entry_fresh;
#[test]
fn cache_entry_is_fresh_at_threshold() {
assert!(is_cache_entry_fresh(30, 15));
}
#[test]
fn cache_entry_is_not_fresh_past_threshold() {
assert!(!is_cache_entry_fresh(31, 15));
}
#[test]
fn cache_age_clamps_future_timestamp() {
assert_eq!(cache_age_seconds(10, 11), 0);
assert!(is_cache_entry_fresh(10, 11));
}
}