use async_trait::async_trait;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HealthStatus {
Healthy,
Degraded,
Unhealthy,
}
impl fmt::Display for HealthStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HealthStatus::Healthy => write!(f, "healthy"),
HealthStatus::Degraded => write!(f, "degraded"),
HealthStatus::Unhealthy => write!(f, "unhealthy"),
}
}
}
#[derive(Debug, Clone)]
pub struct HealthCheckResult {
pub component: String,
pub status: HealthStatus,
pub message: Option<String>,
pub metadata: HashMap<String, String>,
}
impl HealthCheckResult {
pub fn healthy(component: impl Into<String>) -> Self {
Self {
component: component.into(),
status: HealthStatus::Healthy,
message: None,
metadata: HashMap::new(),
}
}
pub fn degraded(component: impl Into<String>, message: impl Into<String>) -> Self {
Self {
component: component.into(),
status: HealthStatus::Degraded,
message: Some(message.into()),
metadata: HashMap::new(),
}
}
pub fn unhealthy(component: impl Into<String>, message: impl Into<String>) -> Self {
Self {
component: component.into(),
status: HealthStatus::Unhealthy,
message: Some(message.into()),
metadata: HashMap::new(),
}
}
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
}
#[derive(Debug, Clone)]
pub struct HealthReport {
pub status: HealthStatus,
pub checks: Vec<HealthCheckResult>,
}
impl HealthReport {
pub fn new(checks: Vec<HealthCheckResult>) -> Self {
let status = checks
.iter()
.map(|c| c.status)
.max_by_key(|s| match s {
HealthStatus::Healthy => 0,
HealthStatus::Degraded => 1,
HealthStatus::Unhealthy => 2,
})
.unwrap_or(HealthStatus::Healthy);
Self { status, checks }
}
pub fn is_healthy(&self) -> bool {
self.status == HealthStatus::Healthy
}
pub fn is_unhealthy(&self) -> bool {
self.status == HealthStatus::Unhealthy
}
}
#[async_trait]
pub trait HealthCheck: Send + Sync {
async fn check(&self) -> HealthCheckResult;
}
pub trait CacheHealthCheck: HealthCheck {}
pub trait DatabaseHealthCheck: HealthCheck {}
#[derive(Default)]
pub struct HealthCheckManager {
checks: HashMap<String, Arc<dyn HealthCheck>>,
}
impl HealthCheckManager {
pub fn new() -> Self {
Self {
checks: HashMap::new(),
}
}
pub fn register(&mut self, name: impl Into<String>, check: Arc<dyn HealthCheck>) {
self.checks.insert(name.into(), check);
}
pub fn count(&self) -> usize {
self.checks.len()
}
pub async fn run_checks(&self) -> HealthReport {
let mut results = Vec::new();
for check in self.checks.values() {
let result = check.check().await;
results.push(result);
}
HealthReport::new(results)
}
}