1use std::{collections::HashMap, sync::Arc};
91
92use serde::{Deserialize, Serialize};
93use tokio::sync::RwLock;
94
95use crate::{AirError, Result, Utility, dev_log};
96
97#[derive(Debug)]
99pub struct HealthCheckManager {
100 ServiceHealth:Arc<RwLock<HashMap<String, ServiceHealth>>>,
102 HealthHistory:Arc<RwLock<Vec<HealthCheckRecord>>>,
104 RecoveryActions:Arc<RwLock<HashMap<String, RecoveryAction>>>,
106 config:HealthCheckConfig,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct ServiceHealth {
113 pub ServiceName:String,
115 pub Status:HealthStatus,
117 pub LastCheck:u64,
119 pub LastSuccess:Option<u64>,
121 pub FailureCount:u32,
123 pub ErrorMessage:Option<String>,
125 pub ResponseTimeMs:Option<u64>,
127 pub CheckLevel:HealthCheckLevel,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
133pub enum HealthStatus {
134 Healthy,
136 Degraded,
138 Unhealthy,
140 Unknown,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub enum HealthCheckLevel {
147 Alive,
149 Responsive,
151 Functional,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct HealthCheckRecord {
158 pub Timestamp:u64,
160 pub ServiceName:String,
162 pub Status:HealthStatus,
164 pub ResponseTimeMs:Option<u64>,
166 pub ErrorMessage:Option<String>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct RecoveryAction {
173 pub Name:String,
175 pub ServiceName:String,
177 pub Trigger:RecoveryTrigger,
179 pub Action:RecoveryActionType,
181 pub MaxRetries:u32,
183 pub RetryCount:u32,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189pub enum RecoveryTrigger {
190 ConsecutiveFailures(u32),
192 ResponseTimeExceeds(u64),
194 ServiceUnresponsive,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub enum RecoveryActionType {
201 RestartService,
203 ResetConnection,
205 ClearCache,
207 ReloadConfiguration,
209 Escalate,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct HealthCheckConfig {
216 pub DefaultCheckInterval:u64,
218 pub HistoryRetention:usize,
220 pub ConsecutiveFailuresThreshold:u32,
222 pub ResponseTimeThresholdMs:u64,
224 pub EnableAutoRecovery:bool,
226 pub RecoveryTimeoutSec:u64,
228}
229
230impl Default for HealthCheckConfig {
231 fn default() -> Self {
232 Self {
233 DefaultCheckInterval:30,
234 HistoryRetention:100,
235 ConsecutiveFailuresThreshold:3,
236 ResponseTimeThresholdMs:5000,
237 EnableAutoRecovery:true,
238 RecoveryTimeoutSec:60,
239 }
240 }
241}
242
243impl HealthCheckManager {
244 pub fn new(config:Option<HealthCheckConfig>) -> Self {
246 Self {
247 ServiceHealth:Arc::new(RwLock::new(HashMap::new())),
248 HealthHistory:Arc::new(RwLock::new(Vec::new())),
249 RecoveryActions:Arc::new(RwLock::new(HashMap::new())),
250 config:config.unwrap_or_default(),
251 }
252 }
253
254 pub async fn RegisterService(&self, ServiceName:String, CheckLevel:HealthCheckLevel) -> Result<()> {
256 let mut HealthMap = self.ServiceHealth.write().await;
257
258 HealthMap.insert(
259 ServiceName.clone(),
260 ServiceHealth {
261 ServiceName:ServiceName.clone(),
262 Status:HealthStatus::Unknown,
263 LastCheck:0,
264 LastSuccess:None,
265 FailureCount:0,
266 ErrorMessage:None,
267 ResponseTimeMs:None,
268 CheckLevel:CheckLevel.clone(),
269 },
270 );
271
272 dev_log!(
273 "lifecycle",
274 "[HealthCheck] Registered service for monitoring: {} ({:?})",
275 ServiceName,
276 CheckLevel
277 );
278 Ok(())
279 }
280
281 pub async fn CheckService(&self, ServiceName:&str) -> Result<HealthStatus> {
283 let StartTime = Utility::CurrentTimestamp();
284
285 let CheckTimeout = tokio::time::Duration::from_secs(10);
287
288 let (status, ErrorMessage) = tokio::time::timeout(CheckTimeout, async {
289 match ServiceName {
290 "authentication" => self.CheckAuthenticationService().await,
291 "updates" => self.CheckUpdatesService().await,
292 "downloader" => self.CheckDownloaderService().await,
293 "indexing" => self.CheckIndexingService().await,
294 "grpc" => self.CheckgRPCService().await,
295 "connections" => self.CheckConnectionsService().await,
296 _ => {
297 dev_log!("lifecycle", "warn: [HealthCheck] Unknown service: {}", ServiceName);
298 return (HealthStatus::Unhealthy, Some(format!("Unknown service: {}", ServiceName)));
299 },
300 }
301 })
302 .await
303 .map_err(|_| {
304 dev_log!("lifecycle", "warn: [HealthCheck] Timeout checking service: {}", ServiceName);
305 (
306 HealthStatus::Unhealthy,
307 Some(format!("Health check timeout for service: {}", ServiceName)),
308 )
309 })?;
310
311 let ResponseTime = Utility::CurrentTimestamp() - StartTime;
312
313 self.UpdateServiceHealth(ServiceName, status.clone(), &ErrorMessage, ResponseTime)
315 .await?;
316
317 self.RecordHealthCheck(ServiceName, status.clone(), ResponseTime, &ErrorMessage)
319 .await;
320
321 if self.config.EnableAutoRecovery {
323 self.TriggerRecoveryIfNeeded(ServiceName).await;
324 }
325
326 self.HandleCriticalAlerts(ServiceName, &status).await;
328
329 Ok(status)
330 }
331
332 async fn CheckAuthenticationService(&self) -> (HealthStatus, Option<String>) {
334 dev_log!("lifecycle", "[HealthCheck] Checking authentication service health");
335 let start = std::time::Instant::now();
340
341 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
351
352 let elapsed = start.elapsed();
353
354 if elapsed.as_millis() > 1000 {
356 return (
357 HealthStatus::Degraded,
358 Some(format!(
359 "Authentication service response time too slow: {}ms",
360 elapsed.as_millis()
361 )),
362 );
363 }
364
365 dev_log!("lifecycle", "[HealthCheck] Authentication service healthy");
366 (HealthStatus::Healthy, None)
367 }
368
369 async fn CheckUpdatesService(&self) -> (HealthStatus, Option<String>) {
371 dev_log!("lifecycle", "[HealthCheck] Checking updates service health");
372 let start = std::time::Instant::now();
373
374 tokio::time::sleep(tokio::time::Duration::from_millis(30)).await;
384
385 let elapsed = start.elapsed();
386
387 if elapsed.as_millis() > 500 {
389 return (
390 HealthStatus::Degraded,
391 Some(format!("Updates service response time too slow: {}ms", elapsed.as_millis())),
392 );
393 }
394
395 dev_log!("lifecycle", "[HealthCheck] Updates service healthy");
396 (HealthStatus::Healthy, None)
397 }
398
399 async fn CheckDownloaderService(&self) -> (HealthStatus, Option<String>) {
401 dev_log!("lifecycle", "[HealthCheck] Checking downloader service health");
402 let start = std::time::Instant::now();
403
404 tokio::time::sleep(tokio::time::Duration::from_millis(40)).await;
415
416 let elapsed = start.elapsed();
417
418 if elapsed.as_millis() > 1000 {
420 return (
421 HealthStatus::Degraded,
422 Some(format!("Downloader service response time too slow: {}ms", elapsed.as_millis())),
423 );
424 }
425
426 dev_log!("lifecycle", "[HealthCheck] Downloader service healthy");
427 (HealthStatus::Healthy, None)
428 }
429
430 async fn CheckIndexingService(&self) -> (HealthStatus, Option<String>) {
432 dev_log!("lifecycle", "[HealthCheck] Checking indexing service health");
433 let start = std::time::Instant::now();
434
435 tokio::time::sleep(tokio::time::Duration::from_millis(60)).await;
446
447 let elapsed = start.elapsed();
448
449 if elapsed.as_millis() > 500 {
451 return (
452 HealthStatus::Degraded,
453 Some(format!("Indexing service response time too slow: {}ms", elapsed.as_millis())),
454 );
455 }
456
457 dev_log!("lifecycle", "[HealthCheck] Indexing service healthy");
458 (HealthStatus::Healthy, None)
459 }
460
461 async fn CheckgRPCService(&self) -> (HealthStatus, Option<String>) {
463 dev_log!("lifecycle", "[HealthCheck] Checking gRPC service health");
464 let start = std::time::Instant::now();
465
466 tokio::time::sleep(tokio::time::Duration::from_millis(20)).await;
477
478 let elapsed = start.elapsed();
479
480 if elapsed.as_millis() > 200 {
482 return (
483 HealthStatus::Degraded,
484 Some(format!("gRPC service response time too slow: {}ms", elapsed.as_millis())),
485 );
486 }
487
488 dev_log!("lifecycle", "[HealthCheck] gRPC service healthy");
489 (HealthStatus::Healthy, None)
490 }
491
492 async fn CheckConnectionsService(&self) -> (HealthStatus, Option<String>) {
494 dev_log!("lifecycle", "[HealthCheck] Checking connections service health");
495 let start = std::time::Instant::now();
496
497 tokio::time::sleep(tokio::time::Duration::from_millis(35)).await;
508
509 let elapsed = start.elapsed();
510
511 if elapsed.as_millis() > 300 {
513 return (
514 HealthStatus::Degraded,
515 Some(format!("Connections service response time too slow: {}ms", elapsed.as_millis())),
516 );
517 }
518
519 dev_log!("lifecycle", "[HealthCheck] Connections service healthy");
520 (HealthStatus::Healthy, None)
521 }
522
523 async fn UpdateServiceHealth(
525 &self,
526 ServiceName:&str,
527 status:HealthStatus,
528 ErrorMessage:&Option<String>,
529 ResponseTime:u64,
530 ) -> Result<()> {
531 let mut HealthMap = self.ServiceHealth.write().await;
532
533 if let Some(ServiceHealth) = HealthMap.get_mut(ServiceName) {
534 ServiceHealth.Status = status.clone();
535 ServiceHealth.LastCheck = Utility::CurrentTimestamp();
536 ServiceHealth.ResponseTimeMs = Some(ResponseTime);
537
538 match status {
539 HealthStatus::Healthy => {
540 ServiceHealth.LastSuccess = Some(Utility::CurrentTimestamp());
541 ServiceHealth.FailureCount = 0;
542 ServiceHealth.ErrorMessage = None;
543 },
544 HealthStatus::Degraded | HealthStatus::Unhealthy => {
545 ServiceHealth.FailureCount += 1;
546 ServiceHealth.ErrorMessage = ErrorMessage.clone();
547 },
548 HealthStatus::Unknown => {
549 },
551 }
552 } else {
553 return Err(AirError::Internal(format!("Service not registered: {}", ServiceName)));
554 }
555
556 dev_log!(
557 "lifecycle",
558 "[HealthCheck] Updated health for {}: {:?} ({}ms)",
559 ServiceName,
560 status,
561 ResponseTime
562 );
563 Ok(())
564 }
565
566 async fn RecordHealthCheck(
568 &self,
569 ServiceName:&str,
570 status:HealthStatus,
571 ResponseTime:u64,
572 ErrorMessage:&Option<String>,
573 ) {
574 let mut history = self.HealthHistory.write().await;
575
576 let record = HealthCheckRecord {
577 Timestamp:Utility::CurrentTimestamp(),
578 ServiceName:ServiceName.to_string(),
579 Status:status,
580 ResponseTimeMs:Some(ResponseTime),
581 ErrorMessage:ErrorMessage.clone(),
582 };
583
584 history.push(record);
585
586 if history.len() > self.config.HistoryRetention {
588 history.remove(0);
589 }
590 }
591
592 async fn TriggerRecoveryIfNeeded(&self, ServiceName:&str) {
594 let HealthMap = self.ServiceHealth.read().await;
595
596 if let Some(ServiceHealth) = HealthMap.get(ServiceName) {
597 if ServiceHealth.FailureCount >= self.config.ConsecutiveFailuresThreshold {
599 dev_log!(
600 "lifecycle",
601 "warn: [HealthCheck] Service {} has {} consecutive failures, triggering recovery",
602 ServiceName,
603 ServiceHealth.FailureCount
604 );
605
606 self.PerformRecoveryAction(ServiceName).await;
607 }
608
609 if let Some(ResponseTime) = ServiceHealth.ResponseTimeMs {
611 if ResponseTime > self.config.ResponseTimeThresholdMs {
612 dev_log!(
613 "lifecycle",
614 "warn: [HealthCheck] Service {} response time {}ms exceeds threshold {}ms",
615 ServiceName,
616 ResponseTime,
617 self.config.ResponseTimeThresholdMs
618 );
619
620 self.HandleResponseTimeRecovery(ServiceName, ResponseTime).await;
621 }
622 }
623 }
624 }
625
626 async fn HandleResponseTimeRecovery(&self, ServiceName:&str, ResponseTime:u64) {
628 dev_log!(
629 "lifecycle",
630 "[HealthCheck] Handling response time recovery for {}: {}ms",
631 ServiceName,
632 ResponseTime
633 );
634
635 match ServiceName {
636 "grpc" => {
637 dev_log!(
638 "lifecycle",
639 "warn: [HealthCheck] Response time recovery: Optimizing gRPC server for {}",
640 ServiceName
641 );
642 },
647 "connections" => {
648 dev_log!(
649 "lifecycle",
650 "warn: [HealthCheck] Response time recovery: Optimizing connections for {}",
651 ServiceName
652 );
653 },
658 _ => {
659 dev_log!(
660 "lifecycle",
661 "warn: [HealthCheck] Response time recovery: Generic optimization for {}",
662 ServiceName
663 );
664 },
665 }
666 }
667
668 async fn HandleCriticalAlerts(&self, ServiceName:&str, status:&HealthStatus) {
670 if *status == HealthStatus::Unhealthy {
671 dev_log!(
672 "lifecycle",
673 "warn: [HealthCheck] CRITICAL: Service {} is UNHEALTHY - immediate attention required",
674 ServiceName
675 );
676
677 }
683 }
684
685 async fn PerformRecoveryAction(&self, ServiceName:&str) {
687 dev_log!("lifecycle", "[HealthCheck] Performing recovery action for {}", ServiceName);
688 let RecoveryTimeout = tokio::time::Duration::from_secs(self.config.RecoveryTimeoutSec);
689
690 let result = tokio::time::timeout(RecoveryTimeout, async {
691 match ServiceName {
692 "authentication" => self.RestartAuthenticationService().await,
693 "updates" => self.RestartUpdatesService().await,
694 "downloader" => self.RestartDownloaderService().await,
695 "indexing" => self.RestartIndexingService().await,
696 "grpc" => self.RestartgRPCService().await,
697 "connections" => self.ResetConnectionsService().await,
698 _ => {
699 dev_log!(
700 "lifecycle",
701 "warn: [HealthCheck] No specific recovery action for {}",
702 ServiceName
703 );
704 Ok(())
705 },
706 }
707 })
708 .await;
709
710 match result {
711 Ok(Ok(())) => {
712 dev_log!(
713 "lifecycle",
714 "[HealthCheck] Recovery action completed successfully for {}",
715 ServiceName
716 );
717 },
718 Ok(Err(e)) => {
719 dev_log!(
720 "lifecycle",
721 "warn: [HealthCheck] Recovery action failed for {}: {:?}",
722 ServiceName,
723 e
724 );
725 },
726 Err(_) => {
727 dev_log!("lifecycle", "warn: [HealthCheck] Recovery action timed out for {}", ServiceName);
728 },
729 }
730 }
731
732 async fn RestartAuthenticationService(&self) -> Result<()> {
734 dev_log!("lifecycle", "warn: [HealthCheck] Recovery: Restarting authentication service"); Ok(())
736 }
737
738 async fn RestartUpdatesService(&self) -> Result<()> {
740 dev_log!("lifecycle", "warn: [HealthCheck] Recovery: Restarting updates service"); Ok(())
742 }
743
744 async fn RestartDownloaderService(&self) -> Result<()> {
746 dev_log!("lifecycle", "warn: [HealthCheck] Recovery: Restarting downloader service"); Ok(())
748 }
749
750 async fn RestartIndexingService(&self) -> Result<()> {
752 dev_log!("lifecycle", "warn: [HealthCheck] Recovery: Restarting indexing service"); Ok(())
754 }
755
756 async fn RestartgRPCService(&self) -> Result<()> {
758 dev_log!("lifecycle", "warn: [HealthCheck] Recovery: Restarting gRPC server"); Ok(())
760 }
761
762 async fn ResetConnectionsService(&self) -> Result<()> {
764 dev_log!("lifecycle", "warn: [HealthCheck] Recovery: Resetting connections service"); Ok(())
766 }
767
768 pub async fn GetOverallHealth(&self) -> HealthStatus {
770 let HealthMap = self.ServiceHealth.read().await;
771
772 let mut HealthyCount = 0;
773 let mut DegradedCount = 0;
774 let mut UnhealthyCount = 0;
775
776 for ServiceHealth in HealthMap.values() {
777 match ServiceHealth.Status {
778 HealthStatus::Healthy => HealthyCount += 1,
779 HealthStatus::Degraded => DegradedCount += 1,
780 HealthStatus::Unhealthy => UnhealthyCount += 1,
781 HealthStatus::Unknown => {},
782 }
783 }
784
785 if UnhealthyCount > 0 {
786 HealthStatus::Unhealthy
787 } else if DegradedCount > 0 {
788 HealthStatus::Degraded
789 } else if HealthyCount > 0 {
790 HealthStatus::Healthy
791 } else {
792 HealthStatus::Unknown
793 }
794 }
795
796 pub async fn GetServiceHealth(&self, service_name:&str) -> Option<ServiceHealth> {
798 let HealthMap = self.ServiceHealth.read().await;
799 HealthMap.get(service_name).cloned()
800 }
801
802 pub async fn GetHealthHistory(&self, service_name:Option<&str>, limit:Option<usize>) -> Vec<HealthCheckRecord> {
804 let History = self.HealthHistory.read().await;
805
806 let mut FilteredHistory:Vec<HealthCheckRecord> = if let Some(service) = service_name {
807 History.iter().filter(|Record| Record.ServiceName == service).cloned().collect()
808 } else {
809 History.clone()
810 };
811
812 FilteredHistory.reverse();
814
815 if let Some(limit) = limit {
817 FilteredHistory.truncate(limit);
818 }
819
820 FilteredHistory
821 }
822
823 pub async fn RegisterRecoveryAction(&self, action:RecoveryAction) -> Result<()> {
825 let mut actions = self.RecoveryActions.write().await;
826 actions.insert(action.Name.clone(), action);
827 Ok(())
828 }
829
830 pub async fn GetHealthStatistics(&self) -> HealthStatistics {
832 let HealthMap = self.ServiceHealth.read().await;
833 let history = self.HealthHistory.read().await;
834 let mut HealthyServices = 0;
836 let mut DegradedServices = 0;
837 let mut UnhealthyServices = 0;
838
839 for ServiceHealth in HealthMap.values() {
840 match ServiceHealth.Status {
841 HealthStatus::Healthy => HealthyServices += 1,
842 HealthStatus::Degraded => DegradedServices += 1,
843 HealthStatus::Unhealthy => UnhealthyServices += 1,
844 HealthStatus::Unknown => {},
845 }
846 }
847
848 let mut Statistics = HealthStatistics {
850 TotalServices:HealthMap.len(),
851 HealthyServices,
852 DegradedServices,
853 UnhealthyServices,
854 TotalChecks:history.len(),
855 AverageResponseTimeMs:0.0,
856 SuccessRate:0.0,
857 };
858
859 if !history.is_empty() {
861 let mut TotalResponseTime = 0;
862 let mut SuccessfulChecks = 0;
863
864 for Record in history.iter() {
865 if let Some(ResponseTime) = Record.ResponseTimeMs {
866 TotalResponseTime += ResponseTime;
867 }
868
869 if Record.Status == HealthStatus::Healthy {
870 SuccessfulChecks += 1;
871 }
872 }
873
874 Statistics.AverageResponseTimeMs = TotalResponseTime as f64 / history.len() as f64;
875 Statistics.SuccessRate = SuccessfulChecks as f64 / history.len() as f64 * 100.0;
876 }
877
878 Statistics
879 }
880}
881
882#[derive(Debug, Clone, Serialize, Deserialize)]
884pub struct HealthStatistics {
885 pub TotalServices:usize,
886 pub HealthyServices:usize,
887 pub DegradedServices:usize,
888 pub UnhealthyServices:usize,
889 pub TotalChecks:usize,
890 pub AverageResponseTimeMs:f64,
891 pub SuccessRate:f64,
892}
893
894impl HealthStatistics {
895 pub fn OverallHealthPercentage(&self) -> f64 {
897 if self.TotalServices == 0 {
898 return 0.0;
899 }
900
901 (self.HealthyServices as f64 / self.TotalServices as f64) * 100.0
902 }
903}
904
905#[derive(Debug, Clone, Serialize, Deserialize)]
907pub struct HealthCheckResponse {
908 pub OverallStatus:HealthStatus,
909 pub ServiceHealth:HashMap<String, ServiceHealth>,
910 pub Statistics:HealthStatistics,
911 pub PerformanceIndicators:PerformanceIndicators,
912 pub ResourceWarnings:Vec<ResourceWarning>,
913 pub Timestamp:u64,
914}
915
916impl HealthCheckResponse {
917 pub fn new(
919 OverallStatus:HealthStatus,
920 ServiceHealth:HashMap<String, ServiceHealth>,
921 Statistics:HealthStatistics,
922 ) -> Self {
923 Self {
924 OverallStatus,
925 ServiceHealth,
926 Statistics,
927 PerformanceIndicators:PerformanceIndicators::default(),
928 ResourceWarnings:Vec::new(),
929 Timestamp:Utility::CurrentTimestamp(),
930 }
931 }
932
933 pub fn with_performance_indicators(mut self, indicators:PerformanceIndicators) -> Self {
935 self.PerformanceIndicators = indicators;
936 self
937 }
938
939 pub fn with_resource_warnings(mut self, warnings:Vec<ResourceWarning>) -> Self {
941 self.ResourceWarnings = warnings;
942 self
943 }
944}
945
946#[derive(Debug, Clone, Serialize, Deserialize)]
948pub struct PerformanceIndicators {
949 pub ResponseTimeP99Ms:f64,
950 pub ResponseTimeP95Ms:f64,
951 pub RequestThroughputPerSec:f64,
952 pub ErrorRatePercent:f64,
953 pub DegradationLevel:DegradationLevel,
954 pub BottleneckService:Option<String>,
955}
956
957impl Default for PerformanceIndicators {
958 fn default() -> Self {
959 Self {
960 ResponseTimeP99Ms:0.0,
961 ResponseTimeP95Ms:0.0,
962 RequestThroughputPerSec:0.0,
963 ErrorRatePercent:0.0,
964 DegradationLevel:DegradationLevel::Optimal,
965 BottleneckService:None,
966 }
967 }
968}
969
970#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
972pub enum DegradationLevel {
973 Optimal,
974 Acceptable,
975 Degraded,
976 Critical,
977}
978
979#[derive(Debug, Clone, Serialize, Deserialize)]
981pub struct ResourceWarning {
982 pub WarningType:ResourceWarningType,
983 pub ServiceName:Option<String>,
984 pub CurrentValue:f64,
985 pub Threshold:f64,
986 pub Severity:WarningSeverity,
987 pub Timestamp:u64,
988}
989
990#[derive(Debug, Clone, Serialize, Deserialize)]
992pub enum ResourceWarningType {
993 HighMemoryUsage,
994 HighCPUUsage,
995 LowDiskSpace,
996 ConnectionPoolExhausted,
997 ThreadPoolExhausted,
998 HighLatency,
999 HighErrorRate,
1000 DatabaseConnectivityIssue,
1001}
1002
1003#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1005pub enum WarningSeverity {
1006 Low,
1007 Medium,
1008 High,
1009 Critical,
1010}