use async_trait::async_trait;
use serde::{Deserialize, Serialize};
#[async_trait(?Send)]
pub trait HealthChecker {
async fn check_health(&self, url: &str) -> Result<bool, String>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JanitorConfig {
#[serde(default = "default_request_timeout_ms")]
pub request_timeout_ms: u64,
#[serde(default = "default_host_down_revoke_score")]
pub host_down_revoke_score: u32,
}
fn default_request_timeout_ms() -> u64 {
10_000
}
fn default_host_down_revoke_score() -> u32 {
3
}
impl Default for JanitorConfig {
fn default() -> Self {
Self {
request_timeout_ms: default_request_timeout_ms(),
host_down_revoke_score: default_host_down_revoke_score(),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct JanitorResult {
pub ship_records_checked: u32,
pub slap_records_checked: u32,
pub records_evicted: u32,
pub domains_healthy: u32,
pub domains_unhealthy: u32,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub domain_results: Vec<DomainHealthResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DomainHealthResult {
pub domain: String,
pub healthy: bool,
pub record_count: u32,
pub records_evicted: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn janitor_config_defaults() {
let config = JanitorConfig::default();
assert_eq!(config.request_timeout_ms, 10_000);
assert_eq!(config.host_down_revoke_score, 3);
}
#[test]
fn janitor_config_serde_roundtrip() {
let config = JanitorConfig {
request_timeout_ms: 5000,
host_down_revoke_score: 5,
};
let json = serde_json::to_string(&config).unwrap();
let back: JanitorConfig = serde_json::from_str(&json).unwrap();
assert_eq!(back.request_timeout_ms, 5000);
assert_eq!(back.host_down_revoke_score, 5);
}
#[test]
fn janitor_result_defaults() {
let result = JanitorResult::default();
assert_eq!(result.ship_records_checked, 0);
assert_eq!(result.slap_records_checked, 0);
assert_eq!(result.records_evicted, 0);
assert_eq!(result.domains_healthy, 0);
assert_eq!(result.domains_unhealthy, 0);
assert!(result.domain_results.is_empty());
}
#[test]
fn janitor_result_serde_roundtrip() {
let result = JanitorResult {
ship_records_checked: 10,
slap_records_checked: 5,
records_evicted: 2,
domains_healthy: 3,
domains_unhealthy: 1,
domain_results: vec![
DomainHealthResult {
domain: "https://example.com".to_string(),
healthy: true,
record_count: 5,
records_evicted: 0,
},
DomainHealthResult {
domain: "https://down.example.com".to_string(),
healthy: false,
record_count: 2,
records_evicted: 2,
},
],
};
let json = serde_json::to_string(&result).unwrap();
let back: JanitorResult = serde_json::from_str(&json).unwrap();
assert_eq!(back.ship_records_checked, 10);
assert_eq!(back.domain_results.len(), 2);
assert!(back.domain_results[0].healthy);
assert!(!back.domain_results[1].healthy);
}
#[test]
fn domain_health_result_serde() {
let r = DomainHealthResult {
domain: "https://test.com".to_string(),
healthy: true,
record_count: 3,
records_evicted: 0,
};
let json = serde_json::to_string(&r).unwrap();
assert!(json.contains("test.com"));
let back: DomainHealthResult = serde_json::from_str(&json).unwrap();
assert_eq!(back.domain, "https://test.com");
assert!(back.healthy);
}
#[allow(dead_code)]
fn assert_object_safe(_: &dyn HealthChecker) {}
}