1use async_trait::async_trait;
4use linkme::distributed_slice;
5use rtb_app::app::App;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum HealthStatus {
10 Ok {
12 summary: String,
14 },
15 Warn {
17 summary: String,
19 },
20 Fail {
22 summary: String,
24 },
25}
26
27impl HealthStatus {
28 #[must_use]
30 pub fn ok(summary: impl Into<String>) -> Self {
31 Self::Ok { summary: summary.into() }
32 }
33
34 #[must_use]
36 pub fn warn(summary: impl Into<String>) -> Self {
37 Self::Warn { summary: summary.into() }
38 }
39
40 #[must_use]
42 pub fn fail(summary: impl Into<String>) -> Self {
43 Self::Fail { summary: summary.into() }
44 }
45
46 #[must_use]
48 pub const fn is_fail(&self) -> bool {
49 matches!(self, Self::Fail { .. })
50 }
51}
52
53#[async_trait]
57pub trait HealthCheck: Send + Sync + 'static {
58 fn name(&self) -> &'static str;
60
61 async fn check(&self, app: &App) -> HealthStatus;
63}
64
65#[distributed_slice]
75pub static HEALTH_CHECKS: [fn() -> Box<dyn HealthCheck>];
76
77#[derive(Debug, Clone)]
79pub struct HealthReport {
80 pub entries: Vec<(&'static str, HealthStatus)>,
82}
83
84impl HealthReport {
85 #[must_use]
87 pub fn is_ok(&self) -> bool {
88 self.entries.iter().all(|(_, s)| !s.is_fail())
89 }
90
91 #[must_use]
93 pub fn render(&self) -> String {
94 use std::fmt::Write;
95 let mut out = String::new();
96 for (name, status) in &self.entries {
97 let (label, summary) = match status {
98 HealthStatus::Ok { summary } => ("OK ", summary),
99 HealthStatus::Warn { summary } => ("WARN", summary),
100 HealthStatus::Fail { summary } => ("FAIL", summary),
101 };
102 let _ = writeln!(out, " [{label}] {name}: {summary}");
103 }
104 out
105 }
106}
107
108pub async fn run_all(app: &App) -> HealthReport {
111 let mut entries = Vec::with_capacity(HEALTH_CHECKS.len());
112 for factory in HEALTH_CHECKS {
113 let check = factory();
114 let status = check.check(app).await;
115 entries.push((check.name(), status));
116 }
117 HealthReport { entries }
118}