use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum HealthStatus {
Healthy,
Degraded,
Unhealthy,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ComponentHealth {
pub name: String,
pub status: HealthStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
#[derive(Clone)]
pub struct ServiceHealth {
service: String,
version: String,
components: Arc<RwLock<HashMap<String, ComponentHealth>>>,
}
impl ServiceHealth {
pub fn new(service: impl Into<String>, version: impl Into<String>) -> Self {
Self {
service: service.into(),
version: version.into(),
components: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn service(&self) -> &str {
&self.service
}
pub fn version(&self) -> &str {
&self.version
}
pub fn register(&self, name: impl Into<String>) {
let name = name.into();
let mut map = self.components.write();
map.insert(
name.clone(),
ComponentHealth {
name,
status: HealthStatus::Healthy,
message: None,
},
);
}
pub fn update(&self, name: impl Into<String>, status: HealthStatus, message: Option<String>) {
let name = name.into();
let mut map = self.components.write();
map.insert(
name.clone(),
ComponentHealth {
name,
status,
message,
},
);
}
pub fn is_healthy(&self) -> bool {
let map = self.components.read();
map.values().all(|c| c.status == HealthStatus::Healthy)
}
pub fn status(&self) -> HashMap<String, ComponentHealth> {
self.components.read().clone()
}
pub fn overall_status(&self) -> HealthStatus {
let map = self.components.read();
if map.is_empty() {
return HealthStatus::Healthy;
}
if map.values().any(|c| c.status == HealthStatus::Unhealthy) {
return HealthStatus::Unhealthy;
}
if map.values().any(|c| c.status == HealthStatus::Degraded) {
return HealthStatus::Degraded;
}
HealthStatus::Healthy
}
}