use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CheckStatus {
Ok,
Warn,
Fail,
}
impl CheckStatus {
fn rank(self) -> u8 {
match self {
CheckStatus::Ok => 0,
CheckStatus::Warn => 1,
CheckStatus::Fail => 2,
}
}
pub fn worst(self, other: CheckStatus) -> CheckStatus {
if other.rank() > self.rank() {
other
} else {
self
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DoctorCheck {
pub name: String,
pub status: CheckStatus,
pub message: String,
}
impl DoctorCheck {
pub fn new(name: impl Into<String>, status: CheckStatus, message: impl Into<String>) -> Self {
Self {
name: name.into(),
status,
message: message.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DoctorReport {
pub overall: CheckStatus,
pub checks: Vec<DoctorCheck>,
pub generated_at: chrono::DateTime<chrono::Utc>,
}
impl DoctorReport {
pub fn from_checks(checks: Vec<DoctorCheck>) -> Self {
let overall = checks
.iter()
.fold(CheckStatus::Ok, |acc, c| acc.worst(c.status));
Self {
overall,
checks,
generated_at: chrono::Utc::now(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn worst_picks_the_most_severe() {
assert_eq!(CheckStatus::Ok.worst(CheckStatus::Warn), CheckStatus::Warn);
assert_eq!(CheckStatus::Warn.worst(CheckStatus::Ok), CheckStatus::Warn);
assert_eq!(
CheckStatus::Warn.worst(CheckStatus::Fail),
CheckStatus::Fail
);
assert_eq!(CheckStatus::Fail.worst(CheckStatus::Ok), CheckStatus::Fail);
assert_eq!(CheckStatus::Ok.worst(CheckStatus::Ok), CheckStatus::Ok);
}
#[test]
fn status_serde_round_trips() {
let json = serde_json::to_string(&CheckStatus::Warn).unwrap();
assert_eq!(json, "\"warn\"");
let back: CheckStatus = serde_json::from_str(&json).unwrap();
assert_eq!(back, CheckStatus::Warn);
}
#[test]
fn report_overall_is_worst_check() {
let ok = DoctorReport::from_checks(vec![
DoctorCheck::new("a", CheckStatus::Ok, "fine"),
DoctorCheck::new("b", CheckStatus::Ok, "fine"),
]);
assert_eq!(ok.overall, CheckStatus::Ok);
let mixed = DoctorReport::from_checks(vec![
DoctorCheck::new("a", CheckStatus::Ok, "fine"),
DoctorCheck::new("b", CheckStatus::Warn, "meh"),
DoctorCheck::new("c", CheckStatus::Fail, "broken"),
]);
assert_eq!(mixed.overall, CheckStatus::Fail);
let empty = DoctorReport::from_checks(vec![]);
assert_eq!(empty.overall, CheckStatus::Ok);
}
#[test]
fn report_serde_round_trips() {
let report = DoctorReport::from_checks(vec![DoctorCheck::new(
"instructions",
CheckStatus::Ok,
"last-instructions.md present",
)]);
let json = serde_json::to_string(&report).unwrap();
let back: DoctorReport = serde_json::from_str(&json).unwrap();
assert_eq!(back.overall, CheckStatus::Ok);
assert_eq!(back.checks.len(), 1);
assert_eq!(back.checks[0].name, "instructions");
}
}