use crate::{error::BuildError, CanonicalizationAlgorithm, FidelityOptions};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{Duration, Instant};
pub struct BuildVerifier {
config: VerificationConfig,
}
impl BuildVerifier {
pub fn new(config: VerificationConfig) -> Self {
Self { config }
}
pub fn verify(
&self,
xml_output: &str,
fidelity_options: &FidelityOptions,
) -> Result<VerificationResult, BuildError> {
let start_time = Instant::now();
let mut issues = Vec::new();
let mut round_trip_success = true;
let mut canonicalization_success = true;
let mut schema_validation_success = true;
let mut determinism_success = true;
if self.config.enable_round_trip_verification {
match self.verify_round_trip(xml_output, fidelity_options) {
Ok(result) => {
if !result.success {
round_trip_success = false;
issues.extend(result.issues);
}
}
Err(e) => {
round_trip_success = false;
issues.push(VerificationIssue {
severity: VerificationSeverity::Error,
category: "round-trip".to_string(),
message: format!("Round-trip verification failed: {}", e),
path: None,
suggestion: Some("Check XML structure and fidelity options".to_string()),
});
}
}
}
if self.config.enable_canonicalization_verification {
match self.verify_canonicalization(xml_output, fidelity_options) {
Ok(result) => {
if !result.success {
canonicalization_success = false;
issues.extend(result.issues);
}
}
Err(e) => {
canonicalization_success = false;
issues.push(VerificationIssue {
severity: VerificationSeverity::Error,
category: "canonicalization".to_string(),
message: format!("Canonicalization verification failed: {}", e),
path: None,
suggestion: Some("Check canonicalization settings".to_string()),
});
}
}
}
if self.config.enable_schema_validation {
match self.verify_schema(xml_output) {
Ok(result) => {
if !result.success {
schema_validation_success = false;
issues.extend(result.issues);
}
}
Err(e) => {
schema_validation_success = false;
issues.push(VerificationIssue {
severity: VerificationSeverity::Error,
category: "schema".to_string(),
message: format!("Schema validation failed: {}", e),
path: None,
suggestion: Some("Check XML against DDEX schema".to_string()),
});
}
}
}
if self.config.enable_determinism_verification {
match self.verify_determinism(xml_output, fidelity_options) {
Ok(result) => {
if !result.success {
determinism_success = false;
issues.extend(result.issues);
}
}
Err(e) => {
determinism_success = false;
issues.push(VerificationIssue {
severity: VerificationSeverity::Error,
category: "determinism".to_string(),
message: format!("Determinism verification failed: {}", e),
path: None,
suggestion: Some("Check determinism configuration".to_string()),
});
}
}
}
let verification_time = start_time.elapsed();
let overall_success = round_trip_success
&& canonicalization_success
&& schema_validation_success
&& determinism_success;
Ok(VerificationResult {
success: overall_success,
round_trip_success,
canonicalization_success,
schema_validation_success,
determinism_success,
issues,
verification_time,
})
}
fn verify_round_trip(
&self,
_xml_output: &str,
_fidelity_options: &FidelityOptions,
) -> Result<RoundTripVerificationResult, BuildError> {
let issues = Vec::new();
Ok(RoundTripVerificationResult {
success: true, issues,
})
}
fn verify_canonicalization(
&self,
xml_output: &str,
fidelity_options: &FidelityOptions,
) -> Result<CanonicalizationVerificationResult, BuildError> {
let mut issues = Vec::new();
let mut success = true;
match &fidelity_options.canonicalization {
CanonicalizationAlgorithm::None => {
if let Err(e) = quick_xml::Reader::from_str(xml_output).read_event() {
success = false;
issues.push(VerificationIssue {
severity: VerificationSeverity::Error,
category: "xml-wellformed".to_string(),
message: format!("XML is not well-formed: {}", e),
path: None,
suggestion: Some("Check XML syntax".to_string()),
});
}
}
CanonicalizationAlgorithm::C14N
| CanonicalizationAlgorithm::C14N11
| CanonicalizationAlgorithm::DbC14N => {
let mut canonicalized_versions = Vec::new();
for _ in 0..3 {
match self.canonicalize_xml(xml_output, &fidelity_options.canonicalization) {
Ok(canonical) => canonicalized_versions.push(canonical),
Err(e) => {
success = false;
issues.push(VerificationIssue {
severity: VerificationSeverity::Error,
category: "canonicalization".to_string(),
message: format!("Canonicalization failed: {}", e),
path: None,
suggestion: Some(
"Check canonicalization algorithm settings".to_string(),
),
});
break;
}
}
}
if canonicalized_versions.len() >= 2 {
let first = &canonicalized_versions[0];
for (i, version) in canonicalized_versions.iter().enumerate().skip(1) {
if first != version {
success = false;
issues.push(VerificationIssue {
severity: VerificationSeverity::Error,
category: "canonicalization-consistency".to_string(),
message: format!(
"Canonicalization is not deterministic: iteration {} differs",
i + 1
),
path: None,
suggestion: Some(
"Check for non-deterministic elements in canonicalization"
.to_string(),
),
});
}
}
}
}
CanonicalizationAlgorithm::Custom(_rules) => {
issues.push(VerificationIssue {
severity: VerificationSeverity::Info,
category: "canonicalization".to_string(),
message: "Custom canonicalization verification not yet implemented".to_string(),
path: None,
suggestion: None,
});
}
}
Ok(CanonicalizationVerificationResult { success, issues })
}
fn verify_schema(&self, xml_output: &str) -> Result<SchemaVerificationResult, BuildError> {
let mut issues = Vec::new();
let mut success = true;
let mut reader = quick_xml::Reader::from_str(xml_output);
loop {
match reader.read_event() {
Ok(quick_xml::events::Event::Eof) => break,
Ok(_) => continue,
Err(e) => {
success = false;
issues.push(VerificationIssue {
severity: VerificationSeverity::Error,
category: "xml-syntax".to_string(),
message: format!("XML syntax error: {}", e),
path: Some(format!("position {}", reader.buffer_position())),
suggestion: Some("Fix XML syntax errors".to_string()),
});
break;
}
}
}
Ok(SchemaVerificationResult { success, issues })
}
fn verify_determinism(
&self,
xml_output: &str,
fidelity_options: &FidelityOptions,
) -> Result<DeterminismVerificationResult, BuildError> {
let mut issues = Vec::new();
let mut success = true;
let non_deterministic_patterns = [
(
r#"\btimestamp\s*=\s*['"][^'"]*['"]"#,
"timestamp attributes",
),
(
r#"\bcreated\s*=\s*['"][^'"]*['"]"#,
"creation time attributes",
),
(r#"\buuid\s*=\s*['"][^'"]*['"]"#, "UUID attributes"),
(r#"\bid\s*=\s*['"]uuid:[^'"]*['"]"#, "UUID-based IDs"),
];
for (pattern, description) in non_deterministic_patterns {
if let Ok(re) = regex::Regex::new(pattern) {
if re.is_match(xml_output) {
issues.push(VerificationIssue {
severity: VerificationSeverity::Warning,
category: "determinism".to_string(),
message: format!(
"Potentially non-deterministic element detected: {}",
description
),
path: None,
suggestion: Some(
"Use content-based IDs instead of random values".to_string(),
),
});
}
}
}
if !fidelity_options.preserve_attribute_order {
if let Ok(attribute_order_regex) = regex::Regex::new(r#"<\w+[^>]*>"#) {
for attr_match in attribute_order_regex.find_iter(xml_output) {
let element = attr_match.as_str();
if !self.is_attribute_order_deterministic(element) {
success = false;
issues.push(VerificationIssue {
severity: VerificationSeverity::Error,
category: "determinism".to_string(),
message: "Attributes are not in deterministic order".to_string(),
path: Some(element.to_string()),
suggestion: Some("Enable deterministic attribute ordering".to_string()),
});
}
}
}
}
Ok(DeterminismVerificationResult { success, issues })
}
fn is_attribute_order_deterministic(&self, element_str: &str) -> bool {
if let Ok(attr_regex) = regex::Regex::new(r#"(\w+)\s*=\s*['"][^'"]*['"]"#) {
let mut attributes: Vec<&str> = attr_regex
.captures_iter(element_str)
.filter_map(|cap| cap.get(1).map(|m| m.as_str()))
.collect();
let original_order = attributes.clone();
attributes.sort();
original_order == attributes
} else {
true
}
}
fn canonicalize_xml(
&self,
xml: &str,
algorithm: &CanonicalizationAlgorithm,
) -> Result<String, BuildError> {
match algorithm {
CanonicalizationAlgorithm::None => Ok(xml.to_string()),
CanonicalizationAlgorithm::C14N => {
Ok(xml.to_string()) }
CanonicalizationAlgorithm::C14N11 => {
Ok(xml.to_string()) }
CanonicalizationAlgorithm::DbC14N => {
Ok(xml.to_string()) }
CanonicalizationAlgorithm::Custom(_rules) => {
Ok(xml.to_string()) }
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationConfig {
pub enable_round_trip_verification: bool,
pub enable_canonicalization_verification: bool,
pub enable_schema_validation: bool,
pub enable_determinism_verification: bool,
pub determinism_test_iterations: usize,
pub verification_timeout: Duration,
}
impl Default for VerificationConfig {
fn default() -> Self {
Self {
enable_round_trip_verification: true,
enable_canonicalization_verification: true,
enable_schema_validation: false,
enable_determinism_verification: true,
determinism_test_iterations: 3,
verification_timeout: Duration::from_secs(30),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationResult {
pub success: bool,
pub round_trip_success: bool,
pub canonicalization_success: bool,
pub schema_validation_success: bool,
pub determinism_success: bool,
pub issues: Vec<VerificationIssue>,
pub verification_time: Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationIssue {
pub severity: VerificationSeverity,
pub category: String,
pub message: String,
pub path: Option<String>,
pub suggestion: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum VerificationSeverity {
Error,
Warning,
Info,
}
#[derive(Debug, Clone)]
struct RoundTripVerificationResult {
success: bool,
issues: Vec<VerificationIssue>,
}
#[derive(Debug, Clone)]
struct CanonicalizationVerificationResult {
success: bool,
issues: Vec<VerificationIssue>,
}
#[derive(Debug, Clone)]
struct SchemaVerificationResult {
success: bool,
issues: Vec<VerificationIssue>,
}
#[derive(Debug, Clone)]
struct DeterminismVerificationResult {
success: bool,
issues: Vec<VerificationIssue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationReport {
pub config: VerificationConfig,
pub fidelity_options: FidelityOptions,
pub result: VerificationResult,
pub statistics: VerificationStatistics,
pub recommendations: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationStatistics {
pub elements_verified: usize,
pub attributes_verified: usize,
pub namespaces_processed: usize,
pub comments_verified: usize,
pub processing_instructions_verified: usize,
pub extensions_verified: HashMap<String, usize>,
pub memory_usage: usize,
}
impl Default for VerificationStatistics {
fn default() -> Self {
Self {
elements_verified: 0,
attributes_verified: 0,
namespaces_processed: 0,
comments_verified: 0,
processing_instructions_verified: 0,
extensions_verified: HashMap::new(),
memory_usage: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verification_config_default() {
let config = VerificationConfig::default();
assert!(config.enable_round_trip_verification);
assert!(config.enable_canonicalization_verification);
assert!(!config.enable_schema_validation);
assert!(config.enable_determinism_verification);
assert_eq!(config.determinism_test_iterations, 3);
}
#[test]
fn test_build_verifier_creation() {
let config = VerificationConfig::default();
let verifier = BuildVerifier::new(config);
assert_eq!(verifier.config.determinism_test_iterations, 3);
}
#[test]
fn test_attribute_order_determinism() {
let verifier = BuildVerifier::new(VerificationConfig::default());
assert!(verifier.is_attribute_order_deterministic(r#"<element a="1" b="2" c="3">"#));
assert!(!verifier.is_attribute_order_deterministic(r#"<element c="3" a="1" b="2">"#));
}
#[test]
fn test_verification_issue_creation() {
let issue = VerificationIssue {
severity: VerificationSeverity::Error,
category: "test".to_string(),
message: "Test issue".to_string(),
path: Some("/test/path".to_string()),
suggestion: Some("Fix the test".to_string()),
};
assert_eq!(issue.severity, VerificationSeverity::Error);
assert_eq!(issue.category, "test");
assert_eq!(issue.message, "Test issue");
}
#[test]
fn test_verification_statistics() {
let stats = VerificationStatistics::default();
assert_eq!(stats.elements_verified, 0);
assert_eq!(stats.attributes_verified, 0);
assert_eq!(stats.memory_usage, 0);
}
}