1use async_trait::async_trait;
23use serde::{Deserialize, Serialize};
24use std::collections::HashMap;
25use std::sync::Arc;
26use std::time::{Duration, Instant};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30pub enum HealthStatus {
31 Healthy,
33 Degraded,
35 Unhealthy,
37}
38
39impl HealthStatus {
40 pub fn is_healthy(&self) -> bool {
42 matches!(self, HealthStatus::Healthy)
43 }
44
45 pub fn is_degraded(&self) -> bool {
47 matches!(self, HealthStatus::Degraded)
48 }
49
50 pub fn is_unhealthy(&self) -> bool {
52 matches!(self, HealthStatus::Unhealthy)
53 }
54
55 pub fn is_ready(&self) -> bool {
57 !self.is_unhealthy()
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct HealthCheckResult {
64 pub status: HealthStatus,
66 pub component: String,
68 pub message: String,
70 pub checked_at: String,
72 pub duration_ms: u64,
74 pub metadata: HashMap<String, String>,
76}
77
78impl HealthCheckResult {
79 pub fn healthy(component: String, message: String, duration: Duration) -> Self {
81 Self {
82 status: HealthStatus::Healthy,
83 component,
84 message,
85 checked_at: chrono::Utc::now().to_rfc3339(),
86 duration_ms: duration.as_millis() as u64,
87 metadata: HashMap::new(),
88 }
89 }
90
91 pub fn degraded(component: String, message: String, duration: Duration) -> Self {
93 Self {
94 status: HealthStatus::Degraded,
95 component,
96 message,
97 checked_at: chrono::Utc::now().to_rfc3339(),
98 duration_ms: duration.as_millis() as u64,
99 metadata: HashMap::new(),
100 }
101 }
102
103 pub fn unhealthy(component: String, message: String, duration: Duration) -> Self {
105 Self {
106 status: HealthStatus::Unhealthy,
107 component,
108 message,
109 checked_at: chrono::Utc::now().to_rfc3339(),
110 duration_ms: duration.as_millis() as u64,
111 metadata: HashMap::new(),
112 }
113 }
114
115 pub fn with_metadata(mut self, key: String, value: String) -> Self {
117 self.metadata.insert(key, value);
118 self
119 }
120}
121
122#[async_trait]
124pub trait HealthCheck: Send + Sync {
125 async fn check_liveness(&self) -> HealthCheckResult;
130
131 async fn check_readiness(&self) -> HealthCheckResult;
136
137 fn component_name(&self) -> String;
139}
140
141pub struct HealthChecker {
143 checks: Arc<parking_lot::RwLock<Vec<Arc<dyn HealthCheck>>>>,
145}
146
147impl HealthChecker {
148 pub fn new() -> Self {
150 Self {
151 checks: Arc::new(parking_lot::RwLock::new(Vec::new())),
152 }
153 }
154
155 pub fn register<H: HealthCheck + 'static>(&self, check: H) {
157 self.checks.write().push(Arc::new(check));
158 }
159
160 pub async fn check_liveness(&self) -> AggregateHealthResult {
162 let checks = self.checks.read().clone();
163 let mut results = Vec::new();
164
165 for check in checks {
166 results.push(check.check_liveness().await);
167 }
168
169 AggregateHealthResult::from_results(results)
170 }
171
172 pub async fn check_readiness(&self) -> AggregateHealthResult {
174 let checks = self.checks.read().clone();
175 let mut results = Vec::new();
176
177 for check in checks {
178 results.push(check.check_readiness().await);
179 }
180
181 AggregateHealthResult::from_results(results)
182 }
183
184 pub async fn detailed_status(&self) -> DetailedHealthStatus {
186 let checks = self.checks.read().clone();
187 let mut liveness_results = Vec::new();
188 let mut readiness_results = Vec::new();
189
190 for check in checks {
191 liveness_results.push(check.check_liveness().await);
192 readiness_results.push(check.check_readiness().await);
193 }
194
195 DetailedHealthStatus {
196 liveness: AggregateHealthResult::from_results(liveness_results),
197 readiness: AggregateHealthResult::from_results(readiness_results),
198 }
199 }
200}
201
202impl Default for HealthChecker {
203 fn default() -> Self {
204 Self::new()
205 }
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct AggregateHealthResult {
211 pub status: HealthStatus,
213 pub components: Vec<HealthCheckResult>,
215 pub total_components: usize,
217 pub healthy_count: usize,
219 pub degraded_count: usize,
221 pub unhealthy_count: usize,
223}
224
225impl AggregateHealthResult {
226 pub fn from_results(components: Vec<HealthCheckResult>) -> Self {
228 let total_components = components.len();
229 let mut healthy_count = 0;
230 let mut degraded_count = 0;
231 let mut unhealthy_count = 0;
232
233 for result in &components {
234 match result.status {
235 HealthStatus::Healthy => healthy_count += 1,
236 HealthStatus::Degraded => degraded_count += 1,
237 HealthStatus::Unhealthy => unhealthy_count += 1,
238 }
239 }
240
241 let status = if unhealthy_count > 0 {
243 HealthStatus::Unhealthy
244 } else if degraded_count > 0 {
245 HealthStatus::Degraded
246 } else {
247 HealthStatus::Healthy
248 };
249
250 Self {
251 status,
252 components,
253 total_components,
254 healthy_count,
255 degraded_count,
256 unhealthy_count,
257 }
258 }
259
260 pub fn all_healthy(&self) -> bool {
262 self.status == HealthStatus::Healthy
263 }
264
265 pub fn any_unhealthy(&self) -> bool {
267 self.unhealthy_count > 0
268 }
269
270 pub fn unhealthy_components(&self) -> Vec<&HealthCheckResult> {
272 self.components
273 .iter()
274 .filter(|r| r.status == HealthStatus::Unhealthy)
275 .collect()
276 }
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct DetailedHealthStatus {
282 pub liveness: AggregateHealthResult,
284 pub readiness: AggregateHealthResult,
286}
287
288impl DetailedHealthStatus {
289 pub fn is_alive(&self) -> bool {
291 self.liveness.status != HealthStatus::Unhealthy
292 }
293
294 pub fn is_ready(&self) -> bool {
296 self.readiness.status != HealthStatus::Unhealthy
297 }
298}
299
300#[derive(Clone)]
302pub struct SimpleHealthCheck {
303 name: String,
304 is_healthy: Arc<parking_lot::RwLock<bool>>,
305}
306
307impl SimpleHealthCheck {
308 pub fn new(name: String) -> Self {
310 Self {
311 name,
312 is_healthy: Arc::new(parking_lot::RwLock::new(true)),
313 }
314 }
315
316 pub fn set_healthy(&self, healthy: bool) {
318 *self.is_healthy.write() = healthy;
319 }
320}
321
322#[async_trait]
323impl HealthCheck for SimpleHealthCheck {
324 async fn check_liveness(&self) -> HealthCheckResult {
325 let start = Instant::now();
326 let is_healthy = *self.is_healthy.read();
327 let duration = start.elapsed();
328
329 if is_healthy {
330 HealthCheckResult::healthy(
331 self.name.clone(),
332 "Component is alive".to_string(),
333 duration,
334 )
335 } else {
336 HealthCheckResult::unhealthy(
337 self.name.clone(),
338 "Component is not alive".to_string(),
339 duration,
340 )
341 }
342 }
343
344 async fn check_readiness(&self) -> HealthCheckResult {
345 let start = Instant::now();
346 let is_healthy = *self.is_healthy.read();
347 let duration = start.elapsed();
348
349 if is_healthy {
350 HealthCheckResult::healthy(
351 self.name.clone(),
352 "Component is ready".to_string(),
353 duration,
354 )
355 } else {
356 HealthCheckResult::unhealthy(
357 self.name.clone(),
358 "Component is not ready".to_string(),
359 duration,
360 )
361 }
362 }
363
364 fn component_name(&self) -> String {
365 self.name.clone()
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[tokio::test]
374 async fn test_health_status() {
375 assert!(HealthStatus::Healthy.is_healthy());
376 assert!(!HealthStatus::Degraded.is_healthy());
377 assert!(!HealthStatus::Unhealthy.is_healthy());
378
379 assert!(HealthStatus::Healthy.is_ready());
380 assert!(HealthStatus::Degraded.is_ready());
381 assert!(!HealthStatus::Unhealthy.is_ready());
382 }
383
384 #[tokio::test]
385 async fn test_simple_health_check() {
386 let check = SimpleHealthCheck::new("test".to_string());
387
388 let result = check.check_liveness().await;
389 assert!(result.status.is_healthy());
390
391 check.set_healthy(false);
392 let result = check.check_liveness().await;
393 assert!(result.status.is_unhealthy());
394 }
395
396 #[tokio::test]
397 async fn test_health_checker_aggregate() {
398 let checker = HealthChecker::new();
399
400 let check1 = SimpleHealthCheck::new("component1".to_string());
401 let check2 = SimpleHealthCheck::new("component2".to_string());
402
403 checker.register(check1.clone());
404 checker.register(check2.clone());
405
406 let result = checker.check_liveness().await;
407 assert!(result.all_healthy());
408 assert_eq!(result.healthy_count, 2);
409
410 check1.set_healthy(false);
412
413 let result = checker.check_liveness().await;
414 assert!(!result.all_healthy());
415 assert!(result.any_unhealthy());
416 assert_eq!(result.healthy_count, 1);
417 assert_eq!(result.unhealthy_count, 1);
418 }
419
420 #[tokio::test]
421 async fn test_detailed_status() {
422 let checker = HealthChecker::new();
423 let check = SimpleHealthCheck::new("test".to_string());
424 checker.register(check);
425
426 let status = checker.detailed_status().await;
427 assert!(status.is_alive());
428 assert!(status.is_ready());
429 }
430
431 #[tokio::test]
432 async fn test_aggregate_health_result() {
433 let results = vec![
434 HealthCheckResult::healthy(
435 "comp1".to_string(),
436 "OK".to_string(),
437 Duration::from_millis(10),
438 ),
439 HealthCheckResult::degraded(
440 "comp2".to_string(),
441 "Slow".to_string(),
442 Duration::from_millis(100),
443 ),
444 ];
445
446 let aggregate = AggregateHealthResult::from_results(results);
447 assert_eq!(aggregate.status, HealthStatus::Degraded);
448 assert_eq!(aggregate.healthy_count, 1);
449 assert_eq!(aggregate.degraded_count, 1);
450 assert_eq!(aggregate.unhealthy_count, 0);
451 }
452}