athena_rs 3.22.1

Hyper performant polyglot Database driver
Documentation
//! Cluster-health cache helpers for `/health/cluster`.
//!
//! This module isolates cache-entry serialization, freshness checks, and cache
//! read/write orchestration so the route handler can focus on payload
//! collection and response behavior.

use serde::{Deserialize, Serialize};
use serde_json::json;

use crate::AppState;
use crate::api::health_contracts::ClusterHealthPayload;

/// Maximum age (seconds) for a reusable cached cluster-health payload.
const CLUSTER_CACHE_MAX_AGE_SECONDS: i64 = 15;

/// Cached cluster-health payload wrapper with cache timestamp.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ClusterHealthCacheEntry {
    /// Unix timestamp when this payload was cached.
    cached_at_epoch_seconds: i64,
    /// Cached cluster-health payload.
    payload: ClusterHealthPayload,
}

/// Reads a cached cluster-health payload when the cache entry is still fresh.
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
    }
}

/// Stores a cluster-health payload into cache with the provided timestamp.
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;
}

/// Returns whether a cache entry is still reusable for route responses.
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
}

/// Computes non-negative cache age in 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]
    /// Keeps cache entries fresh exactly at the max-age threshold.
    fn cache_entry_is_fresh_at_threshold() {
        assert!(is_cache_entry_fresh(30, 15));
    }

    #[test]
    /// Expires cache entries that are older than the max-age threshold.
    fn cache_entry_is_not_fresh_past_threshold() {
        assert!(!is_cache_entry_fresh(31, 15));
    }

    #[test]
    /// Treats future-dated cache entries as zero age via explicit clamping.
    fn cache_age_clamps_future_timestamp() {
        assert_eq!(cache_age_seconds(10, 11), 0);
        assert!(is_cache_entry_fresh(10, 11));
    }
}