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}