Skip to main content

rskit_component/
health.rs

1/// Liveness state of a component.
2#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
3#[serde(rename_all = "lowercase")]
4#[non_exhaustive]
5pub enum HealthStatus {
6    /// Component is operating normally.
7    Healthy,
8    /// Component is functional but operating in a reduced capacity.
9    Degraded,
10    /// Component is not functioning correctly.
11    Unhealthy,
12}
13
14impl std::fmt::Display for HealthStatus {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        match self {
17            Self::Healthy => f.write_str("healthy"),
18            Self::Degraded => f.write_str("degraded"),
19            Self::Unhealthy => f.write_str("unhealthy"),
20        }
21    }
22}
23
24/// Health report from a single component.
25#[derive(Debug, Clone, PartialEq, serde::Serialize)]
26pub struct Health {
27    /// Component name as returned by [`crate::Component::name`].
28    pub name: String,
29    /// Overall health status of the component.
30    pub status: HealthStatus,
31    /// Optional human-readable explanation for non-healthy status.
32    pub message: Option<String>,
33}
34
35impl Health {
36    /// Create a healthy report for the named component.
37    #[must_use]
38    pub fn healthy(name: impl Into<String>) -> Self {
39        Self {
40            name: name.into(),
41            status: HealthStatus::Healthy,
42            message: None,
43        }
44    }
45
46    /// Create a degraded report with an explanatory message.
47    #[must_use]
48    pub fn degraded(name: impl Into<String>, msg: impl Into<String>) -> Self {
49        Self {
50            name: name.into(),
51            status: HealthStatus::Degraded,
52            message: Some(msg.into()),
53        }
54    }
55
56    /// Create an unhealthy report with an explanatory message.
57    #[must_use]
58    pub fn unhealthy(name: impl Into<String>, msg: impl Into<String>) -> Self {
59        Self {
60            name: name.into(),
61            status: HealthStatus::Unhealthy,
62            message: Some(msg.into()),
63        }
64    }
65
66    /// Returns `true` if the status is [`HealthStatus::Healthy`].
67    #[must_use]
68    pub fn is_healthy(&self) -> bool {
69        self.status == HealthStatus::Healthy
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::{Health, HealthStatus};
76
77    #[test]
78    fn healthy_sets_status_and_no_message() {
79        let h = Health::healthy("db");
80        assert_eq!(h.status, HealthStatus::Healthy);
81        assert_eq!(h.name, "db");
82        assert!(h.message.is_none());
83    }
84
85    #[test]
86    fn healthy_is_healthy_returns_true() {
87        assert!(Health::healthy("cache").is_healthy());
88    }
89
90    #[test]
91    fn degraded_sets_status_and_message() {
92        let h = Health::degraded("queue", "high latency");
93        assert_eq!(h.status, HealthStatus::Degraded);
94        assert_eq!(h.message, Some("high latency".to_string()));
95    }
96
97    #[test]
98    fn unhealthy_sets_status_and_message() {
99        let h = Health::unhealthy("db", "connection refused");
100        assert_eq!(h.status, HealthStatus::Unhealthy);
101        assert_eq!(h.message, Some("connection refused".to_string()));
102    }
103
104    #[test]
105    fn health_status_display_values() {
106        assert_eq!(HealthStatus::Healthy.to_string(), "healthy");
107        assert_eq!(HealthStatus::Degraded.to_string(), "degraded");
108        assert_eq!(HealthStatus::Unhealthy.to_string(), "unhealthy");
109    }
110}