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