use async_trait::async_trait;
use linkme::distributed_slice;
use rtb_app::app::App;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HealthStatus {
Ok {
summary: String,
},
Warn {
summary: String,
},
Fail {
summary: String,
},
}
impl HealthStatus {
#[must_use]
pub fn ok(summary: impl Into<String>) -> Self {
Self::Ok { summary: summary.into() }
}
#[must_use]
pub fn warn(summary: impl Into<String>) -> Self {
Self::Warn { summary: summary.into() }
}
#[must_use]
pub fn fail(summary: impl Into<String>) -> Self {
Self::Fail { summary: summary.into() }
}
#[must_use]
pub const fn is_fail(&self) -> bool {
matches!(self, Self::Fail { .. })
}
}
#[async_trait]
pub trait HealthCheck: Send + Sync + 'static {
fn name(&self) -> &'static str;
async fn check(&self, app: &App) -> HealthStatus;
}
#[distributed_slice]
pub static HEALTH_CHECKS: [fn() -> Box<dyn HealthCheck>];
#[derive(Debug, Clone)]
pub struct HealthReport {
pub entries: Vec<(&'static str, HealthStatus)>,
}
impl HealthReport {
#[must_use]
pub fn is_ok(&self) -> bool {
self.entries.iter().all(|(_, s)| !s.is_fail())
}
#[must_use]
pub fn render(&self) -> String {
use std::fmt::Write;
let mut out = String::new();
for (name, status) in &self.entries {
let (label, summary) = match status {
HealthStatus::Ok { summary } => ("OK ", summary),
HealthStatus::Warn { summary } => ("WARN", summary),
HealthStatus::Fail { summary } => ("FAIL", summary),
};
let _ = writeln!(out, " [{label}] {name}: {summary}");
}
out
}
}
pub async fn run_all(app: &App) -> HealthReport {
let mut entries = Vec::with_capacity(HEALTH_CHECKS.len());
for factory in HEALTH_CHECKS {
let check = factory();
let status = check.check(app).await;
entries.push((check.name(), status));
}
HealthReport { entries }
}