waterui_cli/toolchain/
doctor.rs

1//! Toolchain diagnostics for the `water doctor` command.
2
3use crate::{
4    android::toolchain::{AndroidNdk, AndroidSdk, Java},
5    apple::toolchain::{AppleSdk, Xcode},
6    toolchain::Toolchain,
7};
8
9/// Status of a toolchain check.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum CheckStatus {
12    /// Toolchain is available and working.
13    Ok,
14    /// Toolchain is missing or misconfigured.
15    Missing,
16    /// Toolchain check was skipped (e.g., not applicable on this platform).
17    Skipped,
18}
19
20/// A single item in the doctor report.
21#[derive(Debug)]
22pub struct DoctorItem {
23    /// Name of the toolchain or component.
24    pub name: &'static str,
25    /// Status of the check.
26    pub status: CheckStatus,
27    /// Optional message with details or suggestions.
28    pub message: Option<String>,
29    /// Whether the issue can be fixed automatically.
30    pub fixable: bool,
31}
32
33impl DoctorItem {
34    const fn ok(name: &'static str) -> Self {
35        Self {
36            name,
37            status: CheckStatus::Ok,
38            message: None,
39            fixable: false,
40        }
41    }
42
43    fn missing(name: &'static str, message: impl Into<String>) -> Self {
44        Self {
45            name,
46            status: CheckStatus::Missing,
47            message: Some(message.into()),
48            fixable: false,
49        }
50    }
51
52    const fn skipped(name: &'static str) -> Self {
53        Self {
54            name,
55            status: CheckStatus::Skipped,
56            message: None,
57            fixable: false,
58        }
59    }
60}
61
62/// Run diagnostics on all toolchains and return a report.
63pub async fn doctor() -> Vec<DoctorItem> {
64    let mut items = Vec::new();
65
66    // Check Xcode (macOS only)
67    if cfg!(target_os = "macos") {
68        match Xcode.check().await {
69            Ok(()) => items.push(DoctorItem::ok("Xcode")),
70            Err(e) => items.push(DoctorItem::missing("Xcode", e.to_string())),
71        }
72
73        // Check iOS SDK
74        match AppleSdk::Ios.check().await {
75            Ok(()) => items.push(DoctorItem::ok("iOS SDK")),
76            Err(e) => items.push(DoctorItem::missing("iOS SDK", e.to_string())),
77        }
78
79        // Check macOS SDK
80        match AppleSdk::Macos.check().await {
81            Ok(()) => items.push(DoctorItem::ok("macOS SDK")),
82            Err(e) => items.push(DoctorItem::missing("macOS SDK", e.to_string())),
83        }
84    } else {
85        items.push(DoctorItem::skipped("Xcode"));
86        items.push(DoctorItem::skipped("iOS SDK"));
87        items.push(DoctorItem::skipped("macOS SDK"));
88    }
89
90    // Check Android SDK
91    match AndroidSdk.check().await {
92        Ok(()) => items.push(DoctorItem::ok("Android SDK")),
93        Err(e) => items.push(DoctorItem::missing("Android SDK", e.to_string())),
94    }
95
96    // Check Android NDK
97    match AndroidNdk.check().await {
98        Ok(()) => items.push(DoctorItem::ok("Android NDK")),
99        Err(e) => items.push(DoctorItem::missing("Android NDK", e.to_string())),
100    }
101
102    // Check Java
103    match Java::detect_path().await {
104        Some(_) => items.push(DoctorItem::ok("Java")),
105        None => items.push(DoctorItem::missing(
106            "Java",
107            "Install JDK or set JAVA_HOME. Android Studio includes a bundled JDK.",
108        )),
109    }
110
111    items
112}