1use std::fs;
7use std::panic::{catch_unwind, AssertUnwindSafe};
8
9use crate::output::types::{Verifier, VerifyResult};
10
11pub struct YAMLVerifier;
13
14impl Verifier for YAMLVerifier {
15 fn verify(&self, path: &str, _module_id: &str) -> VerifyResult {
16 let content = match fs::read_to_string(path) {
17 Ok(c) => c,
18 Err(e) => return VerifyResult::fail(format!("Cannot read file: {e}")),
19 };
20
21 let parsed: serde_yaml::Value = match serde_yaml::from_str(&content) {
22 Ok(v) => v,
23 Err(e) => return VerifyResult::fail(format!("Invalid YAML: {e}")),
24 };
25
26 let bindings = match parsed.get("bindings") {
27 Some(b) => b,
28 None => return VerifyResult::fail("Missing or empty 'bindings' list".into()),
29 };
30
31 let bindings_seq = match bindings.as_sequence() {
32 Some(s) if !s.is_empty() => s,
33 _ => return VerifyResult::fail("Missing or empty 'bindings' list".into()),
34 };
35
36 let first = &bindings_seq[0];
37 for field in &["module_id", "target"] {
38 match first.get(*field) {
39 Some(v) if !v.is_null() => {}
40 _ => {
41 return VerifyResult::fail(format!(
42 "Missing required field '{field}' in binding"
43 ))
44 }
45 }
46 }
47
48 VerifyResult::ok()
49 }
50}
51
52pub struct JSONVerifier {
54 }
57
58impl JSONVerifier {
59 pub fn new() -> Self {
61 Self {}
62 }
63}
64
65impl Default for JSONVerifier {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl Verifier for JSONVerifier {
72 fn verify(&self, path: &str, _module_id: &str) -> VerifyResult {
73 let content = match fs::read_to_string(path) {
74 Ok(c) => c,
75 Err(e) => return VerifyResult::fail(format!("Cannot read file: {e}")),
76 };
77
78 match serde_json::from_str::<serde_json::Value>(&content) {
79 Ok(_) => VerifyResult::ok(),
80 Err(e) => VerifyResult::fail(format!("Invalid JSON: {e}")),
81 }
82 }
83}
84
85pub struct MagicBytesVerifier {
87 expected: Vec<u8>,
88}
89
90impl MagicBytesVerifier {
91 pub fn new(expected: Vec<u8>) -> Self {
93 Self { expected }
94 }
95}
96
97impl Verifier for MagicBytesVerifier {
98 fn verify(&self, path: &str, _module_id: &str) -> VerifyResult {
99 let content = match fs::read(path) {
100 Ok(c) => c,
101 Err(e) => return VerifyResult::fail(format!("Cannot read file: {e}")),
102 };
103
104 if content.len() < self.expected.len() {
105 return VerifyResult::fail(format!(
106 "File too short: expected at least {} bytes, got {}",
107 self.expected.len(),
108 content.len()
109 ));
110 }
111
112 let header = &content[..self.expected.len()];
113 if header != self.expected.as_slice() {
114 return VerifyResult::fail(format!(
115 "Magic bytes mismatch: expected {:?}, got {:?}",
116 self.expected, header
117 ));
118 }
119
120 VerifyResult::ok()
121 }
122}
123
124pub struct RegistryVerifier<'a> {
126 registry: &'a apcore::Registry,
127}
128
129impl<'a> RegistryVerifier<'a> {
130 pub fn new(registry: &'a apcore::Registry) -> Self {
132 Self { registry }
133 }
134}
135
136impl Verifier for RegistryVerifier<'_> {
137 fn verify(&self, _path: &str, module_id: &str) -> VerifyResult {
138 if self.registry.has(module_id) {
139 VerifyResult::ok()
140 } else {
141 VerifyResult::fail(format!(
142 "Module '{module_id}' not found in registry after registration"
143 ))
144 }
145 }
146}
147
148pub fn run_verifier_chain(
154 verifiers: &[&dyn Verifier],
155 path: &str,
156 module_id: &str,
157) -> VerifyResult {
158 for verifier in verifiers {
159 let verifier = AssertUnwindSafe(verifier);
160 let path = path.to_string();
161 let module_id = module_id.to_string();
162 let outcome = catch_unwind(move || verifier.verify(&path, &module_id));
163 match outcome {
164 Ok(result) if !result.ok => return result,
165 Ok(_) => {} Err(panic_info) => {
167 let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
168 (*s).to_string()
169 } else if let Some(s) = panic_info.downcast_ref::<String>() {
170 s.clone()
171 } else {
172 "unknown panic".to_string()
173 };
174 return VerifyResult::fail(format!("Verifier crashed: {msg}"));
175 }
176 }
177 }
178 VerifyResult::ok()
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use std::io::Write;
185 use tempfile::NamedTempFile;
186
187 #[test]
188 fn test_yaml_verifier_valid() {
189 let mut f = NamedTempFile::new().unwrap();
190 writeln!(f, "bindings:\n - module_id: test\n target: app:func").unwrap();
191 let result = YAMLVerifier.verify(f.path().to_str().unwrap(), "test");
192 assert!(result.ok);
193 }
194
195 #[test]
196 fn test_yaml_verifier_invalid_yaml() {
197 let mut f = NamedTempFile::new().unwrap();
198 writeln!(f, "{{invalid: yaml: [}}").unwrap();
199 let result = YAMLVerifier.verify(f.path().to_str().unwrap(), "test");
200 assert!(!result.ok);
201 assert!(result.error.unwrap().contains("Invalid YAML"));
202 }
203
204 #[test]
205 fn test_yaml_verifier_missing_bindings() {
206 let mut f = NamedTempFile::new().unwrap();
207 writeln!(f, "other_key: value").unwrap();
208 let result = YAMLVerifier.verify(f.path().to_str().unwrap(), "test");
209 assert!(!result.ok);
210 }
211
212 #[test]
213 fn test_yaml_verifier_missing_required_field() {
214 let mut f = NamedTempFile::new().unwrap();
215 writeln!(f, "bindings:\n - module_id: test").unwrap();
216 let result = YAMLVerifier.verify(f.path().to_str().unwrap(), "test");
217 assert!(!result.ok);
218 assert!(result.error.unwrap().contains("target"));
219 }
220
221 #[test]
222 fn test_json_verifier_valid() {
223 let mut f = NamedTempFile::new().unwrap();
224 writeln!(f, r#"{{"key": "value"}}"#).unwrap();
225 let result = JSONVerifier::new().verify(f.path().to_str().unwrap(), "test");
226 assert!(result.ok);
227 }
228
229 #[test]
230 fn test_json_verifier_invalid() {
231 let mut f = NamedTempFile::new().unwrap();
232 writeln!(f, "not json").unwrap();
233 let result = JSONVerifier::new().verify(f.path().to_str().unwrap(), "test");
234 assert!(!result.ok);
235 }
236
237 #[test]
238 fn test_magic_bytes_verifier_match() {
239 let mut f = NamedTempFile::new().unwrap();
240 f.write_all(b"\x89PNG\r\n\x1a\nrest of file").unwrap();
241 let verifier = MagicBytesVerifier::new(b"\x89PNG\r\n\x1a\n".to_vec());
242 let result = verifier.verify(f.path().to_str().unwrap(), "test");
243 assert!(result.ok);
244 }
245
246 #[test]
247 fn test_magic_bytes_verifier_mismatch() {
248 let mut f = NamedTempFile::new().unwrap();
249 f.write_all(b"NOT PNG").unwrap();
250 let verifier = MagicBytesVerifier::new(b"\x89PNG".to_vec());
251 let result = verifier.verify(f.path().to_str().unwrap(), "test");
252 assert!(!result.ok);
253 assert!(result.error.unwrap().contains("mismatch"));
254 }
255
256 #[test]
257 fn test_run_verifier_chain_all_pass() {
258 let v1 = JSONVerifier::new();
259 let mut f = NamedTempFile::new().unwrap();
260 writeln!(f, r#"{{"ok": true}}"#).unwrap();
261 let verifiers: Vec<&dyn Verifier> = vec![&v1];
262 let result = run_verifier_chain(&verifiers, f.path().to_str().unwrap(), "test");
263 assert!(result.ok);
264 }
265
266 #[test]
267 fn test_run_verifier_chain_stops_on_failure() {
268 let v1 = JSONVerifier::new();
269 let mut f = NamedTempFile::new().unwrap();
270 writeln!(f, "not json").unwrap();
271 let verifiers: Vec<&dyn Verifier> = vec![&v1];
272 let result = run_verifier_chain(&verifiers, f.path().to_str().unwrap(), "test");
273 assert!(!result.ok);
274 }
275
276 #[test]
277 fn test_run_verifier_chain_empty() {
278 let verifiers: Vec<&dyn Verifier> = vec![];
279 let result = run_verifier_chain(&verifiers, "", "test");
280 assert!(result.ok);
281 }
282
283 #[test]
284 fn test_yaml_verifier_nonexistent_file() {
285 let result = YAMLVerifier.verify("/tmp/nonexistent_file_abc123.yaml", "test");
286 assert!(!result.ok);
287 assert!(result.error.unwrap().contains("Cannot read file"));
288 }
289
290 #[test]
291 fn test_json_verifier_nonexistent_file() {
292 let result = JSONVerifier::new().verify("/tmp/nonexistent_file_abc123.json", "test");
293 assert!(!result.ok);
294 assert!(result.error.unwrap().contains("Cannot read file"));
295 }
296
297 #[test]
298 fn test_magic_bytes_verifier_file_too_short() {
299 let mut f = NamedTempFile::new().unwrap();
300 f.write_all(b"AB").unwrap();
301 let verifier = MagicBytesVerifier::new(b"ABCDEF".to_vec());
302 let result = verifier.verify(f.path().to_str().unwrap(), "test");
303 assert!(!result.ok);
304 let err = result.error.unwrap();
305 assert!(err.contains("File too short"), "got: {err}");
306 assert!(err.contains("6"), "should mention expected length");
307 assert!(err.contains("2"), "should mention actual length");
308 }
309
310 struct PanickingVerifier;
312
313 impl Verifier for PanickingVerifier {
314 fn verify(&self, _path: &str, _module_id: &str) -> VerifyResult {
315 panic!("verifier exploded");
316 }
317 }
318
319 #[test]
320 fn test_run_verifier_chain_panic_caught() {
321 let bad = PanickingVerifier;
322 let verifiers: Vec<&dyn Verifier> = vec![&bad];
323 let result = run_verifier_chain(&verifiers, "/fake", "test");
324 assert!(!result.ok);
325 let err = result.error.unwrap();
326 assert!(
327 err.contains("Verifier crashed"),
328 "expected crash message, got: {err}"
329 );
330 assert!(
331 err.contains("verifier exploded"),
332 "expected panic message, got: {err}"
333 );
334 }
335
336 struct AlwaysFailVerifier {
338 message: String,
339 }
340
341 impl AlwaysFailVerifier {
342 fn new(message: &str) -> Self {
343 Self {
344 message: message.to_string(),
345 }
346 }
347 }
348
349 impl Verifier for AlwaysFailVerifier {
350 fn verify(&self, _path: &str, _module_id: &str) -> VerifyResult {
351 VerifyResult::fail(self.message.clone())
352 }
353 }
354
355 struct AlwaysPassVerifier;
357
358 impl Verifier for AlwaysPassVerifier {
359 fn verify(&self, _path: &str, _module_id: &str) -> VerifyResult {
360 VerifyResult::ok()
361 }
362 }
363
364 #[test]
365 fn test_run_verifier_chain_crash_caught() {
366 let bad = AlwaysFailVerifier::new("simulated crash");
367 let verifiers: Vec<&dyn Verifier> = vec![&bad];
368 let result = run_verifier_chain(&verifiers, "/fake", "test");
369 assert!(!result.ok);
370 assert_eq!(result.error.as_deref(), Some("simulated crash"));
371 }
372
373 #[test]
374 fn test_run_verifier_chain_first_failure_stops() {
375 let fail_v = AlwaysFailVerifier::new("first failed");
377 let pass_v = AlwaysPassVerifier;
378 let verifiers: Vec<&dyn Verifier> = vec![&fail_v, &pass_v];
379 let result = run_verifier_chain(&verifiers, "/fake", "test");
380 assert!(!result.ok);
381 assert_eq!(result.error.as_deref(), Some("first failed"));
382 }
383}