use std::path::Path;
use serde::Serialize;
mod chain;
mod config;
mod disk;
mod live;
mod origin;
mod ports;
mod report;
mod state;
#[derive(Debug, Serialize)]
pub struct Finding {
pub group: &'static str,
pub id: &'static str,
pub severity: Severity,
pub title: String,
pub detail: Option<String>,
pub remediation: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Pass,
Warn,
Fail,
}
#[derive(Debug, Default, Serialize)]
pub struct Report {
pub findings: Vec<Finding>,
}
impl Report {
pub fn push(&mut self, finding: Finding) {
self.findings.push(finding);
}
pub fn counts(&self) -> (usize, usize, usize) {
self.findings
.iter()
.fold((0, 0, 0), |(p, w, f), finding| match finding.severity {
Severity::Pass => (p + 1, w, f),
Severity::Warn => (p, w + 1, f),
Severity::Fail => (p, w, f + 1),
})
}
pub fn has_fail(&self) -> bool {
self.findings.iter().any(|x| x.severity == Severity::Fail)
}
pub fn has_warn(&self) -> bool {
self.findings.iter().any(|x| x.severity == Severity::Warn)
}
}
#[derive(Debug)]
pub struct DoctorFailed;
impl std::fmt::Display for DoctorFailed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "doctor found failing checks")
}
}
impl std::error::Error for DoctorFailed {}
pub async fn run(
args: &decdn_common::cli::DoctorArgs,
global_config: Option<&Path>,
) -> anyhow::Result<()> {
let mut report = Report::default();
let resolved = config::check_config(&mut report, args, global_config);
if let Some(resolved) = &resolved {
let live = if args.offline {
live::LiveInfo {
daemon_running: false,
cache_bytes: None,
}
} else {
live::probe_live(&mut report, resolved, args, global_config).await
};
disk::check_disk(&mut report, resolved, live.cache_bytes);
state::check_state(&mut report, resolved, live.daemon_running);
if !args.offline {
chain::check_chain(&mut report, resolved, args.timeout_ms).await;
origin::check_origins(&mut report, resolved, args.timeout_ms).await;
ports::check_ports(&mut report, resolved, live.daemon_running);
}
}
let mut stdout = std::io::stdout().lock();
report::render(&mut stdout, &report, args.json, args.strict)
.map_err(|e| anyhow::anyhow!("failed to write doctor report: {e}"))?;
let fail = report.has_fail() || (args.strict && report.has_warn());
if fail {
return Err(DoctorFailed.into());
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] mod tests;