Skip to main content

agentshield/
doctor.rs

1use std::path::{Path, PathBuf};
2
3use serde::Serialize;
4
5use crate::adapter::all_adapters;
6use crate::config::Config;
7use crate::error::Result;
8
9#[derive(Debug, Clone, Serialize)]
10pub struct EnabledFeatures {
11    pub python: bool,
12    pub typescript: bool,
13    pub runtime: bool,
14}
15
16#[derive(Debug, Clone, Serialize)]
17pub struct DoctorReport {
18    pub version: String,
19    pub target: PathBuf,
20    pub config_path: PathBuf,
21    pub config_found: bool,
22    pub fail_on: String,
23    pub ignore_tests: bool,
24    pub include_patterns: Vec<String>,
25    pub exclude_patterns: Vec<String>,
26    pub enabled_features: EnabledFeatures,
27    pub detected_adapters: Vec<String>,
28    pub available_adapters: Vec<String>,
29    pub runtime_wrap_available: bool,
30}
31
32pub fn run_doctor(
33    target: &Path,
34    config_path: Option<PathBuf>,
35    ignore_tests_override: bool,
36) -> Result<DoctorReport> {
37    let effective_config_path = config_path.unwrap_or_else(|| target.join(".agentshield.toml"));
38    let config_found = effective_config_path.exists();
39    let config = Config::load(&effective_config_path)?;
40    let ignore_tests = ignore_tests_override || config.scan.ignore_tests;
41
42    let adapters = all_adapters();
43    let available_adapters = adapters
44        .iter()
45        .map(|adapter| adapter.framework().to_string())
46        .collect();
47    let detected_adapters = adapters
48        .iter()
49        .filter(|adapter| adapter.detect(target))
50        .map(|adapter| adapter.framework().to_string())
51        .collect();
52
53    Ok(DoctorReport {
54        version: env!("CARGO_PKG_VERSION").to_string(),
55        target: target.to_path_buf(),
56        config_path: effective_config_path,
57        config_found,
58        fail_on: config.policy.fail_on.to_string(),
59        ignore_tests,
60        include_patterns: config.scan.include.clone(),
61        exclude_patterns: config.scan.exclude.clone(),
62        enabled_features: EnabledFeatures {
63            python: cfg!(feature = "python"),
64            typescript: cfg!(feature = "typescript"),
65            runtime: cfg!(feature = "runtime"),
66        },
67        detected_adapters,
68        available_adapters,
69        runtime_wrap_available: cfg!(feature = "runtime"),
70    })
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn reports_missing_config_and_enabled_features() {
79        let tmp = tempfile::TempDir::new().unwrap();
80
81        let report = run_doctor(tmp.path(), None, false).unwrap();
82
83        assert_eq!(report.version, env!("CARGO_PKG_VERSION"));
84        assert_eq!(report.target, tmp.path());
85        assert_eq!(report.config_path, tmp.path().join(".agentshield.toml"));
86        assert!(!report.config_found);
87        assert_eq!(report.fail_on, "high");
88        assert!(!report.ignore_tests);
89        assert!(report.include_patterns.is_empty());
90        assert!(report.exclude_patterns.is_empty());
91        assert_eq!(report.enabled_features.python, cfg!(feature = "python"));
92        assert_eq!(
93            report.enabled_features.typescript,
94            cfg!(feature = "typescript")
95        );
96        assert_eq!(report.enabled_features.runtime, cfg!(feature = "runtime"));
97        assert_eq!(report.runtime_wrap_available, cfg!(feature = "runtime"));
98        assert!(!report.available_adapters.is_empty());
99        assert!(report.detected_adapters.is_empty());
100    }
101
102    #[test]
103    fn ignore_tests_cli_override_enables_effective_setting() {
104        let tmp = tempfile::TempDir::new().unwrap();
105
106        let report = run_doctor(tmp.path(), None, true).unwrap();
107
108        assert!(report.ignore_tests);
109    }
110}