use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthStatus {
pub healthy: bool,
pub last_checked: DateTime<Utc>,
pub check_duration_ms: u64,
pub error: Option<String>,
}
impl HealthStatus {
#[must_use]
pub fn healthy(duration_ms: u64) -> Self {
Self {
healthy: true,
last_checked: Utc::now(),
check_duration_ms: duration_ms,
error: None,
}
}
#[must_use]
pub fn unhealthy(duration_ms: u64, error: String) -> Self {
Self {
healthy: false,
last_checked: Utc::now(),
check_duration_ms: duration_ms,
error: Some(error),
}
}
}
pub async fn check_health(backend: &dyn super::SandboxBackend) -> HealthStatus {
let start = std::time::Instant::now();
match backend.health_check().await {
Ok(true) => HealthStatus::healthy(start.elapsed().as_millis() as u64),
Ok(false) => HealthStatus::unhealthy(
start.elapsed().as_millis() as u64,
"health check returned false".into(),
),
Err(e) => HealthStatus::unhealthy(start.elapsed().as_millis() as u64, e.to_string()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn healthy_status() {
let status = HealthStatus::healthy(42);
assert!(status.healthy);
assert_eq!(status.check_duration_ms, 42);
assert!(status.error.is_none());
}
#[test]
fn unhealthy_status() {
let status = HealthStatus::unhealthy(100, "timeout".into());
assert!(!status.healthy);
assert_eq!(status.error.as_deref(), Some("timeout"));
}
#[test]
fn serde_roundtrip() {
let status = HealthStatus::healthy(10);
let json = serde_json::to_string(&status).unwrap();
let back: HealthStatus = serde_json::from_str(&json).unwrap();
assert!(back.healthy);
}
#[tokio::test]
async fn check_noop_health() {
let noop = crate::backend::NoopBackend;
let status = check_health(&noop).await;
assert!(status.healthy);
}
}