Skip to main content

verifyos_cli/
config.rs

1use serde::Deserialize;
2use std::path::{Path, PathBuf};
3
4#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
5pub struct FileConfig {
6    pub format: Option<String>,
7    pub baseline: Option<PathBuf>,
8    pub md_out: Option<PathBuf>,
9    pub agent_pack: Option<PathBuf>,
10    pub agent_pack_format: Option<String>,
11    pub profile: Option<String>,
12    pub fail_on: Option<String>,
13    pub timings: Option<String>,
14    pub include: Option<Vec<String>>,
15    pub exclude: Option<Vec<String>>,
16    pub init: Option<InitDefaults>,
17    pub doctor: Option<DoctorDefaults>,
18    pub ci: Option<CiDefaults>,
19}
20
21#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
22pub struct InitDefaults {
23    pub output_dir: Option<PathBuf>,
24    pub path: Option<PathBuf>,
25    pub agent_pack_dir: Option<PathBuf>,
26    pub write_commands: Option<bool>,
27    pub shell_script: Option<bool>,
28    pub fix_prompt: Option<bool>,
29    pub profile: Option<String>,
30}
31
32#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
33pub struct DoctorDefaults {
34    pub output_dir: Option<PathBuf>,
35    pub agents: Option<PathBuf>,
36    pub format: Option<String>,
37    pub fix: Option<bool>,
38    pub repair: Option<Vec<String>>,
39    pub freshness_against: Option<PathBuf>,
40    pub plan_out: Option<PathBuf>,
41    pub profile: Option<String>,
42    pub open_pr_brief: Option<bool>,
43    pub open_pr_comment: Option<bool>,
44}
45
46#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
47pub struct CiDefaults {
48    pub doctor_repair: Option<Vec<String>>,
49    pub comment_mode: Option<String>,
50}
51
52#[derive(Debug, Clone, Default, PartialEq, Eq)]
53pub struct CliOverrides {
54    pub format: Option<String>,
55    pub baseline: Option<PathBuf>,
56    pub md_out: Option<PathBuf>,
57    pub agent_pack: Option<PathBuf>,
58    pub agent_pack_format: Option<String>,
59    pub profile: Option<String>,
60    pub fail_on: Option<String>,
61    pub timings: Option<String>,
62    pub include: Vec<String>,
63    pub exclude: Vec<String>,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct RuntimeConfig {
68    pub format: String,
69    pub baseline: Option<PathBuf>,
70    pub md_out: Option<PathBuf>,
71    pub agent_pack: Option<PathBuf>,
72    pub agent_pack_format: String,
73    pub profile: String,
74    pub fail_on: String,
75    pub timings: String,
76    pub include: Vec<String>,
77    pub exclude: Vec<String>,
78}
79
80pub fn load_file_config(config_path: Option<&Path>) -> Result<FileConfig, miette::Report> {
81    let Some(path) = resolve_config_path(config_path) else {
82        return Ok(FileConfig::default());
83    };
84
85    let raw = std::fs::read_to_string(&path)
86        .map_err(|err| miette::miette!("Failed to read config file {}: {}", path.display(), err))?;
87    toml::from_str(&raw)
88        .map_err(|err| miette::miette!("Failed to parse config file {}: {}", path.display(), err))
89}
90
91pub fn resolve_runtime_config(file: FileConfig, cli: CliOverrides) -> RuntimeConfig {
92    RuntimeConfig {
93        format: cli
94            .format
95            .or(file.format)
96            .unwrap_or_else(|| "table".to_string()),
97        baseline: cli.baseline.or(file.baseline),
98        md_out: cli.md_out.or(file.md_out),
99        agent_pack: cli.agent_pack.or(file.agent_pack),
100        agent_pack_format: cli
101            .agent_pack_format
102            .or(file.agent_pack_format)
103            .unwrap_or_else(|| "json".to_string()),
104        profile: cli
105            .profile
106            .or(file.profile)
107            .unwrap_or_else(|| "full".to_string()),
108        fail_on: cli
109            .fail_on
110            .or(file.fail_on)
111            .unwrap_or_else(|| "error".to_string()),
112        timings: cli
113            .timings
114            .or(file.timings)
115            .unwrap_or_else(|| "off".to_string()),
116        include: if cli.include.is_empty() {
117            file.include.unwrap_or_default()
118        } else {
119            cli.include
120        },
121        exclude: if cli.exclude.is_empty() {
122            file.exclude.unwrap_or_default()
123        } else {
124            cli.exclude
125        },
126    }
127}
128
129fn resolve_config_path(config_path: Option<&Path>) -> Option<PathBuf> {
130    if let Some(path) = config_path {
131        return Some(path.to_path_buf());
132    }
133
134    let default = PathBuf::from("verifyos.toml");
135    if default.exists() {
136        Some(default)
137    } else {
138        None
139    }
140}