Skip to main content

ralph/commands/doctor/
types.rs

1//! Types for doctor checks and reports.
2//!
3//! Responsibilities:
4//! - Define severity levels, check results, and report structures
5//! - Provide factory methods for creating check results
6//!
7//! Not handled here:
8//! - Actual check implementations (see submodules)
9//! - Output formatting (see output.rs)
10//!
11//! Invariants/assumptions:
12//! - CheckResult factories are pure functions with no side effects
13//! - DoctorReport maintains consistent summary statistics
14
15use serde::Serialize;
16
17/// Severity level for a doctor check.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
19#[serde(rename_all = "PascalCase")]
20pub enum CheckSeverity {
21    /// Check passed successfully.
22    Success,
23    /// Non-critical issue, operation can continue.
24    Warning,
25    /// Critical issue, operation should not proceed.
26    Error,
27}
28
29/// A single check result.
30#[derive(Debug, Clone, Serialize)]
31pub struct CheckResult {
32    /// Category of the check (git, queue, runner, project, lock).
33    pub category: String,
34    /// Specific check name (e.g., "git_binary", "queue_valid").
35    pub check: String,
36    /// Severity level of the result.
37    pub severity: CheckSeverity,
38    /// Human-readable message describing the result.
39    pub message: String,
40    /// Whether a fix is available for this issue.
41    pub fix_available: bool,
42    /// Whether a fix was applied (None if not attempted, Some(true/false) if attempted).
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub fix_applied: Option<bool>,
45    /// Suggested fix or action for the user.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub suggested_fix: Option<String>,
48}
49
50impl CheckResult {
51    /// Create a successful check result.
52    pub fn success(category: &str, check: &str, message: &str) -> Self {
53        Self {
54            category: category.to_string(),
55            check: check.to_string(),
56            severity: CheckSeverity::Success,
57            message: message.to_string(),
58            fix_available: false,
59            fix_applied: None,
60            suggested_fix: None,
61        }
62    }
63
64    /// Create a warning check result.
65    pub fn warning(
66        category: &str,
67        check: &str,
68        message: &str,
69        fix_available: bool,
70        suggested_fix: Option<&str>,
71    ) -> Self {
72        Self {
73            category: category.to_string(),
74            check: check.to_string(),
75            severity: CheckSeverity::Warning,
76            message: message.to_string(),
77            fix_available,
78            fix_applied: None,
79            suggested_fix: suggested_fix.map(|s| s.to_string()),
80        }
81    }
82
83    /// Create an error check result.
84    pub fn error(
85        category: &str,
86        check: &str,
87        message: &str,
88        fix_available: bool,
89        suggested_fix: Option<&str>,
90    ) -> Self {
91        Self {
92            category: category.to_string(),
93            check: check.to_string(),
94            severity: CheckSeverity::Error,
95            message: message.to_string(),
96            fix_available,
97            fix_applied: None,
98            suggested_fix: suggested_fix.map(|s| s.to_string()),
99        }
100    }
101
102    /// Mark that a fix was applied to this check.
103    pub fn with_fix_applied(mut self, applied: bool) -> Self {
104        self.fix_applied = Some(applied);
105        self
106    }
107}
108
109/// Summary of all checks.
110#[derive(Debug, Clone, Serialize)]
111pub struct Summary {
112    /// Total number of checks performed.
113    pub total: usize,
114    /// Number of successful checks.
115    pub passed: usize,
116    /// Number of warnings.
117    pub warnings: usize,
118    /// Number of errors.
119    pub errors: usize,
120    /// Number of fixes applied.
121    pub fixes_applied: usize,
122    /// Number of fixes that failed.
123    pub fixes_failed: usize,
124}
125
126/// Full doctor report (for JSON output).
127#[derive(Debug, Clone, Serialize)]
128pub struct DoctorReport {
129    /// Overall success status (true if no errors).
130    pub success: bool,
131    /// Individual check results.
132    pub checks: Vec<CheckResult>,
133    /// Summary statistics.
134    pub summary: Summary,
135}
136
137impl DoctorReport {
138    /// Create a new empty report.
139    pub fn new() -> Self {
140        Self {
141            success: true,
142            checks: Vec::new(),
143            summary: Summary {
144                total: 0,
145                passed: 0,
146                warnings: 0,
147                errors: 0,
148                fixes_applied: 0,
149                fixes_failed: 0,
150            },
151        }
152    }
153
154    /// Add a check result to the report.
155    pub fn add(&mut self, result: CheckResult) {
156        self.summary.total += 1;
157        match result.severity {
158            CheckSeverity::Success => self.summary.passed += 1,
159            CheckSeverity::Warning => self.summary.warnings += 1,
160            CheckSeverity::Error => {
161                self.summary.errors += 1;
162                self.success = false;
163            }
164        }
165        if result.fix_applied == Some(true) {
166            self.summary.fixes_applied += 1;
167        } else if result.fix_applied == Some(false) {
168            self.summary.fixes_failed += 1;
169        }
170        self.checks.push(result);
171    }
172}
173
174impl Default for DoctorReport {
175    fn default() -> Self {
176        Self::new()
177    }
178}