1use crate::{error::BuildError, CanonicalizationAlgorithm, FidelityOptions};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::time::{Duration, Instant};
11
12pub struct BuildVerifier {
14 config: VerificationConfig,
15}
16
17impl BuildVerifier {
18 pub fn new(config: VerificationConfig) -> Self {
20 Self { config }
21 }
22
23 pub fn verify(
25 &self,
26 xml_output: &str,
27 fidelity_options: &FidelityOptions,
28 ) -> Result<VerificationResult, BuildError> {
29 let start_time = Instant::now();
30 let mut issues = Vec::new();
31
32 let mut round_trip_success = true;
34 let mut canonicalization_success = true;
35 let mut schema_validation_success = true;
36 let mut determinism_success = true;
37
38 if self.config.enable_round_trip_verification {
40 match self.verify_round_trip(xml_output, fidelity_options) {
41 Ok(result) => {
42 if !result.success {
43 round_trip_success = false;
44 issues.extend(result.issues);
45 }
46 }
47 Err(e) => {
48 round_trip_success = false;
49 issues.push(VerificationIssue {
50 severity: VerificationSeverity::Error,
51 category: "round-trip".to_string(),
52 message: format!("Round-trip verification failed: {}", e),
53 path: None,
54 suggestion: Some("Check XML structure and fidelity options".to_string()),
55 });
56 }
57 }
58 }
59
60 if self.config.enable_canonicalization_verification {
62 match self.verify_canonicalization(xml_output, fidelity_options) {
63 Ok(result) => {
64 if !result.success {
65 canonicalization_success = false;
66 issues.extend(result.issues);
67 }
68 }
69 Err(e) => {
70 canonicalization_success = false;
71 issues.push(VerificationIssue {
72 severity: VerificationSeverity::Error,
73 category: "canonicalization".to_string(),
74 message: format!("Canonicalization verification failed: {}", e),
75 path: None,
76 suggestion: Some("Check canonicalization settings".to_string()),
77 });
78 }
79 }
80 }
81
82 if self.config.enable_schema_validation {
84 match self.verify_schema(xml_output) {
85 Ok(result) => {
86 if !result.success {
87 schema_validation_success = false;
88 issues.extend(result.issues);
89 }
90 }
91 Err(e) => {
92 schema_validation_success = false;
93 issues.push(VerificationIssue {
94 severity: VerificationSeverity::Error,
95 category: "schema".to_string(),
96 message: format!("Schema validation failed: {}", e),
97 path: None,
98 suggestion: Some("Check XML against DDEX schema".to_string()),
99 });
100 }
101 }
102 }
103
104 if self.config.enable_determinism_verification {
106 match self.verify_determinism(xml_output, fidelity_options) {
107 Ok(result) => {
108 if !result.success {
109 determinism_success = false;
110 issues.extend(result.issues);
111 }
112 }
113 Err(e) => {
114 determinism_success = false;
115 issues.push(VerificationIssue {
116 severity: VerificationSeverity::Error,
117 category: "determinism".to_string(),
118 message: format!("Determinism verification failed: {}", e),
119 path: None,
120 suggestion: Some("Check determinism configuration".to_string()),
121 });
122 }
123 }
124 }
125
126 let verification_time = start_time.elapsed();
127 let overall_success = round_trip_success
128 && canonicalization_success
129 && schema_validation_success
130 && determinism_success;
131
132 Ok(VerificationResult {
133 success: overall_success,
134 round_trip_success,
135 canonicalization_success,
136 schema_validation_success,
137 determinism_success,
138 issues,
139 verification_time,
140 })
141 }
142
143 fn verify_round_trip(
145 &self,
146 _xml_output: &str,
147 _fidelity_options: &FidelityOptions,
148 ) -> Result<RoundTripVerificationResult, BuildError> {
149 let issues = Vec::new();
152
153 Ok(RoundTripVerificationResult {
162 success: true, issues,
164 })
165 }
166
167 fn verify_canonicalization(
169 &self,
170 xml_output: &str,
171 fidelity_options: &FidelityOptions,
172 ) -> Result<CanonicalizationVerificationResult, BuildError> {
173 let mut issues = Vec::new();
174 let mut success = true;
175
176 match &fidelity_options.canonicalization {
177 CanonicalizationAlgorithm::None => {
178 if let Err(e) = quick_xml::Reader::from_str(xml_output).read_event() {
180 success = false;
181 issues.push(VerificationIssue {
182 severity: VerificationSeverity::Error,
183 category: "xml-wellformed".to_string(),
184 message: format!("XML is not well-formed: {}", e),
185 path: None,
186 suggestion: Some("Check XML syntax".to_string()),
187 });
188 }
189 }
190 CanonicalizationAlgorithm::C14N
191 | CanonicalizationAlgorithm::C14N11
192 | CanonicalizationAlgorithm::DbC14N => {
193 let mut canonicalized_versions = Vec::new();
195
196 for _ in 0..3 {
197 match self.canonicalize_xml(xml_output, &fidelity_options.canonicalization) {
198 Ok(canonical) => canonicalized_versions.push(canonical),
199 Err(e) => {
200 success = false;
201 issues.push(VerificationIssue {
202 severity: VerificationSeverity::Error,
203 category: "canonicalization".to_string(),
204 message: format!("Canonicalization failed: {}", e),
205 path: None,
206 suggestion: Some(
207 "Check canonicalization algorithm settings".to_string(),
208 ),
209 });
210 break;
211 }
212 }
213 }
214
215 if canonicalized_versions.len() >= 2 {
216 let first = &canonicalized_versions[0];
217 for (i, version) in canonicalized_versions.iter().enumerate().skip(1) {
218 if first != version {
219 success = false;
220 issues.push(VerificationIssue {
221 severity: VerificationSeverity::Error,
222 category: "canonicalization-consistency".to_string(),
223 message: format!(
224 "Canonicalization is not deterministic: iteration {} differs",
225 i + 1
226 ),
227 path: None,
228 suggestion: Some(
229 "Check for non-deterministic elements in canonicalization"
230 .to_string(),
231 ),
232 });
233 }
234 }
235 }
236 }
237 CanonicalizationAlgorithm::Custom(_rules) => {
238 issues.push(VerificationIssue {
241 severity: VerificationSeverity::Info,
242 category: "canonicalization".to_string(),
243 message: "Custom canonicalization verification not yet implemented".to_string(),
244 path: None,
245 suggestion: None,
246 });
247 }
248 }
249
250 Ok(CanonicalizationVerificationResult { success, issues })
251 }
252
253 fn verify_schema(&self, xml_output: &str) -> Result<SchemaVerificationResult, BuildError> {
255 let mut issues = Vec::new();
256 let mut success = true;
257
258 let mut reader = quick_xml::Reader::from_str(xml_output);
260
261 loop {
262 match reader.read_event() {
263 Ok(quick_xml::events::Event::Eof) => break,
264 Ok(_) => continue,
265 Err(e) => {
266 success = false;
267 issues.push(VerificationIssue {
268 severity: VerificationSeverity::Error,
269 category: "xml-syntax".to_string(),
270 message: format!("XML syntax error: {}", e),
271 path: Some(format!("position {}", reader.buffer_position())),
272 suggestion: Some("Fix XML syntax errors".to_string()),
273 });
274 break;
275 }
276 }
277 }
278
279 Ok(SchemaVerificationResult { success, issues })
284 }
285
286 fn verify_determinism(
288 &self,
289 xml_output: &str,
290 fidelity_options: &FidelityOptions,
291 ) -> Result<DeterminismVerificationResult, BuildError> {
292 let mut issues = Vec::new();
293 let mut success = true;
294
295 let non_deterministic_patterns = [
297 (
298 r#"\btimestamp\s*=\s*['"][^'"]*['"]"#,
299 "timestamp attributes",
300 ),
301 (
302 r#"\bcreated\s*=\s*['"][^'"]*['"]"#,
303 "creation time attributes",
304 ),
305 (r#"\buuid\s*=\s*['"][^'"]*['"]"#, "UUID attributes"),
306 (r#"\bid\s*=\s*['"]uuid:[^'"]*['"]"#, "UUID-based IDs"),
307 ];
308
309 for (pattern, description) in non_deterministic_patterns {
310 if let Ok(re) = regex::Regex::new(pattern) {
311 if re.is_match(xml_output) {
312 issues.push(VerificationIssue {
313 severity: VerificationSeverity::Warning,
314 category: "determinism".to_string(),
315 message: format!(
316 "Potentially non-deterministic element detected: {}",
317 description
318 ),
319 path: None,
320 suggestion: Some(
321 "Use content-based IDs instead of random values".to_string(),
322 ),
323 });
324 }
325 }
326 }
327
328 if !fidelity_options.preserve_attribute_order {
330 if let Ok(attribute_order_regex) = regex::Regex::new(r#"<\w+[^>]*>"#) {
332 for attr_match in attribute_order_regex.find_iter(xml_output) {
333 let element = attr_match.as_str();
334 if !self.is_attribute_order_deterministic(element) {
335 success = false;
336 issues.push(VerificationIssue {
337 severity: VerificationSeverity::Error,
338 category: "determinism".to_string(),
339 message: "Attributes are not in deterministic order".to_string(),
340 path: Some(element.to_string()),
341 suggestion: Some("Enable deterministic attribute ordering".to_string()),
342 });
343 }
344 }
345 }
346 }
347
348 Ok(DeterminismVerificationResult { success, issues })
349 }
350
351 fn is_attribute_order_deterministic(&self, element_str: &str) -> bool {
353 if let Ok(attr_regex) = regex::Regex::new(r#"(\w+)\s*=\s*['"][^'"]*['"]"#) {
355 let mut attributes: Vec<&str> = attr_regex
356 .captures_iter(element_str)
357 .filter_map(|cap| cap.get(1).map(|m| m.as_str()))
358 .collect();
359
360 let original_order = attributes.clone();
361 attributes.sort();
362
363 original_order == attributes
364 } else {
365 true
367 }
368 }
369
370 fn canonicalize_xml(
372 &self,
373 xml: &str,
374 algorithm: &CanonicalizationAlgorithm,
375 ) -> Result<String, BuildError> {
376 match algorithm {
377 CanonicalizationAlgorithm::None => Ok(xml.to_string()),
378 CanonicalizationAlgorithm::C14N => {
379 Ok(xml.to_string()) }
382 CanonicalizationAlgorithm::C14N11 => {
383 Ok(xml.to_string()) }
386 CanonicalizationAlgorithm::DbC14N => {
387 Ok(xml.to_string()) }
390 CanonicalizationAlgorithm::Custom(_rules) => {
391 Ok(xml.to_string()) }
394 }
395 }
396}
397
398#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct VerificationConfig {
401 pub enable_round_trip_verification: bool,
403 pub enable_canonicalization_verification: bool,
405 pub enable_schema_validation: bool,
407 pub enable_determinism_verification: bool,
409 pub determinism_test_iterations: usize,
411 pub verification_timeout: Duration,
413}
414
415impl Default for VerificationConfig {
416 fn default() -> Self {
417 Self {
418 enable_round_trip_verification: true,
419 enable_canonicalization_verification: true,
420 enable_schema_validation: false,
421 enable_determinism_verification: true,
422 determinism_test_iterations: 3,
423 verification_timeout: Duration::from_secs(30),
424 }
425 }
426}
427
428#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct VerificationResult {
431 pub success: bool,
433 pub round_trip_success: bool,
435 pub canonicalization_success: bool,
437 pub schema_validation_success: bool,
439 pub determinism_success: bool,
441 pub issues: Vec<VerificationIssue>,
443 pub verification_time: Duration,
445}
446
447#[derive(Debug, Clone, Serialize, Deserialize)]
449pub struct VerificationIssue {
450 pub severity: VerificationSeverity,
452 pub category: String,
454 pub message: String,
456 pub path: Option<String>,
458 pub suggestion: Option<String>,
460}
461
462#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
464pub enum VerificationSeverity {
465 Error,
467 Warning,
469 Info,
471}
472
473#[derive(Debug, Clone)]
475struct RoundTripVerificationResult {
476 success: bool,
477 issues: Vec<VerificationIssue>,
478}
479
480#[derive(Debug, Clone)]
482struct CanonicalizationVerificationResult {
483 success: bool,
484 issues: Vec<VerificationIssue>,
485}
486
487#[derive(Debug, Clone)]
489struct SchemaVerificationResult {
490 success: bool,
491 issues: Vec<VerificationIssue>,
492}
493
494#[derive(Debug, Clone)]
496struct DeterminismVerificationResult {
497 success: bool,
498 issues: Vec<VerificationIssue>,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct VerificationReport {
504 pub config: VerificationConfig,
506 pub fidelity_options: FidelityOptions,
508 pub result: VerificationResult,
510 pub statistics: VerificationStatistics,
512 pub recommendations: Vec<String>,
514}
515
516#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct VerificationStatistics {
519 pub elements_verified: usize,
521 pub attributes_verified: usize,
523 pub namespaces_processed: usize,
525 pub comments_verified: usize,
527 pub processing_instructions_verified: usize,
529 pub extensions_verified: HashMap<String, usize>,
531 pub memory_usage: usize,
533}
534
535impl Default for VerificationStatistics {
536 fn default() -> Self {
537 Self {
538 elements_verified: 0,
539 attributes_verified: 0,
540 namespaces_processed: 0,
541 comments_verified: 0,
542 processing_instructions_verified: 0,
543 extensions_verified: HashMap::new(),
544 memory_usage: 0,
545 }
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 #[test]
554 fn test_verification_config_default() {
555 let config = VerificationConfig::default();
556 assert!(config.enable_round_trip_verification);
557 assert!(config.enable_canonicalization_verification);
558 assert!(!config.enable_schema_validation);
559 assert!(config.enable_determinism_verification);
560 assert_eq!(config.determinism_test_iterations, 3);
561 }
562
563 #[test]
564 fn test_build_verifier_creation() {
565 let config = VerificationConfig::default();
566 let verifier = BuildVerifier::new(config);
567 assert_eq!(verifier.config.determinism_test_iterations, 3);
568 }
569
570 #[test]
571 fn test_attribute_order_determinism() {
572 let verifier = BuildVerifier::new(VerificationConfig::default());
573
574 assert!(verifier.is_attribute_order_deterministic(r#"<element a="1" b="2" c="3">"#));
576
577 assert!(!verifier.is_attribute_order_deterministic(r#"<element c="3" a="1" b="2">"#));
579 }
580
581 #[test]
582 fn test_verification_issue_creation() {
583 let issue = VerificationIssue {
584 severity: VerificationSeverity::Error,
585 category: "test".to_string(),
586 message: "Test issue".to_string(),
587 path: Some("/test/path".to_string()),
588 suggestion: Some("Fix the test".to_string()),
589 };
590
591 assert_eq!(issue.severity, VerificationSeverity::Error);
592 assert_eq!(issue.category, "test");
593 assert_eq!(issue.message, "Test issue");
594 }
595
596 #[test]
597 fn test_verification_statistics() {
598 let stats = VerificationStatistics::default();
599 assert_eq!(stats.elements_verified, 0);
600 assert_eq!(stats.attributes_verified, 0);
601 assert_eq!(stats.memory_usage, 0);
602 }
603}