1use std::fs;
7use std::panic::{catch_unwind, AssertUnwindSafe};
8
9use crate::output::types::{Verifier, VerifyResult};
10
11pub struct SyntaxVerifier;
17
18impl Verifier for SyntaxVerifier {
19 fn verify(&self, path: &str, _module_id: &str) -> VerifyResult {
20 let content = match fs::read_to_string(path) {
21 Ok(c) => c,
22 Err(e) => return VerifyResult::fail(format!("Cannot read file: {e}")),
23 };
24
25 if content.trim().is_empty() {
26 return VerifyResult::fail("File is empty".into());
27 }
28
29 match syn::parse_file(&content) {
30 Ok(_) => VerifyResult::ok(),
31 Err(e) => VerifyResult::fail(format!("Invalid Rust syntax: {e}")),
32 }
33 }
34}
35
36pub struct YAMLVerifier;
38
39impl Verifier for YAMLVerifier {
40 fn verify(&self, path: &str, _module_id: &str) -> VerifyResult {
41 let content = match fs::read_to_string(path) {
42 Ok(c) => c,
43 Err(e) => return VerifyResult::fail(format!("Cannot read file: {e}")),
44 };
45
46 let parsed: serde_yaml::Value = match serde_yaml::from_str(&content) {
47 Ok(v) => v,
48 Err(e) => return VerifyResult::fail(format!("Invalid YAML: {e}")),
49 };
50
51 let bindings = match parsed.get("bindings") {
52 Some(b) => b,
53 None => return VerifyResult::fail("Missing or empty 'bindings' list".into()),
54 };
55
56 let bindings_seq = match bindings.as_sequence() {
57 Some(s) if !s.is_empty() => s,
58 _ => return VerifyResult::fail("Missing or empty 'bindings' list".into()),
59 };
60
61 let first = &bindings_seq[0];
62 for field in &["module_id", "target"] {
63 match first.get(*field) {
64 Some(v) if !v.is_null() => {}
65 _ => {
66 return VerifyResult::fail(format!(
67 "Missing required field '{field}' in binding"
68 ))
69 }
70 }
71 }
72
73 VerifyResult::ok()
74 }
75}
76
77pub struct JSONVerifier {
79 }
82
83impl JSONVerifier {
84 pub fn new() -> Self {
86 Self {}
87 }
88}
89
90impl Default for JSONVerifier {
91 fn default() -> Self {
92 Self::new()
93 }
94}
95
96impl Verifier for JSONVerifier {
97 fn verify(&self, path: &str, _module_id: &str) -> VerifyResult {
98 let content = match fs::read_to_string(path) {
99 Ok(c) => c,
100 Err(e) => return VerifyResult::fail(format!("Cannot read file: {e}")),
101 };
102
103 match serde_json::from_str::<serde_json::Value>(&content) {
104 Ok(_) => VerifyResult::ok(),
105 Err(e) => VerifyResult::fail(format!("Invalid JSON: {e}")),
106 }
107 }
108}
109
110pub struct MagicBytesVerifier {
112 expected: Vec<u8>,
113}
114
115impl MagicBytesVerifier {
116 pub fn new(expected: Vec<u8>) -> Self {
118 Self { expected }
119 }
120}
121
122impl Verifier for MagicBytesVerifier {
123 fn verify(&self, path: &str, _module_id: &str) -> VerifyResult {
124 let content = match fs::read(path) {
125 Ok(c) => c,
126 Err(e) => return VerifyResult::fail(format!("Cannot read file: {e}")),
127 };
128
129 if content.len() < self.expected.len() {
130 return VerifyResult::fail(format!(
131 "File too short: expected at least {} bytes, got {}",
132 self.expected.len(),
133 content.len()
134 ));
135 }
136
137 let header = &content[..self.expected.len()];
138 if header != self.expected.as_slice() {
139 return VerifyResult::fail(format!(
140 "Magic bytes mismatch: expected {:?}, got {:?}",
141 self.expected, header
142 ));
143 }
144
145 VerifyResult::ok()
146 }
147}
148
149pub struct RegistryVerifier<'a> {
151 registry: &'a apcore::Registry,
152}
153
154impl<'a> RegistryVerifier<'a> {
155 pub fn new(registry: &'a apcore::Registry) -> Self {
157 Self { registry }
158 }
159}
160
161impl Verifier for RegistryVerifier<'_> {
162 fn verify(&self, _path: &str, module_id: &str) -> VerifyResult {
163 if self.registry.has(module_id) {
164 VerifyResult::ok()
165 } else {
166 VerifyResult::fail(format!(
167 "Module '{module_id}' not found in registry after registration"
168 ))
169 }
170 }
171}
172
173pub fn run_verifier_chain(
179 verifiers: &[&dyn Verifier],
180 path: &str,
181 module_id: &str,
182) -> VerifyResult {
183 for verifier in verifiers {
184 let verifier = AssertUnwindSafe(verifier);
185 let path = path.to_string();
186 let module_id = module_id.to_string();
187 let outcome = catch_unwind(move || verifier.verify(&path, &module_id));
188 match outcome {
189 Ok(result) if !result.ok => return result,
190 Ok(_) => {} Err(panic_info) => {
192 let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
193 (*s).to_string()
194 } else if let Some(s) = panic_info.downcast_ref::<String>() {
195 s.clone()
196 } else {
197 "unknown panic".to_string()
198 };
199 return VerifyResult::fail(format!("Verifier crashed: {msg}"));
200 }
201 }
202 }
203 VerifyResult::ok()
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use std::io::Write;
210 use tempfile::NamedTempFile;
211
212 #[test]
213 fn test_yaml_verifier_valid() {
214 let mut f = NamedTempFile::new().unwrap();
215 writeln!(f, "bindings:\n - module_id: test\n target: app:func").unwrap();
216 let result = YAMLVerifier.verify(f.path().to_str().unwrap(), "test");
217 assert!(result.ok);
218 }
219
220 #[test]
221 fn test_yaml_verifier_invalid_yaml() {
222 let mut f = NamedTempFile::new().unwrap();
223 writeln!(f, "{{invalid: yaml: [}}").unwrap();
224 let result = YAMLVerifier.verify(f.path().to_str().unwrap(), "test");
225 assert!(!result.ok);
226 assert!(result.error.unwrap().contains("Invalid YAML"));
227 }
228
229 #[test]
230 fn test_yaml_verifier_missing_bindings() {
231 let mut f = NamedTempFile::new().unwrap();
232 writeln!(f, "other_key: value").unwrap();
233 let result = YAMLVerifier.verify(f.path().to_str().unwrap(), "test");
234 assert!(!result.ok);
235 }
236
237 #[test]
238 fn test_yaml_verifier_missing_required_field() {
239 let mut f = NamedTempFile::new().unwrap();
240 writeln!(f, "bindings:\n - module_id: test").unwrap();
241 let result = YAMLVerifier.verify(f.path().to_str().unwrap(), "test");
242 assert!(!result.ok);
243 assert!(result.error.unwrap().contains("target"));
244 }
245
246 #[test]
247 fn test_json_verifier_valid() {
248 let mut f = NamedTempFile::new().unwrap();
249 writeln!(f, r#"{{"key": "value"}}"#).unwrap();
250 let result = JSONVerifier::new().verify(f.path().to_str().unwrap(), "test");
251 assert!(result.ok);
252 }
253
254 #[test]
255 fn test_json_verifier_invalid() {
256 let mut f = NamedTempFile::new().unwrap();
257 writeln!(f, "not json").unwrap();
258 let result = JSONVerifier::new().verify(f.path().to_str().unwrap(), "test");
259 assert!(!result.ok);
260 }
261
262 #[test]
263 fn test_magic_bytes_verifier_match() {
264 let mut f = NamedTempFile::new().unwrap();
265 f.write_all(b"\x89PNG\r\n\x1a\nrest of file").unwrap();
266 let verifier = MagicBytesVerifier::new(b"\x89PNG\r\n\x1a\n".to_vec());
267 let result = verifier.verify(f.path().to_str().unwrap(), "test");
268 assert!(result.ok);
269 }
270
271 #[test]
272 fn test_magic_bytes_verifier_mismatch() {
273 let mut f = NamedTempFile::new().unwrap();
274 f.write_all(b"NOT PNG").unwrap();
275 let verifier = MagicBytesVerifier::new(b"\x89PNG".to_vec());
276 let result = verifier.verify(f.path().to_str().unwrap(), "test");
277 assert!(!result.ok);
278 assert!(result.error.unwrap().contains("mismatch"));
279 }
280
281 #[test]
282 fn test_run_verifier_chain_all_pass() {
283 let v1 = JSONVerifier::new();
284 let mut f = NamedTempFile::new().unwrap();
285 writeln!(f, r#"{{"ok": true}}"#).unwrap();
286 let verifiers: Vec<&dyn Verifier> = vec![&v1];
287 let result = run_verifier_chain(&verifiers, f.path().to_str().unwrap(), "test");
288 assert!(result.ok);
289 }
290
291 #[test]
292 fn test_run_verifier_chain_stops_on_failure() {
293 let v1 = JSONVerifier::new();
294 let mut f = NamedTempFile::new().unwrap();
295 writeln!(f, "not json").unwrap();
296 let verifiers: Vec<&dyn Verifier> = vec![&v1];
297 let result = run_verifier_chain(&verifiers, f.path().to_str().unwrap(), "test");
298 assert!(!result.ok);
299 }
300
301 #[test]
302 fn test_run_verifier_chain_empty() {
303 let verifiers: Vec<&dyn Verifier> = vec![];
304 let result = run_verifier_chain(&verifiers, "", "test");
305 assert!(result.ok);
306 }
307
308 #[test]
309 fn test_yaml_verifier_nonexistent_file() {
310 let result = YAMLVerifier.verify("/tmp/nonexistent_file_abc123.yaml", "test");
311 assert!(!result.ok);
312 assert!(result.error.unwrap().contains("Cannot read file"));
313 }
314
315 #[test]
316 fn test_json_verifier_nonexistent_file() {
317 let result = JSONVerifier::new().verify("/tmp/nonexistent_file_abc123.json", "test");
318 assert!(!result.ok);
319 assert!(result.error.unwrap().contains("Cannot read file"));
320 }
321
322 #[test]
323 fn test_magic_bytes_verifier_file_too_short() {
324 let mut f = NamedTempFile::new().unwrap();
325 f.write_all(b"AB").unwrap();
326 let verifier = MagicBytesVerifier::new(b"ABCDEF".to_vec());
327 let result = verifier.verify(f.path().to_str().unwrap(), "test");
328 assert!(!result.ok);
329 let err = result.error.unwrap();
330 assert!(err.contains("File too short"), "got: {err}");
331 assert!(err.contains("6"), "should mention expected length");
332 assert!(err.contains("2"), "should mention actual length");
333 }
334
335 struct PanickingVerifier;
337
338 impl Verifier for PanickingVerifier {
339 fn verify(&self, _path: &str, _module_id: &str) -> VerifyResult {
340 panic!("verifier exploded");
341 }
342 }
343
344 #[test]
345 fn test_run_verifier_chain_panic_caught() {
346 let bad = PanickingVerifier;
347 let verifiers: Vec<&dyn Verifier> = vec![&bad];
348 let result = run_verifier_chain(&verifiers, "/fake", "test");
349 assert!(!result.ok);
350 let err = result.error.unwrap();
351 assert!(
352 err.contains("Verifier crashed"),
353 "expected crash message, got: {err}"
354 );
355 assert!(
356 err.contains("verifier exploded"),
357 "expected panic message, got: {err}"
358 );
359 }
360
361 struct AlwaysFailVerifier {
363 message: String,
364 }
365
366 impl AlwaysFailVerifier {
367 fn new(message: &str) -> Self {
368 Self {
369 message: message.to_string(),
370 }
371 }
372 }
373
374 impl Verifier for AlwaysFailVerifier {
375 fn verify(&self, _path: &str, _module_id: &str) -> VerifyResult {
376 VerifyResult::fail(self.message.clone())
377 }
378 }
379
380 struct AlwaysPassVerifier;
382
383 impl Verifier for AlwaysPassVerifier {
384 fn verify(&self, _path: &str, _module_id: &str) -> VerifyResult {
385 VerifyResult::ok()
386 }
387 }
388
389 #[test]
390 fn test_run_verifier_chain_crash_caught() {
391 let bad = AlwaysFailVerifier::new("simulated crash");
392 let verifiers: Vec<&dyn Verifier> = vec![&bad];
393 let result = run_verifier_chain(&verifiers, "/fake", "test");
394 assert!(!result.ok);
395 assert_eq!(result.error.as_deref(), Some("simulated crash"));
396 }
397
398 #[test]
399 fn test_run_verifier_chain_first_failure_stops() {
400 let fail_v = AlwaysFailVerifier::new("first failed");
402 let pass_v = AlwaysPassVerifier;
403 let verifiers: Vec<&dyn Verifier> = vec![&fail_v, &pass_v];
404 let result = run_verifier_chain(&verifiers, "/fake", "test");
405 assert!(!result.ok);
406 assert_eq!(result.error.as_deref(), Some("first failed"));
407 }
408
409 #[test]
410 fn test_syntax_verifier_valid_rust() {
411 let mut f = NamedTempFile::new().unwrap();
412 writeln!(f, "fn main() {{\n println!(\"hello\");\n}}").unwrap();
413 let result = SyntaxVerifier.verify(f.path().to_str().unwrap(), "test");
414 assert!(result.ok, "expected ok, got: {:?}", result.error);
415 }
416
417 #[test]
418 fn test_syntax_verifier_invalid_rust() {
419 let mut f = NamedTempFile::new().unwrap();
420 writeln!(f, "fn main() {{{{{{").unwrap();
421 let result = SyntaxVerifier.verify(f.path().to_str().unwrap(), "test");
422 assert!(!result.ok);
423 assert!(result.error.unwrap().contains("Invalid Rust syntax"));
424 }
425
426 #[test]
427 fn test_syntax_verifier_empty_file() {
428 let mut f = NamedTempFile::new().unwrap();
429 write!(f, "").unwrap();
430 let result = SyntaxVerifier.verify(f.path().to_str().unwrap(), "test");
431 assert!(!result.ok);
432 assert!(result.error.unwrap().contains("File is empty"));
433 }
434
435 #[test]
436 fn test_syntax_verifier_nonexistent_file() {
437 let result = SyntaxVerifier.verify("/tmp/nonexistent_rs_file_abc123.rs", "test");
438 assert!(!result.ok);
439 assert!(result.error.unwrap().contains("Cannot read file"));
440 }
441
442 #[test]
443 fn test_syntax_verifier_whitespace_only() {
444 let mut f = NamedTempFile::new().unwrap();
445 write!(f, " \n\n \t ").unwrap();
446 let result = SyntaxVerifier.verify(f.path().to_str().unwrap(), "test");
447 assert!(!result.ok);
448 assert!(result.error.unwrap().contains("File is empty"));
449 }
450
451 #[test]
452 fn test_syntax_verifier_complex_valid() {
453 let mut f = NamedTempFile::new().unwrap();
454 writeln!(
455 f,
456 "use std::collections::HashMap;\n\
457 \n\
458 pub struct Foo {{\n\
459 pub name: String,\n\
460 pub values: HashMap<String, i32>,\n\
461 }}\n\
462 \n\
463 impl Foo {{\n\
464 pub fn new(name: String) -> Self {{\n\
465 Self {{ name, values: HashMap::new() }}\n\
466 }}\n\
467 }}"
468 )
469 .unwrap();
470 let result = SyntaxVerifier.verify(f.path().to_str().unwrap(), "test");
471 assert!(result.ok, "expected ok, got: {:?}", result.error);
472 }
473}