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