mihomo-rs 2.2.0

A Rust SDK and CLI tool for mihomo proxy management with service lifecycle management, configuration handling, and real-time monitoring
Documentation
use crate::cli::{print_info, print_table, DoctorAction};
use crate::doctor::{
    explain_check, fix_doctor, list_checks, run_doctor, DoctorRunOptions, DoctorStatus,
};

pub async fn handle_doctor(action: DoctorAction) -> anyhow::Result<i32> {
    match action {
        DoctorAction::Run { only, json } => {
            let report = run_doctor(DoctorRunOptions { only }).await;
            if json {
                println!("{}", serde_json::to_string_pretty(&report)?);
            } else {
                print_doctor_report(&report);
            }

            return Ok(if report.has_failures() { 1 } else { 0 });
        }
        DoctorAction::List => {
            let rows = list_checks()
                .iter()
                .map(|check| {
                    vec![
                        check.id.to_string(),
                        check.category.to_string(),
                        if check.fixable {
                            "yes".to_string()
                        } else {
                            "no".to_string()
                        },
                        if check.default_enabled {
                            "yes".to_string()
                        } else {
                            "no".to_string()
                        },
                        check.summary.to_string(),
                    ]
                })
                .collect();
            print_table(&["ID", "Category", "Fixable", "Default", "Summary"], rows);
        }
        DoctorAction::Fix { only, json } => {
            let report = fix_doctor(DoctorRunOptions { only }).await?;
            if json {
                println!("{}", serde_json::to_string_pretty(&report)?);
            } else if report.fixes.is_empty() {
                print_info("No safe doctor fixes matched the filter");
            } else {
                let rows = report
                    .fixes
                    .iter()
                    .map(|fix| vec![fix.id.clone(), fix.summary.clone()])
                    .collect();
                print_table(&["Check", "Applied Fix"], rows);
            }
        }
        DoctorAction::Explain { check_id } => {
            let info = explain_check(&check_id)?;
            print_info(info.summary);
            println!("id: {}", info.id);
            println!("category: {}", info.category);
            println!("fixable: {}", if info.fixable { "yes" } else { "no" });
            println!(
                "default: {}",
                if info.default_enabled { "yes" } else { "no" }
            );
            println!("why: {}", info.why);
            println!("fail means: {}", info.fail_means);
            println!("hint: {}", info.hint);
        }
    }

    Ok(0)
}

fn print_doctor_report(report: &crate::doctor::DoctorReport) {
    let rows: Vec<Vec<String>> = report
        .checks
        .iter()
        .map(|check| {
            vec![
                check.status.as_str().to_string(),
                check.id.clone(),
                check.summary.clone(),
                check.hint.clone().unwrap_or_default(),
            ]
        })
        .collect();

    if rows.is_empty() {
        print_info("No doctor checks matched the filter");
        return;
    }

    print_table(&["Status", "Check", "Summary", "Hint"], rows);
    let summary = format!(
        "doctor: {} pass, {} warn, {} fail, {} skip",
        report.count_by_status(DoctorStatus::Pass),
        report.count_by_status(DoctorStatus::Warn),
        report.count_by_status(DoctorStatus::Fail),
        report.count_by_status(DoctorStatus::Skip),
    );

    if report.has_failures() {
        eprintln!("{}", summary);
    } else {
        println!("{}", summary);
    }
}