use std::collections::HashMap;
use std::time::SystemTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComponentStatus {
Healthy,
Degraded,
Unhealthy,
}
#[derive(Debug, Clone)]
pub struct ComponentState {
pub name: String,
pub critical: bool,
pub status: ComponentStatus,
pub last_checked_at: Option<SystemTime>,
pub last_error: Option<String>,
}
#[derive(Debug, Default)]
pub struct ComponentRegistry {
components: HashMap<String, ComponentState>,
}
impl ComponentRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, name: &str, critical: bool) {
self.components.insert(
name.to_string(),
ComponentState {
name: name.to_string(),
critical,
status: ComponentStatus::Healthy,
last_checked_at: None,
last_error: None,
},
);
}
pub fn report(&mut self, name: &str, status: ComponentStatus, error: Option<String>) {
let critical = self
.components
.get(name)
.map(|existing| existing.critical)
.unwrap_or(false);
self.components.insert(
name.to_string(),
ComponentState {
name: name.to_string(),
critical,
status,
last_checked_at: Some(SystemTime::now()),
last_error: error,
},
);
}
pub fn overall_status(&self) -> ComponentStatus {
let states: Vec<&ComponentState> = self.components.values().collect();
if states
.iter()
.any(|c| c.critical && c.status == ComponentStatus::Unhealthy)
{
return ComponentStatus::Unhealthy;
}
if states.iter().any(|c| c.status != ComponentStatus::Healthy) {
return ComponentStatus::Degraded;
}
ComponentStatus::Healthy
}
pub fn snapshot(&self) -> Vec<ComponentState> {
self.components.values().cloned().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn newly_registered_components_start_healthy() {
let mut registry = ComponentRegistry::new();
registry.register("db", true);
assert_eq!(registry.overall_status(), ComponentStatus::Healthy);
}
#[test]
fn a_critical_component_failure_makes_the_whole_registry_unhealthy() {
let mut registry = ComponentRegistry::new();
registry.register("db", true);
registry.report(
"db",
ComponentStatus::Unhealthy,
Some("timeout".to_string()),
);
assert_eq!(registry.overall_status(), ComponentStatus::Unhealthy);
}
#[test]
fn a_non_critical_component_failure_only_degrades_the_registry() {
let mut registry = ComponentRegistry::new();
registry.register("cache", false);
registry.report("cache", ComponentStatus::Unhealthy, None);
assert_eq!(registry.overall_status(), ComponentStatus::Degraded);
}
}