1use crate::{error::BuildError, FidelityOptions, CanonicalizationAlgorithm};
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("Check canonicalization algorithm settings".to_string()),
207 });
208 break;
209 }
210 }
211 }
212
213 if canonicalized_versions.len() >= 2 {
214 let first = &canonicalized_versions[0];
215 for (i, version) in canonicalized_versions.iter().enumerate().skip(1) {
216 if first != version {
217 success = false;
218 issues.push(VerificationIssue {
219 severity: VerificationSeverity::Error,
220 category: "canonicalization-consistency".to_string(),
221 message: format!("Canonicalization is not deterministic: iteration {} differs", i + 1),
222 path: None,
223 suggestion: Some("Check for non-deterministic elements in canonicalization".to_string()),
224 });
225 }
226 }
227 }
228 },
229 CanonicalizationAlgorithm::Custom(_rules) => {
230 issues.push(VerificationIssue {
233 severity: VerificationSeverity::Info,
234 category: "canonicalization".to_string(),
235 message: "Custom canonicalization verification not yet implemented".to_string(),
236 path: None,
237 suggestion: None,
238 });
239 },
240 }
241
242 Ok(CanonicalizationVerificationResult {
243 success,
244 issues,
245 })
246 }
247
248 fn verify_schema(&self, xml_output: &str) -> Result<SchemaVerificationResult, BuildError> {
250 let mut issues = Vec::new();
251 let mut success = true;
252
253 let mut reader = quick_xml::Reader::from_str(xml_output);
255
256 loop {
257 match reader.read_event() {
258 Ok(quick_xml::events::Event::Eof) => break,
259 Ok(_) => continue,
260 Err(e) => {
261 success = false;
262 issues.push(VerificationIssue {
263 severity: VerificationSeverity::Error,
264 category: "xml-syntax".to_string(),
265 message: format!("XML syntax error: {}", e),
266 path: Some(format!("position {}", reader.buffer_position())),
267 suggestion: Some("Fix XML syntax errors".to_string()),
268 });
269 break;
270 }
271 }
272 }
273
274 Ok(SchemaVerificationResult {
279 success,
280 issues,
281 })
282 }
283
284 fn verify_determinism(
286 &self,
287 xml_output: &str,
288 fidelity_options: &FidelityOptions,
289 ) -> Result<DeterminismVerificationResult, BuildError> {
290 let mut issues = Vec::new();
291 let mut success = true;
292
293 let non_deterministic_patterns = [
295 (r#"\btimestamp\s*=\s*['"][^'"]*['"]"#, "timestamp attributes"),
296 (r#"\bcreated\s*=\s*['"][^'"]*['"]"#, "creation time attributes"),
297 (r#"\buuid\s*=\s*['"][^'"]*['"]"#, "UUID attributes"),
298 (r#"\bid\s*=\s*['"]uuid:[^'"]*['"]"#, "UUID-based IDs"),
299 ];
300
301 for (pattern, description) in non_deterministic_patterns {
302 if let Ok(re) = regex::Regex::new(pattern) {
303 if re.is_match(xml_output) {
304 issues.push(VerificationIssue {
305 severity: VerificationSeverity::Warning,
306 category: "determinism".to_string(),
307 message: format!("Potentially non-deterministic element detected: {}", description),
308 path: None,
309 suggestion: Some("Use content-based IDs instead of random values".to_string()),
310 });
311 }
312 }
313 }
314
315 if !fidelity_options.preserve_attribute_order {
317 if let Ok(attribute_order_regex) = regex::Regex::new(r#"<\w+[^>]*>"#) {
319 for attr_match in attribute_order_regex.find_iter(xml_output) {
320 let element = attr_match.as_str();
321 if !self.is_attribute_order_deterministic(element) {
322 success = false;
323 issues.push(VerificationIssue {
324 severity: VerificationSeverity::Error,
325 category: "determinism".to_string(),
326 message: "Attributes are not in deterministic order".to_string(),
327 path: Some(element.to_string()),
328 suggestion: Some("Enable deterministic attribute ordering".to_string()),
329 });
330 }
331 }
332 }
333 }
334
335 Ok(DeterminismVerificationResult {
336 success,
337 issues,
338 })
339 }
340
341 fn is_attribute_order_deterministic(&self, element_str: &str) -> bool {
343 if let Ok(attr_regex) = regex::Regex::new(r#"(\w+)\s*=\s*['"][^'"]*['"]"#) {
345 let mut attributes: Vec<&str> = attr_regex
346 .captures_iter(element_str)
347 .filter_map(|cap| cap.get(1).map(|m| m.as_str()))
348 .collect();
349
350 let original_order = attributes.clone();
351 attributes.sort();
352
353 original_order == attributes
354 } else {
355 true
357 }
358 }
359
360 fn canonicalize_xml(
362 &self,
363 xml: &str,
364 algorithm: &CanonicalizationAlgorithm,
365 ) -> Result<String, BuildError> {
366 match algorithm {
367 CanonicalizationAlgorithm::None => Ok(xml.to_string()),
368 CanonicalizationAlgorithm::C14N => {
369 Ok(xml.to_string()) },
372 CanonicalizationAlgorithm::C14N11 => {
373 Ok(xml.to_string()) },
376 CanonicalizationAlgorithm::DbC14N => {
377 Ok(xml.to_string()) },
380 CanonicalizationAlgorithm::Custom(_rules) => {
381 Ok(xml.to_string()) },
384 }
385 }
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct VerificationConfig {
391 pub enable_round_trip_verification: bool,
393 pub enable_canonicalization_verification: bool,
395 pub enable_schema_validation: bool,
397 pub enable_determinism_verification: bool,
399 pub determinism_test_iterations: usize,
401 pub verification_timeout: Duration,
403}
404
405impl Default for VerificationConfig {
406 fn default() -> Self {
407 Self {
408 enable_round_trip_verification: true,
409 enable_canonicalization_verification: true,
410 enable_schema_validation: false,
411 enable_determinism_verification: true,
412 determinism_test_iterations: 3,
413 verification_timeout: Duration::from_secs(30),
414 }
415 }
416}
417
418#[derive(Debug, Clone, Serialize, Deserialize)]
420pub struct VerificationResult {
421 pub success: bool,
423 pub round_trip_success: bool,
425 pub canonicalization_success: bool,
427 pub schema_validation_success: bool,
429 pub determinism_success: bool,
431 pub issues: Vec<VerificationIssue>,
433 pub verification_time: Duration,
435}
436
437#[derive(Debug, Clone, Serialize, Deserialize)]
439pub struct VerificationIssue {
440 pub severity: VerificationSeverity,
442 pub category: String,
444 pub message: String,
446 pub path: Option<String>,
448 pub suggestion: Option<String>,
450}
451
452#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
454pub enum VerificationSeverity {
455 Error,
457 Warning,
459 Info,
461}
462
463#[derive(Debug, Clone)]
465struct RoundTripVerificationResult {
466 success: bool,
467 issues: Vec<VerificationIssue>,
468}
469
470#[derive(Debug, Clone)]
472struct CanonicalizationVerificationResult {
473 success: bool,
474 issues: Vec<VerificationIssue>,
475}
476
477#[derive(Debug, Clone)]
479struct SchemaVerificationResult {
480 success: bool,
481 issues: Vec<VerificationIssue>,
482}
483
484#[derive(Debug, Clone)]
486struct DeterminismVerificationResult {
487 success: bool,
488 issues: Vec<VerificationIssue>,
489}
490
491#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct VerificationReport {
494 pub config: VerificationConfig,
496 pub fidelity_options: FidelityOptions,
498 pub result: VerificationResult,
500 pub statistics: VerificationStatistics,
502 pub recommendations: Vec<String>,
504}
505
506#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct VerificationStatistics {
509 pub elements_verified: usize,
511 pub attributes_verified: usize,
513 pub namespaces_processed: usize,
515 pub comments_verified: usize,
517 pub processing_instructions_verified: usize,
519 pub extensions_verified: HashMap<String, usize>,
521 pub memory_usage: usize,
523}
524
525impl Default for VerificationStatistics {
526 fn default() -> Self {
527 Self {
528 elements_verified: 0,
529 attributes_verified: 0,
530 namespaces_processed: 0,
531 comments_verified: 0,
532 processing_instructions_verified: 0,
533 extensions_verified: HashMap::new(),
534 memory_usage: 0,
535 }
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542 use crate::FidelityOptions;
543
544 #[test]
545 fn test_verification_config_default() {
546 let config = VerificationConfig::default();
547 assert!(config.enable_round_trip_verification);
548 assert!(config.enable_canonicalization_verification);
549 assert!(!config.enable_schema_validation);
550 assert!(config.enable_determinism_verification);
551 assert_eq!(config.determinism_test_iterations, 3);
552 }
553
554 #[test]
555 fn test_build_verifier_creation() {
556 let config = VerificationConfig::default();
557 let verifier = BuildVerifier::new(config);
558 assert_eq!(verifier.config.determinism_test_iterations, 3);
559 }
560
561 #[test]
562 fn test_attribute_order_determinism() {
563 let verifier = BuildVerifier::new(VerificationConfig::default());
564
565 assert!(verifier.is_attribute_order_deterministic(r#"<element a="1" b="2" c="3">"#));
567
568 assert!(!verifier.is_attribute_order_deterministic(r#"<element c="3" a="1" b="2">"#));
570 }
571
572 #[test]
573 fn test_verification_issue_creation() {
574 let issue = VerificationIssue {
575 severity: VerificationSeverity::Error,
576 category: "test".to_string(),
577 message: "Test issue".to_string(),
578 path: Some("/test/path".to_string()),
579 suggestion: Some("Fix the test".to_string()),
580 };
581
582 assert_eq!(issue.severity, VerificationSeverity::Error);
583 assert_eq!(issue.category, "test");
584 assert_eq!(issue.message, "Test issue");
585 }
586
587 #[test]
588 fn test_verification_statistics() {
589 let stats = VerificationStatistics::default();
590 assert_eq!(stats.elements_verified, 0);
591 assert_eq!(stats.attributes_verified, 0);
592 assert_eq!(stats.memory_usage, 0);
593 }
594}