mdbook_validator/
config.rs1use std::collections::HashMap;
6use std::path::PathBuf;
7
8use anyhow::Result;
9
10use crate::error::ValidatorError;
11use serde::Deserialize;
12
13#[derive(Debug, Clone, Deserialize)]
15pub struct ValidatorConfig {
16 pub container: String,
18 pub script: PathBuf,
20 #[serde(default)]
23 pub exec_command: Option<String>,
24}
25
26#[derive(Debug, Clone, Deserialize, Default)]
28pub struct Config {
29 #[serde(default)]
31 pub validators: HashMap<String, ValidatorConfig>,
32 #[serde(default = "default_fail_fast")]
34 pub fail_fast: bool,
35 #[serde(default)]
38 pub fixtures_dir: Option<PathBuf>,
39}
40
41const fn default_fail_fast() -> bool {
42 true
43}
44
45impl Config {
46 pub fn from_context(ctx: &mdbook_preprocessor::PreprocessorContext) -> Result<Self> {
52 let config: Option<Config> = ctx.config.get("preprocessor.validator")?;
54 config.ok_or_else(|| {
55 ValidatorError::Config {
56 message: "No [preprocessor.validator] section in book.toml".into(),
57 }
58 .into()
59 })
60 }
61
62 pub fn get_validator(&self, name: &str) -> Result<&ValidatorConfig> {
68 self.validators.get(name).ok_or_else(|| {
69 ValidatorError::UnknownValidator {
70 name: name.to_owned(),
71 }
72 .into()
73 })
74 }
75}
76
77impl ValidatorConfig {
78 pub fn validate(&self, name: &str) -> Result<()> {
84 if self.container.is_empty() {
85 return Err(ValidatorError::InvalidConfig {
86 name: name.to_owned(),
87 reason: "container cannot be empty".into(),
88 }
89 .into());
90 }
91 if self.script.as_os_str().is_empty() {
92 return Err(ValidatorError::InvalidConfig {
93 name: name.to_owned(),
94 reason: "script path cannot be empty".into(),
95 }
96 .into());
97 }
98 Ok(())
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::error::ValidatorError;
106
107 #[test]
110 fn validator_config_valid() {
111 let config = ValidatorConfig {
112 container: "ubuntu:22.04".to_owned(),
113 script: PathBuf::from("validators/validate.sh"),
114 exec_command: None,
115 };
116 assert!(config.validate("test").is_ok());
117 }
118
119 #[test]
120 fn validator_config_empty_container() {
121 let config = ValidatorConfig {
122 container: String::new(),
123 script: PathBuf::from("validators/validate.sh"),
124 exec_command: None,
125 };
126 let err = config
127 .validate("test")
128 .unwrap_err()
129 .downcast::<ValidatorError>()
130 .expect("should be ValidatorError");
131 assert!(matches!(
132 err,
133 ValidatorError::InvalidConfig { reason, .. } if reason.contains("container cannot be empty")
134 ));
135 }
136
137 #[test]
138 fn validator_config_empty_script() {
139 let config = ValidatorConfig {
140 container: "ubuntu:22.04".to_owned(),
141 script: PathBuf::new(),
142 exec_command: None,
143 };
144 let err = config
145 .validate("test")
146 .unwrap_err()
147 .downcast::<ValidatorError>()
148 .expect("should be ValidatorError");
149 assert!(matches!(
150 err,
151 ValidatorError::InvalidConfig { reason, .. } if reason.contains("script path cannot be empty")
152 ));
153 }
154
155 #[test]
156 fn validator_config_with_exec_command() {
157 let config = ValidatorConfig {
158 container: "ubuntu:22.04".to_owned(),
159 script: PathBuf::from("validators/validate.sh"),
160 exec_command: Some("sqlite3 -json /tmp/test.db".to_owned()),
161 };
162 assert!(config.validate("test").is_ok());
163 assert_eq!(
164 config.exec_command,
165 Some("sqlite3 -json /tmp/test.db".to_owned())
166 );
167 }
168
169 #[test]
172 fn config_get_validator_exists() {
173 let mut validators = HashMap::new();
174 validators.insert(
175 "sqlite".to_owned(),
176 ValidatorConfig {
177 container: "keinos/sqlite3:3.47.2".to_owned(),
178 script: PathBuf::from("validators/validate-sqlite.sh"),
179 exec_command: None,
180 },
181 );
182 let config = Config {
183 validators,
184 fail_fast: true,
185 fixtures_dir: None,
186 };
187
188 let result = config.get_validator("sqlite");
189 assert!(result.is_ok());
190 assert_eq!(result.unwrap().container, "keinos/sqlite3:3.47.2");
191 }
192
193 #[test]
194 fn config_get_validator_not_found() {
195 let config = Config::default();
196 let result = config.get_validator("nonexistent");
197 assert!(result.is_err());
198 let err = result
199 .unwrap_err()
200 .downcast::<ValidatorError>()
201 .expect("should be ValidatorError");
202 assert!(matches!(
203 err,
204 ValidatorError::UnknownValidator { name } if name == "nonexistent"
205 ));
206 }
207
208 #[test]
209 fn config_default_fail_fast_true() {
210 assert!(default_fail_fast());
212 }
213
214 #[test]
217 fn config_parse_from_toml() {
218 let toml_str = r#"
219 fail_fast = false
220 [validators.sqlite]
221 container = "keinos/sqlite3:3.47.2"
222 script = "validators/validate-sqlite.sh"
223 "#;
224 let config: Config = toml::from_str(toml_str).unwrap();
225 assert!(!config.fail_fast);
226 assert!(config.validators.contains_key("sqlite"));
227 }
228
229 #[test]
230 fn config_parse_multiple_validators() {
231 let toml_str = r#"
232 [validators.sqlite]
233 container = "keinos/sqlite3:3.47.2"
234 script = "validators/validate-sqlite.sh"
235
236 [validators.osquery]
237 container = "osquery/osquery:5.17.0-ubuntu22.04"
238 script = "validators/validate-osquery.sh"
239 "#;
240 let config: Config = toml::from_str(toml_str).unwrap();
241 assert_eq!(config.validators.len(), 2);
242 assert!(config.validators.contains_key("sqlite"));
243 assert!(config.validators.contains_key("osquery"));
244 }
245
246 #[test]
247 fn config_parse_with_exec_command() {
248 let toml_str = r#"
249 [validators.custom]
250 container = "ubuntu:22.04"
251 script = "validators/validate-custom.sh"
252 exec_command = "python3 -c"
253 "#;
254 let config: Config = toml::from_str(toml_str).unwrap();
255 let custom = config.validators.get("custom").unwrap();
256 assert_eq!(custom.exec_command, Some("python3 -c".to_owned()));
257 }
258
259 #[test]
260 fn config_parse_with_fixtures_dir() {
261 let toml_str = r#"
262 fixtures_dir = "test-fixtures"
263 [validators.sqlite]
264 container = "keinos/sqlite3:3.47.2"
265 script = "validators/validate-sqlite.sh"
266 "#;
267 let config: Config = toml::from_str(toml_str).unwrap();
268 assert_eq!(config.fixtures_dir, Some(PathBuf::from("test-fixtures")));
269 }
270
271 #[test]
272 fn config_parse_empty_validators() {
273 let toml_str = r"
274 fail_fast = true
275 ";
276 let config: Config = toml::from_str(toml_str).unwrap();
277 assert!(config.validators.is_empty());
278 assert!(config.fail_fast);
279 }
280}