use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum HealthStatus {
Healthy,
Degraded,
Unhealthy,
Unknown,
NotRunning,
}
impl HealthStatus {
pub fn symbol(&self) -> &str {
match self {
HealthStatus::Healthy => "✓",
HealthStatus::Degraded => "⚠",
HealthStatus::Unhealthy => "✗",
HealthStatus::Unknown => "?",
HealthStatus::NotRunning => "✗",
}
}
pub fn color(&self) -> &str {
match self {
HealthStatus::Healthy => "green",
HealthStatus::Degraded => "yellow",
HealthStatus::Unhealthy => "red",
HealthStatus::Unknown => "gray",
HealthStatus::NotRunning => "red",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthResult {
pub status: HealthStatus,
pub response_time_ms: u64,
pub last_checked: DateTime<Utc>,
pub details: Option<String>,
pub suggestion: Option<String>,
}
impl HealthResult {
pub fn healthy(response_time_ms: u64) -> Self {
Self {
status: HealthStatus::Healthy,
response_time_ms,
last_checked: Utc::now(),
details: None,
suggestion: None,
}
}
pub fn unhealthy(reason: String, suggestion: Option<String>) -> Self {
Self {
status: HealthStatus::Unhealthy,
response_time_ms: 0,
last_checked: Utc::now(),
details: Some(reason),
suggestion,
}
}
pub fn not_running(suggestion: Option<String>) -> Self {
Self {
status: HealthStatus::NotRunning,
response_time_ms: 0,
last_checked: Utc::now(),
details: Some("Process not found".to_string()),
suggestion,
}
}
pub fn degraded(response_time_ms: u64, reason: String) -> Self {
Self {
status: HealthStatus::Degraded,
response_time_ms,
last_checked: Utc::now(),
details: Some(reason),
suggestion: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthCheckConfig {
#[serde(flatten)]
pub check_type: HealthCheckType,
#[serde(default = "default_interval", skip_serializing_if = "is_default_interval")]
pub interval: Duration,
#[serde(default = "default_timeout", skip_serializing_if = "is_default_timeout")]
pub timeout: Duration,
#[serde(default = "default_retry_count", skip_serializing_if = "is_default_retry_count")]
pub retry_count: u32,
}
fn default_interval() -> Duration {
Duration::from_secs(30)
}
fn is_default_interval(d: &Duration) -> bool {
*d == Duration::from_secs(30)
}
fn default_timeout() -> Duration {
Duration::from_secs(3)
}
fn is_default_timeout(d: &Duration) -> bool {
*d == Duration::from_secs(3)
}
fn default_retry_count() -> u32 {
2
}
fn is_default_retry_count(c: &u32) -> bool {
*c == 2
}
impl Default for HealthCheckConfig {
fn default() -> Self {
Self {
check_type: HealthCheckType::Port,
interval: Duration::from_secs(30),
timeout: Duration::from_secs(3),
retry_count: 2,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum HealthCheckType {
Port,
Process,
Http {
path: String,
#[serde(skip_serializing_if = "Option::is_none")]
expected_status: Option<u16>,
},
Postgres {
#[serde(skip_serializing_if = "Option::is_none")]
database: Option<String>,
},
Redis,
#[serde(rename = "mysql")]
MySQL {
#[serde(skip_serializing_if = "Option::is_none")]
database: Option<String>,
},
#[serde(rename = "mongodb")]
MongoDB {
#[serde(skip_serializing_if = "Option::is_none")]
database: Option<String>,
},
Custom {
script: String,
},
}