decdn_cli/commands/doctor/
mod.rs1use std::path::Path;
6
7use serde::Serialize;
8
9mod chain;
10mod config;
11mod disk;
12mod live;
13mod origin;
14mod ports;
15mod report;
16mod state;
17
18#[derive(Debug, Serialize)]
21pub struct Finding {
22 pub group: &'static str,
24 pub id: &'static str,
26 pub severity: Severity,
28 pub title: String,
30 pub detail: Option<String>,
32 pub remediation: Option<String>,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
39#[serde(rename_all = "lowercase")]
40pub enum Severity {
41 Pass,
43 Warn,
45 Fail,
47}
48
49#[derive(Debug, Default, Serialize)]
51pub struct Report {
52 pub findings: Vec<Finding>,
54}
55
56impl Report {
57 pub fn push(&mut self, finding: Finding) {
59 self.findings.push(finding);
60 }
61
62 pub fn counts(&self) -> (usize, usize, usize) {
64 self.findings
65 .iter()
66 .fold((0, 0, 0), |(p, w, f), finding| match finding.severity {
67 Severity::Pass => (p + 1, w, f),
68 Severity::Warn => (p, w + 1, f),
69 Severity::Fail => (p, w, f + 1),
70 })
71 }
72
73 pub fn has_fail(&self) -> bool {
75 self.findings.iter().any(|x| x.severity == Severity::Fail)
76 }
77
78 pub fn has_warn(&self) -> bool {
80 self.findings.iter().any(|x| x.severity == Severity::Warn)
81 }
82}
83
84#[derive(Debug)]
88pub struct DoctorFailed;
89
90impl std::fmt::Display for DoctorFailed {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 write!(f, "doctor found failing checks")
93 }
94}
95
96impl std::error::Error for DoctorFailed {}
97
98pub async fn run(
100 args: &decdn_common::cli::DoctorArgs,
101 global_config: Option<&Path>,
102) -> anyhow::Result<()> {
103 let mut report = Report::default();
104 let resolved = config::check_config(&mut report, args, global_config);
105 if let Some(resolved) = &resolved {
107 let live = if args.offline {
108 live::LiveInfo {
109 daemon_running: false,
110 cache_bytes: None,
111 }
112 } else {
113 live::probe_live(&mut report, resolved, args, global_config).await
114 };
115
116 disk::check_disk(&mut report, resolved, live.cache_bytes);
117 state::check_state(&mut report, resolved, live.daemon_running);
118 if !args.offline {
119 chain::check_chain(&mut report, resolved, args.timeout_ms).await;
120 origin::check_origins(&mut report, resolved, args.timeout_ms).await;
121 ports::check_ports(&mut report, resolved, live.daemon_running);
122 }
123 }
124
125 let mut stdout = std::io::stdout().lock();
126 report::render(&mut stdout, &report, args.json, args.strict)
127 .map_err(|e| anyhow::anyhow!("failed to write doctor report: {e}"))?;
128
129 let fail = report.has_fail() || (args.strict && report.has_warn());
130 if fail {
131 return Err(DoctorFailed.into());
132 }
133 Ok(())
134}
135
136#[cfg(test)]
137#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] mod tests;