use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceFreshness {
pub source: String,
pub status: FreshnessStatus,
pub cache_hits: u64,
pub cache_misses: u64,
pub fetched_at: Option<DateTime<Utc>>,
pub reason: Option<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FreshnessStatus {
Fresh,
Cached,
Stale,
Skipped,
Unavailable,
}
impl FreshnessStatus {
pub fn as_label(&self) -> &'static str {
match self {
FreshnessStatus::Fresh => "fresh",
FreshnessStatus::Cached => "cached",
FreshnessStatus::Stale => "stale",
FreshnessStatus::Skipped => "skipped",
FreshnessStatus::Unavailable => "unavailable",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn status_label_is_snake_case() {
assert_eq!(FreshnessStatus::Fresh.as_label(), "fresh");
assert_eq!(FreshnessStatus::Cached.as_label(), "cached");
assert_eq!(FreshnessStatus::Stale.as_label(), "stale");
assert_eq!(FreshnessStatus::Skipped.as_label(), "skipped");
assert_eq!(FreshnessStatus::Unavailable.as_label(), "unavailable");
}
#[test]
fn source_freshness_round_trips_through_json() -> serde_json::Result<()> {
let receipt = SourceFreshness {
source: "github".into(),
status: FreshnessStatus::Cached,
cache_hits: 7,
cache_misses: 0,
fetched_at: None,
reason: None,
};
let json = serde_json::to_string(&receipt)?;
let back: SourceFreshness = serde_json::from_str(&json)?;
assert_eq!(back, receipt);
Ok(())
}
#[test]
fn status_serialises_as_snake_case() -> serde_json::Result<()> {
let json = serde_json::to_string(&FreshnessStatus::Unavailable)?;
assert_eq!(json, "\"unavailable\"");
Ok(())
}
}