Skip to main content

harn_cli/package/manifest/
check_config.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use serde::Deserialize;
5
6/// Severity override for preflight diagnostics. `error` (default) fails
7/// `harn check`; `warning` reports but does not fail; `off` suppresses
8/// entirely. Accepted via `[check].preflight_severity` in harn.toml so
9/// repos with hosts that do not expose every capability statically can
10/// keep the checker running on genuine type errors.
11#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
12pub enum PreflightSeverity {
13    #[default]
14    Error,
15    Warning,
16    Off,
17}
18
19impl PreflightSeverity {
20    pub fn from_opt(raw: Option<&str>) -> Self {
21        match raw.map(|s| s.to_ascii_lowercase()) {
22            Some(v) if v == "warning" || v == "warn" => Self::Warning,
23            Some(v) if v == "off" || v == "allow" || v == "silent" => Self::Off,
24            _ => Self::Error,
25        }
26    }
27}
28
29#[derive(Debug, Default, Clone, Deserialize)]
30pub struct CheckConfig {
31    #[serde(default)]
32    pub strict: bool,
33    #[serde(default)]
34    pub strict_types: bool,
35    /// Explicit operator declaration that these sources are loaded only by a
36    /// trusted Rust host-dispatch boundary.
37    #[serde(default)]
38    pub trusted_host_dispatch: bool,
39    #[serde(default)]
40    pub disable_rules: Vec<String>,
41    #[serde(default)]
42    pub host_capabilities: HashMap<String, Vec<String>>,
43    #[serde(default, alias = "host_capabilities_file")]
44    pub host_capabilities_path: Option<String>,
45    #[serde(default)]
46    pub bundle_root: Option<String>,
47    /// Downgrade or suppress preflight diagnostics. See
48    /// [`PreflightSeverity`].
49    #[serde(default, alias = "preflight-severity")]
50    pub preflight_severity: Option<String>,
51    /// List of `"capability.operation"` strings that should be accepted
52    /// by preflight without emitting a diagnostic, even if the operation
53    /// is not in the default or loaded capability manifest.
54    #[serde(default, alias = "preflight-allow")]
55    pub preflight_allow: Vec<String>,
56}
57
58pub(crate) fn absolutize_check_config_paths(
59    mut config: CheckConfig,
60    manifest_dir: &Path,
61) -> CheckConfig {
62    if let Some(path) = config.host_capabilities_path.clone() {
63        let candidate = PathBuf::from(&path);
64        if !candidate.is_absolute() {
65            config.host_capabilities_path =
66                Some(manifest_dir.join(candidate).display().to_string());
67        }
68    }
69    if let Some(path) = config.bundle_root.clone() {
70        let candidate = PathBuf::from(&path);
71        if !candidate.is_absolute() {
72            config.bundle_root = Some(manifest_dir.join(candidate).display().to_string());
73        }
74    }
75    config
76}
77
78/// Load the `[check]` config from the nearest `harn.toml`.
79/// Walks up from the given file (or from cwd if no file is given),
80/// stopping at a `.git` boundary.
81pub fn load_check_config(harn_file: Option<&std::path::Path>) -> CheckConfig {
82    let anchor = harn_file
83        .map(Path::to_path_buf)
84        .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
85    if let Some((manifest, dir)) =
86        crate::package::manifest_search::nearest_manifest_or_warn(&anchor)
87    {
88        return absolutize_check_config_paths(manifest.check, &dir);
89    }
90    CheckConfig::default()
91}