1use std::collections::HashMap;
10use std::sync::Arc;
11use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
12use tokio::sync::RwLock;
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone)]
17pub struct HealthChecker {
18 health_checks: Arc<RwLock<HashMap<String, HealthCheck>>>,
20 results: Arc<RwLock<HashMap<String, HealthCheckResult>>>,
22 config: HealthConfig,
24 initialized: bool,
26}
27
28impl HealthChecker {
29 pub fn new() -> Self {
31 Self {
32 health_checks: Arc::new(RwLock::new(HashMap::new())),
33 results: Arc::new(RwLock::new(HashMap::new())),
34 config: HealthConfig::default(),
35 initialized: false,
36 }
37 }
38
39 pub fn with_config(config: HealthConfig) -> Self {
41 Self {
42 health_checks: Arc::new(RwLock::new(HashMap::new())),
43 results: Arc::new(RwLock::new(HashMap::new())),
44 config,
45 initialized: false,
46 }
47 }
48
49 pub async fn initialize(&mut self) -> Result<(), HealthError> {
51 self.add_default_health_checks().await;
53
54 self.initialized = true;
55 Ok(())
56 }
57
58 pub async fn shutdown(&mut self) -> Result<(), HealthError> {
60 self.initialized = false;
61 Ok(())
62 }
63
64 pub fn is_initialized(&self) -> bool {
66 self.initialized
67 }
68
69 pub async fn add_health_check(&self, name: String, health_check: HealthCheck) -> Result<(), HealthError> {
71 if !self.initialized {
72 return Err(HealthError::NotInitialized);
73 }
74
75 let mut health_checks = self.health_checks.write().await;
76 health_checks.insert(name, health_check);
77
78 Ok(())
79 }
80
81 pub async fn remove_health_check(&self, name: &str) -> Result<(), HealthError> {
83 if !self.initialized {
84 return Err(HealthError::NotInitialized);
85 }
86
87 let mut health_checks = self.health_checks.write().await;
88 health_checks.remove(name);
89
90 let mut results = self.results.write().await;
91 results.remove(name);
92
93 Ok(())
94 }
95
96 pub async fn run_health_check(&self, name: &str) -> Result<HealthCheckResult, HealthError> {
98 if !self.initialized {
99 return Err(HealthError::NotInitialized);
100 }
101
102 let health_checks = self.health_checks.read().await;
103 let health_check = health_checks.get(name)
104 .ok_or_else(|| HealthError::HealthCheckNotFound(name.to_string()))?;
105
106 let result = self.execute_health_check(health_check).await;
107
108 let mut results = self.results.write().await;
110 results.insert(name.to_string(), result.clone());
111
112 Ok(result)
113 }
114
115 pub async fn check_all(&self) -> Result<Vec<HealthCheckResult>, HealthError> {
117 if !self.initialized {
118 return Err(HealthError::NotInitialized);
119 }
120
121 let health_checks = self.health_checks.read().await;
122 let mut results = Vec::new();
123
124 for (name, health_check) in health_checks.iter() {
125 let result = self.execute_health_check(health_check).await;
126 results.push(result);
127 }
128
129 let mut stored_results = self.results.write().await;
131 for (name, health_check) in health_checks.iter() {
132 let result = self.execute_health_check(health_check).await;
133 stored_results.insert(name.clone(), result);
134 }
135
136 Ok(results)
137 }
138
139 pub async fn get_health_check_result(&self, name: &str) -> Result<Option<HealthCheckResult>, HealthError> {
141 if !self.initialized {
142 return Err(HealthError::NotInitialized);
143 }
144
145 let results = self.results.read().await;
146 Ok(results.get(name).cloned())
147 }
148
149 pub async fn get_all_results(&self) -> Result<HashMap<String, HealthCheckResult>, HealthError> {
151 if !self.initialized {
152 return Err(HealthError::NotInitialized);
153 }
154
155 let results = self.results.read().await;
156 Ok(results.clone())
157 }
158
159 pub async fn get_system_health(&self) -> Result<SystemHealth, HealthError> {
161 if !self.initialized {
162 return Err(HealthError::NotInitialized);
163 }
164
165 let results = self.check_all().await?;
166 let overall_status = if results.iter().all(|r| r.is_healthy) {
167 HealthStatus::Healthy
168 } else {
169 HealthStatus::Unhealthy
170 };
171
172 Ok(SystemHealth {
173 overall_status,
174 health_checks: results,
175 last_checked: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
176 })
177 }
178
179 async fn execute_health_check(&self, health_check: &HealthCheck) -> HealthCheckResult {
181 let start_time = Instant::now();
182
183 match health_check.check_type {
184 HealthCheckType::Ping => self.ping_check(health_check).await,
185 HealthCheckType::Http => self.http_check(health_check).await,
186 HealthCheckType::Database => self.database_check(health_check).await,
187 HealthCheckType::Custom => self.custom_check(health_check).await,
188 }
189 }
190
191 async fn ping_check(&self, health_check: &HealthCheck) -> HealthCheckResult {
193 let start_time = Instant::now();
194
195 tokio::time::sleep(Duration::from_millis(10)).await;
197
198 let duration = start_time.elapsed();
199 let is_healthy = duration < Duration::from_millis(100);
200
201 HealthCheckResult {
202 name: health_check.name.clone(),
203 is_healthy,
204 message: if is_healthy {
205 "Ping successful".to_string()
206 } else {
207 "Ping timeout".to_string()
208 },
209 duration,
210 last_checked: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
211 details: HashMap::new(),
212 }
213 }
214
215 async fn http_check(&self, health_check: &HealthCheck) -> HealthCheckResult {
217 let start_time = Instant::now();
218
219 tokio::time::sleep(Duration::from_millis(50)).await;
221
222 let duration = start_time.elapsed();
223 let is_healthy = duration < Duration::from_millis(500);
224
225 let mut details = HashMap::new();
226 details.insert("status_code".to_string(), "200".to_string());
227 details.insert("response_time_ms".to_string(), duration.as_millis().to_string());
228
229 HealthCheckResult {
230 name: health_check.name.clone(),
231 is_healthy,
232 message: if is_healthy {
233 "HTTP check successful".to_string()
234 } else {
235 "HTTP check failed".to_string()
236 },
237 duration,
238 last_checked: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
239 details,
240 }
241 }
242
243 async fn database_check(&self, health_check: &HealthCheck) -> HealthCheckResult {
245 let start_time = Instant::now();
246
247 tokio::time::sleep(Duration::from_millis(20)).await;
249
250 let duration = start_time.elapsed();
251 let is_healthy = duration < Duration::from_millis(200);
252
253 let mut details = HashMap::new();
254 details.insert("connection_pool_size".to_string(), "10".to_string());
255 details.insert("active_connections".to_string(), "5".to_string());
256
257 HealthCheckResult {
258 name: health_check.name.clone(),
259 is_healthy,
260 message: if is_healthy {
261 "Database check successful".to_string()
262 } else {
263 "Database check failed".to_string()
264 },
265 duration,
266 last_checked: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
267 details,
268 }
269 }
270
271 async fn custom_check(&self, health_check: &HealthCheck) -> HealthCheckResult {
273 let start_time = Instant::now();
274
275 tokio::time::sleep(Duration::from_millis(30)).await;
277
278 let duration = start_time.elapsed();
279 let is_healthy = duration < Duration::from_millis(300);
280
281 HealthCheckResult {
282 name: health_check.name.clone(),
283 is_healthy,
284 message: if is_healthy {
285 "Custom check successful".to_string()
286 } else {
287 "Custom check failed".to_string()
288 },
289 duration,
290 last_checked: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
291 details: HashMap::new(),
292 }
293 }
294
295 async fn add_default_health_checks(&self) {
297 let default_checks = vec![
298 ("system_memory".to_string(), HealthCheck {
299 name: "system_memory".to_string(),
300 check_type: HealthCheckType::Custom,
301 interval: Duration::from_secs(30),
302 timeout: Duration::from_secs(5),
303 enabled: true,
304 }),
305 ("system_cpu".to_string(), HealthCheck {
306 name: "system_cpu".to_string(),
307 check_type: HealthCheckType::Custom,
308 interval: Duration::from_secs(30),
309 timeout: Duration::from_secs(5),
310 enabled: true,
311 }),
312 ("system_disk".to_string(), HealthCheck {
313 name: "system_disk".to_string(),
314 check_type: HealthCheckType::Custom,
315 interval: Duration::from_secs(60),
316 timeout: Duration::from_secs(10),
317 enabled: true,
318 }),
319 ];
320
321 let mut health_checks = self.health_checks.write().await;
322 for (name, health_check) in default_checks {
323 health_checks.insert(name, health_check);
324 }
325 }
326}
327
328#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
330pub struct HealthCheck {
331 pub name: String,
333 pub check_type: HealthCheckType,
335 pub interval: Duration,
337 pub timeout: Duration,
339 pub enabled: bool,
341}
342
343#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
345pub enum HealthCheckType {
346 Ping,
348 Http,
350 Database,
352 Custom,
354}
355
356#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
358pub struct HealthCheckResult {
359 pub name: String,
361 pub is_healthy: bool,
363 pub message: String,
365 pub duration: Duration,
367 pub last_checked: u64,
369 pub details: HashMap<String, String>,
371}
372
373#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
375pub struct SystemHealth {
376 pub overall_status: HealthStatus,
378 pub health_checks: Vec<HealthCheckResult>,
380 pub last_checked: u64,
382}
383
384#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
386pub enum HealthStatus {
387 Healthy,
389 Unhealthy,
391}
392
393#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
395pub struct HealthConfig {
396 pub health_checks: Vec<HealthCheck>,
398 pub default_interval: Duration,
400 pub default_timeout: Duration,
402 pub enable_health_checks: bool,
404}
405
406impl Default for HealthConfig {
407 fn default() -> Self {
408 Self {
409 health_checks: Vec::new(),
410 default_interval: Duration::from_secs(30),
411 default_timeout: Duration::from_secs(5),
412 enable_health_checks: true,
413 }
414 }
415}
416
417#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
419pub enum HealthError {
420 NotInitialized,
422 HealthCheckNotFound(String),
424 HealthCheckExecutionFailed(String),
426 HealthCheckTimeout,
428 ConfigurationError(String),
430}
431
432impl std::fmt::Display for HealthError {
433 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
434 match self {
435 HealthError::NotInitialized => write!(f, "Health checker not initialized"),
436 HealthError::HealthCheckNotFound(name) => write!(f, "Health check not found: {}", name),
437 HealthError::HealthCheckExecutionFailed(msg) => write!(f, "Health check execution failed: {}", msg),
438 HealthError::HealthCheckTimeout => write!(f, "Health check timeout"),
439 HealthError::ConfigurationError(msg) => write!(f, "Configuration error: {}", msg),
440 }
441 }
442}
443
444impl std::error::Error for HealthError {}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 #[tokio::test]
451 async fn test_health_checker_creation() {
452 let checker = HealthChecker::new();
453 assert!(!checker.is_initialized());
454 }
455
456 #[tokio::test]
457 async fn test_health_checker_initialization() {
458 let mut checker = HealthChecker::new();
459 let result = checker.initialize().await;
460 assert!(result.is_ok());
461 assert!(checker.is_initialized());
462 }
463
464 #[tokio::test]
465 async fn test_health_checker_shutdown() {
466 let mut checker = HealthChecker::new();
467 checker.initialize().await.unwrap();
468 let result = checker.shutdown().await;
469 assert!(result.is_ok());
470 assert!(!checker.is_initialized());
471 }
472
473 #[tokio::test]
474 async fn test_add_health_check() {
475 let mut checker = HealthChecker::new();
476 checker.initialize().await.unwrap();
477
478 let health_check = HealthCheck {
479 name: "test_check".to_string(),
480 check_type: HealthCheckType::Ping,
481 interval: Duration::from_secs(30),
482 timeout: Duration::from_secs(5),
483 enabled: true,
484 };
485
486 let result = checker.add_health_check("test_check".to_string(), health_check).await;
487 assert!(result.is_ok());
488 }
489
490 #[tokio::test]
491 async fn test_remove_health_check() {
492 let mut checker = HealthChecker::new();
493 checker.initialize().await.unwrap();
494
495 let health_check = HealthCheck {
496 name: "test_check".to_string(),
497 check_type: HealthCheckType::Ping,
498 interval: Duration::from_secs(30),
499 timeout: Duration::from_secs(5),
500 enabled: true,
501 };
502
503 checker.add_health_check("test_check".to_string(), health_check).await.unwrap();
504
505 let result = checker.remove_health_check("test_check").await;
506 assert!(result.is_ok());
507 }
508
509 #[tokio::test]
510 async fn test_run_health_check() {
511 let mut checker = HealthChecker::new();
512 checker.initialize().await.unwrap();
513
514 let health_check = HealthCheck {
515 name: "test_check".to_string(),
516 check_type: HealthCheckType::Ping,
517 interval: Duration::from_secs(30),
518 timeout: Duration::from_secs(5),
519 enabled: true,
520 };
521
522 checker.add_health_check("test_check".to_string(), health_check).await.unwrap();
523
524 let result = checker.run_health_check("test_check").await.unwrap();
525 assert_eq!(result.name, "test_check");
526 assert!(result.is_healthy);
527 assert!(!result.message.is_empty());
528 }
529
530 #[tokio::test]
531 async fn test_run_health_check_not_found() {
532 let mut checker = HealthChecker::new();
533 checker.initialize().await.unwrap();
534
535 let result = checker.run_health_check("nonexistent").await;
536 assert!(result.is_err());
537 assert!(matches!(result.unwrap_err(), HealthError::HealthCheckNotFound(_)));
538 }
539
540 #[tokio::test]
541 async fn test_check_all() {
542 let mut checker = HealthChecker::new();
543 checker.initialize().await.unwrap();
544
545 let results = checker.check_all().await.unwrap();
546 assert!(!results.is_empty());
547
548 assert!(results.iter().any(|r| r.name == "system_memory"));
550 assert!(results.iter().any(|r| r.name == "system_cpu"));
551 assert!(results.iter().any(|r| r.name == "system_disk"));
552 }
553
554 #[tokio::test]
555 async fn test_get_health_check_result() {
556 let mut checker = HealthChecker::new();
557 checker.initialize().await.unwrap();
558
559 let health_check = HealthCheck {
560 name: "test_check".to_string(),
561 check_type: HealthCheckType::Ping,
562 interval: Duration::from_secs(30),
563 timeout: Duration::from_secs(5),
564 enabled: true,
565 };
566
567 checker.add_health_check("test_check".to_string(), health_check).await.unwrap();
568 checker.run_health_check("test_check").await.unwrap();
569
570 let result = checker.get_health_check_result("test_check").await.unwrap();
571 assert!(result.is_some());
572 let result = result.unwrap();
573 assert_eq!(result.name, "test_check");
574 assert!(result.is_healthy);
575 }
576
577 #[tokio::test]
578 async fn test_get_all_results() {
579 let mut checker = HealthChecker::new();
580 checker.initialize().await.unwrap();
581
582 checker.check_all().await.unwrap();
583
584 let results = checker.get_all_results().await.unwrap();
585 assert!(!results.is_empty());
586
587 assert!(results.contains_key("system_memory"));
589 assert!(results.contains_key("system_cpu"));
590 assert!(results.contains_key("system_disk"));
591 }
592
593 #[tokio::test]
594 async fn test_get_system_health() {
595 let mut checker = HealthChecker::new();
596 checker.initialize().await.unwrap();
597
598 let system_health = checker.get_system_health().await.unwrap();
599 assert_eq!(system_health.overall_status, HealthStatus::Healthy);
600 assert!(!system_health.health_checks.is_empty());
601 assert!(system_health.last_checked > 0);
602 }
603
604 #[tokio::test]
605 async fn test_health_check_types() {
606 let mut checker = HealthChecker::new();
607 checker.initialize().await.unwrap();
608
609 let ping_check = HealthCheck {
611 name: "ping_check".to_string(),
612 check_type: HealthCheckType::Ping,
613 interval: Duration::from_secs(30),
614 timeout: Duration::from_secs(5),
615 enabled: true,
616 };
617
618 checker.add_health_check("ping_check".to_string(), ping_check).await.unwrap();
619 let result = checker.run_health_check("ping_check").await.unwrap();
620 assert!(result.is_healthy);
621 assert!(result.message.contains("Ping"));
622
623 let http_check = HealthCheck {
625 name: "http_check".to_string(),
626 check_type: HealthCheckType::Http,
627 interval: Duration::from_secs(30),
628 timeout: Duration::from_secs(5),
629 enabled: true,
630 };
631
632 checker.add_health_check("http_check".to_string(), http_check).await.unwrap();
633 let result = checker.run_health_check("http_check").await.unwrap();
634 assert!(result.is_healthy);
635 assert!(result.message.contains("HTTP"));
636 assert!(result.details.contains_key("status_code"));
637
638 let db_check = HealthCheck {
640 name: "db_check".to_string(),
641 check_type: HealthCheckType::Database,
642 interval: Duration::from_secs(30),
643 timeout: Duration::from_secs(5),
644 enabled: true,
645 };
646
647 checker.add_health_check("db_check".to_string(), db_check).await.unwrap();
648 let result = checker.run_health_check("db_check").await.unwrap();
649 assert!(result.is_healthy);
650 assert!(result.message.contains("Database"));
651 assert!(result.details.contains_key("connection_pool_size"));
652
653 let custom_check = HealthCheck {
655 name: "custom_check".to_string(),
656 check_type: HealthCheckType::Custom,
657 interval: Duration::from_secs(30),
658 timeout: Duration::from_secs(5),
659 enabled: true,
660 };
661
662 checker.add_health_check("custom_check".to_string(), custom_check).await.unwrap();
663 let result = checker.run_health_check("custom_check").await.unwrap();
664 assert!(result.is_healthy);
665 assert!(result.message.contains("Custom"));
666 }
667
668 #[test]
669 fn test_health_config_default() {
670 let config = HealthConfig::default();
671 assert_eq!(config.default_interval, Duration::from_secs(30));
672 assert_eq!(config.default_timeout, Duration::from_secs(5));
673 assert!(config.enable_health_checks);
674 assert!(config.health_checks.is_empty());
675 }
676
677 #[test]
678 fn test_health_check_creation() {
679 let health_check = HealthCheck {
680 name: "test_check".to_string(),
681 check_type: HealthCheckType::Ping,
682 interval: Duration::from_secs(30),
683 timeout: Duration::from_secs(5),
684 enabled: true,
685 };
686
687 assert_eq!(health_check.name, "test_check");
688 assert_eq!(health_check.check_type, HealthCheckType::Ping);
689 assert_eq!(health_check.interval, Duration::from_secs(30));
690 assert_eq!(health_check.timeout, Duration::from_secs(5));
691 assert!(health_check.enabled);
692 }
693
694 #[test]
695 fn test_health_check_result_creation() {
696 let result = HealthCheckResult {
697 name: "test_check".to_string(),
698 is_healthy: true,
699 message: "Test check passed".to_string(),
700 duration: Duration::from_millis(100),
701 last_checked: 1234567890,
702 details: HashMap::new(),
703 };
704
705 assert_eq!(result.name, "test_check");
706 assert!(result.is_healthy);
707 assert_eq!(result.message, "Test check passed");
708 assert_eq!(result.duration, Duration::from_millis(100));
709 assert_eq!(result.last_checked, 1234567890);
710 }
711
712 #[test]
713 fn test_health_error_display() {
714 let error = HealthError::HealthCheckNotFound("test_check".to_string());
715 let error_string = format!("{}", error);
716 assert!(error_string.contains("Health check not found"));
717 assert!(error_string.contains("test_check"));
718 }
719}