doiget_core/
verify_config.rs1use camino::Utf8Path;
22use serde::Deserialize;
23use thiserror::Error;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
27#[serde(rename_all = "lowercase")]
28pub enum OnMissingId {
29 #[default]
31 Warn,
32 Error,
34 Skip,
36}
37
38#[derive(Debug, Clone, Default, PartialEq, Eq)]
40#[non_exhaustive]
41pub struct VerifyConfig {
42 pub strict: bool,
44 pub on_missing_id: OnMissingId,
46}
47
48#[derive(Debug, Error)]
50#[non_exhaustive]
51pub enum VerifyConfigError {
52 #[error("reading verify config {path}: {source}")]
54 Io {
55 path: String,
57 #[source]
59 source: std::io::Error,
60 },
61 #[error("parsing verify config {path}: {message}")]
64 Parse {
65 path: String,
67 message: String,
69 },
70}
71
72#[derive(Debug, Default, Deserialize)]
76struct RawConfig {
77 #[serde(default)]
78 verify: Option<RawVerify>,
79 #[serde(flatten)]
80 _other: serde::de::IgnoredAny,
81}
82
83#[derive(Debug, Default, Deserialize)]
86#[serde(deny_unknown_fields)]
87struct RawVerify {
88 #[serde(default)]
89 strict: bool,
90 #[serde(default)]
91 on_missing_id: OnMissingId,
92}
93
94pub fn load(config_path: &Utf8Path) -> Result<VerifyConfig, VerifyConfigError> {
101 let text = match std::fs::read_to_string(config_path) {
102 Ok(t) => t,
103 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
104 return Ok(VerifyConfig::default());
105 }
106 Err(e) => {
107 return Err(VerifyConfigError::Io {
108 path: config_path.to_string(),
109 source: e,
110 });
111 }
112 };
113 let raw: RawConfig = toml::from_str(&text).map_err(|e| VerifyConfigError::Parse {
114 path: config_path.to_string(),
115 message: e.to_string(),
116 })?;
117 Ok(match raw.verify {
118 Some(v) => VerifyConfig {
119 strict: v.strict,
120 on_missing_id: v.on_missing_id,
121 },
122 None => VerifyConfig::default(),
123 })
124}
125
126#[cfg(test)]
127#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
128mod tests {
129 use super::*;
130 use camino::Utf8PathBuf;
131
132 fn write(dir: &tempfile::TempDir, body: &str) -> Utf8PathBuf {
133 let p = Utf8PathBuf::try_from(dir.path().join("config.toml")).expect("utf-8");
134 std::fs::write(&p, body).expect("write config");
135 p
136 }
137
138 #[test]
139 fn missing_file_is_defaults() {
140 let cfg = load(Utf8Path::new("/no/such/config.toml")).expect("missing is ok");
141 assert_eq!(cfg, VerifyConfig::default());
142 assert_eq!(cfg.on_missing_id, OnMissingId::Warn);
143 assert!(!cfg.strict);
144 }
145
146 #[test]
147 fn empty_or_unrelated_sections_are_defaults() {
148 let dir = tempfile::TempDir::new().unwrap();
149 let p = write(&dir, "[network]\nadditional_hosts = []\n");
150 let cfg = load(&p).expect("parses");
151 assert_eq!(cfg, VerifyConfig::default());
152 }
153
154 #[test]
155 fn reads_on_missing_id_and_strict() {
156 let dir = tempfile::TempDir::new().unwrap();
157 let p = write(&dir, "[verify]\non_missing_id = \"error\"\nstrict = true\n");
158 let cfg = load(&p).expect("parses");
159 assert_eq!(cfg.on_missing_id, OnMissingId::Error);
160 assert!(cfg.strict);
161 }
162
163 #[test]
164 fn skip_value_parses() {
165 let dir = tempfile::TempDir::new().unwrap();
166 let p = write(&dir, "[verify]\non_missing_id = \"skip\"\n");
167 let cfg = load(&p).expect("parses");
168 assert_eq!(cfg.on_missing_id, OnMissingId::Skip);
169 }
170
171 #[test]
172 fn unknown_key_in_verify_is_an_error() {
173 let dir = tempfile::TempDir::new().unwrap();
174 let p = write(&dir, "[verify]\non_missing_ids = \"warn\"\n");
175 assert!(matches!(load(&p), Err(VerifyConfigError::Parse { .. })));
176 }
177
178 #[test]
179 fn invalid_on_missing_id_value_is_an_error() {
180 let dir = tempfile::TempDir::new().unwrap();
181 let p = write(&dir, "[verify]\non_missing_id = \"sometimes\"\n");
182 assert!(matches!(load(&p), Err(VerifyConfigError::Parse { .. })));
183 }
184}