ddex_builder/
verification.rs

1//! Build Verification for Perfect Fidelity Engine
2//!
3//! This module provides comprehensive verification capabilities for the DDEX Builder,
4//! ensuring that generated XML meets fidelity requirements and can successfully
5//! round-trip through the parser.
6
7use crate::{error::BuildError, FidelityOptions, CanonicalizationAlgorithm};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::time::{Duration, Instant};
11
12/// Build verifier for Perfect Fidelity Engine
13pub struct BuildVerifier {
14    config: VerificationConfig,
15}
16
17impl BuildVerifier {
18    /// Create a new build verifier with the specified configuration
19    pub fn new(config: VerificationConfig) -> Self {
20        Self { config }
21    }
22
23    /// Verify that the generated XML meets fidelity requirements
24    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        // Track verification results
33        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        // Round-trip verification
39        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        // Canonicalization verification
61        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        // Schema validation
83        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        // Determinism verification
105        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    /// Verify round-trip capability: XML → Parse → Build → Compare
144    fn verify_round_trip(
145        &self,
146        xml_output: &str,
147        fidelity_options: &FidelityOptions,
148    ) -> Result<RoundTripVerificationResult, BuildError> {
149        // This would integrate with the ddex-parser to test round-trip
150        // For now, we'll simulate the verification
151        let issues = Vec::new();
152        
153        // TODO: Integrate with ddex-parser when available
154        // let parser = ddex_parser::DDEXParser::new();
155        // let parsed = parser.parse(xml_output)?;
156        // let rebuilt_xml = self.build(parsed)?;
157        // let canonical_original = canonicalize(xml_output)?;
158        // let canonical_rebuilt = canonicalize(rebuilt_xml)?;
159        // let success = canonical_original == canonical_rebuilt;
160
161        Ok(RoundTripVerificationResult {
162            success: true, // Placeholder
163            issues,
164        })
165    }
166
167    /// Verify canonicalization consistency
168    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                // No canonicalization - just verify XML is well-formed
179                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                // Verify that multiple canonicalizations produce the same result
194                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                // Verify custom canonicalization rules
231                // TODO: Implement custom canonicalization verification
232                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    /// Verify against DDEX schema
249    fn verify_schema(&self, xml_output: &str) -> Result<SchemaVerificationResult, BuildError> {
250        let mut issues = Vec::new();
251        let mut success = true;
252
253        // Basic XML well-formedness check
254        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        // TODO: Add actual DDEX schema validation
275        // This would require integrating with a schema validation library
276        // and loading the appropriate DDEX schemas
277
278        Ok(SchemaVerificationResult {
279            success,
280            issues,
281        })
282    }
283
284    /// Verify deterministic output
285    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        // Check for non-deterministic elements
294        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        // Check attribute ordering consistency
316        if !fidelity_options.preserve_attribute_order {
317            // Verify that attributes are in a deterministic order
318            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    /// Check if attributes in an element are in deterministic order
342    fn is_attribute_order_deterministic(&self, element_str: &str) -> bool {
343        // Extract attributes and check if they are sorted
344        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            // If regex fails, assume deterministic
356            true
357        }
358    }
359
360    /// Canonicalize XML using the specified algorithm
361    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                // TODO: Implement C14N canonicalization
370                Ok(xml.to_string()) // Placeholder
371            },
372            CanonicalizationAlgorithm::C14N11 => {
373                // TODO: Implement C14N11 canonicalization
374                Ok(xml.to_string()) // Placeholder
375            },
376            CanonicalizationAlgorithm::DbC14N => {
377                // TODO: Implement DB-C14N canonicalization
378                Ok(xml.to_string()) // Placeholder
379            },
380            CanonicalizationAlgorithm::Custom(_rules) => {
381                // TODO: Implement custom canonicalization
382                Ok(xml.to_string()) // Placeholder
383            },
384        }
385    }
386}
387
388/// Configuration for build verification
389#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct VerificationConfig {
391    /// Enable round-trip verification (build → parse → build)
392    pub enable_round_trip_verification: bool,
393    /// Enable canonicalization verification
394    pub enable_canonicalization_verification: bool,
395    /// Enable schema validation after build
396    pub enable_schema_validation: bool,
397    /// Enable determinism verification (multiple builds identical)
398    pub enable_determinism_verification: bool,
399    /// Number of builds for determinism verification
400    pub determinism_test_iterations: usize,
401    /// Timeout for verification operations
402    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/// Overall verification result
419#[derive(Debug, Clone, Serialize, Deserialize)]
420pub struct VerificationResult {
421    /// Overall verification success
422    pub success: bool,
423    /// Round-trip verification result
424    pub round_trip_success: bool,
425    /// Canonicalization verification result
426    pub canonicalization_success: bool,
427    /// Schema validation result
428    pub schema_validation_success: bool,
429    /// Determinism verification result
430    pub determinism_success: bool,
431    /// Verification errors and warnings
432    pub issues: Vec<VerificationIssue>,
433    /// Time taken for verification
434    pub verification_time: Duration,
435}
436
437/// Verification issue (error or warning)
438#[derive(Debug, Clone, Serialize, Deserialize)]
439pub struct VerificationIssue {
440    /// Issue severity
441    pub severity: VerificationSeverity,
442    /// Issue category
443    pub category: String,
444    /// Human-readable message
445    pub message: String,
446    /// Optional path to the problematic element
447    pub path: Option<String>,
448    /// Optional suggestion for fixing
449    pub suggestion: Option<String>,
450}
451
452/// Verification issue severity
453#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
454pub enum VerificationSeverity {
455    /// Error that prevents successful verification
456    Error,
457    /// Warning that may indicate a problem
458    Warning,
459    /// Informational message
460    Info,
461}
462
463/// Round-trip verification result
464#[derive(Debug, Clone)]
465struct RoundTripVerificationResult {
466    success: bool,
467    issues: Vec<VerificationIssue>,
468}
469
470/// Canonicalization verification result
471#[derive(Debug, Clone)]
472struct CanonicalizationVerificationResult {
473    success: bool,
474    issues: Vec<VerificationIssue>,
475}
476
477/// Schema verification result
478#[derive(Debug, Clone)]
479struct SchemaVerificationResult {
480    success: bool,
481    issues: Vec<VerificationIssue>,
482}
483
484/// Determinism verification result
485#[derive(Debug, Clone)]
486struct DeterminismVerificationResult {
487    success: bool,
488    issues: Vec<VerificationIssue>,
489}
490
491/// Verification report for detailed analysis
492#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct VerificationReport {
494    /// Verification configuration used
495    pub config: VerificationConfig,
496    /// Fidelity options used
497    pub fidelity_options: FidelityOptions,
498    /// Overall result
499    pub result: VerificationResult,
500    /// Detailed statistics
501    pub statistics: VerificationStatistics,
502    /// Recommendations for improvement
503    pub recommendations: Vec<String>,
504}
505
506/// Verification statistics
507#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct VerificationStatistics {
509    /// Total elements verified
510    pub elements_verified: usize,
511    /// Total attributes verified
512    pub attributes_verified: usize,
513    /// Namespaces processed
514    pub namespaces_processed: usize,
515    /// Comments verified
516    pub comments_verified: usize,
517    /// Processing instructions verified
518    pub processing_instructions_verified: usize,
519    /// Extensions verified
520    pub extensions_verified: HashMap<String, usize>,
521    /// Memory usage during verification
522    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        // Deterministic (alphabetically ordered)
566        assert!(verifier.is_attribute_order_deterministic(r#"<element a="1" b="2" c="3">"#));
567        
568        // Non-deterministic
569        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}