github_mcp/core/
component_registry.rs1use std::collections::HashMap;
4use std::time::SystemTime;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ComponentStatus {
8 Healthy,
9 Degraded,
10 Unhealthy,
11}
12
13#[derive(Debug, Clone)]
14pub struct ComponentState {
15 pub name: String,
16 pub critical: bool,
17 pub status: ComponentStatus,
18 pub last_checked_at: Option<SystemTime>,
19 pub last_error: Option<String>,
20}
21
22#[derive(Debug, Default)]
26pub struct ComponentRegistry {
27 components: HashMap<String, ComponentState>,
28}
29
30impl ComponentRegistry {
31 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn register(&mut self, name: &str, critical: bool) {
36 self.components.insert(
37 name.to_string(),
38 ComponentState {
39 name: name.to_string(),
40 critical,
41 status: ComponentStatus::Healthy,
42 last_checked_at: None,
43 last_error: None,
44 },
45 );
46 }
47
48 pub fn report(&mut self, name: &str, status: ComponentStatus, error: Option<String>) {
49 let critical = self
50 .components
51 .get(name)
52 .map(|existing| existing.critical)
53 .unwrap_or(false);
54 self.components.insert(
55 name.to_string(),
56 ComponentState {
57 name: name.to_string(),
58 critical,
59 status,
60 last_checked_at: Some(SystemTime::now()),
61 last_error: error,
62 },
63 );
64 }
65
66 pub fn overall_status(&self) -> ComponentStatus {
67 let states: Vec<&ComponentState> = self.components.values().collect();
68 if states
69 .iter()
70 .any(|c| c.critical && c.status == ComponentStatus::Unhealthy)
71 {
72 return ComponentStatus::Unhealthy;
73 }
74 if states.iter().any(|c| c.status != ComponentStatus::Healthy) {
75 return ComponentStatus::Degraded;
76 }
77 ComponentStatus::Healthy
78 }
79
80 pub fn snapshot(&self) -> Vec<ComponentState> {
81 self.components.values().cloned().collect()
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn newly_registered_components_start_healthy() {
91 let mut registry = ComponentRegistry::new();
92 registry.register("db", true);
93 assert_eq!(registry.overall_status(), ComponentStatus::Healthy);
94 }
95
96 #[test]
97 fn a_critical_component_failure_makes_the_whole_registry_unhealthy() {
98 let mut registry = ComponentRegistry::new();
99 registry.register("db", true);
100 registry.report(
101 "db",
102 ComponentStatus::Unhealthy,
103 Some("timeout".to_string()),
104 );
105 assert_eq!(registry.overall_status(), ComponentStatus::Unhealthy);
106 }
107
108 #[test]
109 fn a_non_critical_component_failure_only_degrades_the_registry() {
110 let mut registry = ComponentRegistry::new();
111 registry.register("cache", false);
112 registry.report("cache", ComponentStatus::Unhealthy, None);
113 assert_eq!(registry.overall_status(), ComponentStatus::Degraded);
114 }
115}