use std::time::Instant;
use axum::{Extension, Json};
use serde::Serialize;
#[derive(Clone, Debug)]
pub struct HealthzState {
pub mode: &'static str,
pub storage: &'static str,
pub version: &'static str,
pub started_at: Instant,
}
impl HealthzState {
pub fn new(mode: &'static str, storage: &'static str) -> Self {
Self {
mode,
storage,
version: env!("CARGO_PKG_VERSION"),
started_at: Instant::now(),
}
}
}
#[derive(Debug, Serialize, PartialEq, Eq)]
pub struct HealthzBody {
pub mode: String,
pub version: String,
pub storage: String,
pub uptime_secs: u64,
}
pub async fn healthz(Extension(state): Extension<HealthzState>) -> Json<HealthzBody> {
Json(HealthzBody {
mode: state.mode.to_string(),
version: state.version.to_string(),
storage: state.storage.to_string(),
uptime_secs: state.started_at.elapsed().as_secs(),
})
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
#[test]
fn body_serialises_to_documented_shape() {
let body = HealthzBody {
mode: "remote".into(),
version: "0.0.1".into(),
storage: "memory".into(),
uptime_secs: 7,
};
let json = serde_json::to_value(&body).expect("HealthzBody must serialise");
assert_eq!(json["mode"], "remote");
assert_eq!(json["version"], "0.0.1");
assert_eq!(json["storage"], "memory");
assert_eq!(json["uptime_secs"], 7);
}
#[tokio::test]
async fn handler_returns_documented_body() {
let state = HealthzState::new("remote", "memory");
let Json(body) = healthz(Extension(state)).await;
assert_eq!(body.mode, "remote");
assert_eq!(body.storage, "memory");
assert_eq!(body.version, env!("CARGO_PKG_VERSION"));
}
#[tokio::test]
async fn uptime_secs_is_monotonic_non_negative() {
let state = HealthzState {
mode: "remote",
storage: "memory",
version: "0.0.1",
started_at: Instant::now() - Duration::from_secs(5),
};
let Json(first) = healthz(Extension(state.clone())).await;
let Json(second) = healthz(Extension(state)).await;
assert!(
first.uptime_secs >= 5,
"expected uptime ≥ 5s after backdating started_at, got {}",
first.uptime_secs
);
assert!(
second.uptime_secs >= first.uptime_secs,
"uptime must be monotonic non-decreasing: {} -> {}",
first.uptime_secs,
second.uptime_secs
);
}
}