workhelix_cli_common/
doctor.rs

1//! Health check and diagnostics module.
2//!
3//! This module provides a framework for running health checks on CLI tools
4//! with tool-specific diagnostics.
5
6use crate::types::{DoctorCheck, RepoInfo};
7
8/// Trait for tools that support doctor health checks.
9///
10/// Implement this trait to provide tool-specific health checks.
11pub trait DoctorChecks {
12    /// Get the repository information for this tool.
13    fn repo_info() -> RepoInfo;
14
15    /// Get the current version of this tool.
16    fn current_version() -> &'static str;
17
18    /// Run tool-specific health checks.
19    ///
20    /// Return a vector of check results. Default implementation returns empty vector.
21    fn tool_checks(&self) -> Vec<DoctorCheck> {
22        Vec::new()
23    }
24}
25
26/// Run doctor command to check health and configuration.
27///
28/// Returns exit code: 0 if healthy, 1 if issues found.
29///
30/// # Type Parameters
31/// * `T` - A type that implements `DoctorChecks`
32pub fn run_doctor<T: DoctorChecks>(tool: &T) -> i32 {
33    let tool_name = T::repo_info().name;
34    println!("🏥 {tool_name} health check");
35    println!("{}", "=".repeat(tool_name.len() + 14));
36    println!();
37
38    let mut has_errors = false;
39    let has_warnings = false;
40
41    // Run tool-specific checks
42    let tool_checks = tool.tool_checks();
43    if !tool_checks.is_empty() {
44        println!("Configuration:");
45        for check in tool_checks {
46            if check.passed {
47                println!("  ✅ {}", check.name);
48            } else {
49                println!("  ❌ {}", check.name);
50                if let Some(msg) = check.message {
51                    println!("     {msg}");
52                }
53                has_errors = true;
54            }
55        }
56        println!();
57    }
58
59    // Summary
60    if has_errors {
61        println!("❌ Issues found - see above for details");
62        1
63    } else if has_warnings {
64        println!("⚠️  Warnings found");
65        0 // Warnings don't cause failure
66    } else {
67        println!("✨ Everything looks healthy!");
68        0
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    struct TestTool;
77
78    impl DoctorChecks for TestTool {
79        fn repo_info() -> RepoInfo {
80            RepoInfo::new("workhelix", "test-tool")
81        }
82
83        fn current_version() -> &'static str {
84            "1.0.0"
85        }
86
87        fn tool_checks(&self) -> Vec<DoctorCheck> {
88            vec![
89                DoctorCheck::pass("Test check 1"),
90                DoctorCheck::fail("Test check 2", "This is a failure"),
91            ]
92        }
93    }
94
95    #[test]
96    fn test_run_doctor() {
97        let tool = TestTool;
98        let exit_code = run_doctor(&tool);
99        // Should return 1 because we have a failing check
100        assert_eq!(exit_code, 1);
101    }
102}