ddex_builder/
fidelity.rs

1//! Perfect Fidelity Engine for DDEX Builder
2//!
3//! This module provides comprehensive fidelity preservation features for the DDEX Builder,
4//! ensuring perfect round-trip compatibility with the DDEX Parser and maintaining
5//! semantic XML reproduction while maintaining structure and content.
6
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::time::Duration;
10
11/// Fidelity configuration for perfect XML preservation
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct FidelityConfig {
14    /// Preservation level for different XML components
15    pub preservation_level: PreservationLevel,
16    /// Comment preservation settings
17    pub comment_preservation: CommentPreservationConfig,
18    /// Processing instruction preservation
19    pub processing_instruction_preservation: bool,
20    /// Extension element preservation settings
21    pub extension_preservation: ExtensionPreservationConfig,
22    /// Attribute preservation settings
23    pub attribute_preservation: AttributePreservationConfig,
24    /// Namespace preservation settings
25    pub namespace_preservation: NamespacePreservationConfig,
26    /// Whitespace preservation settings
27    pub whitespace_preservation: WhitespacePreservationConfig,
28}
29
30impl Default for FidelityConfig {
31    fn default() -> Self {
32        Self {
33            preservation_level: PreservationLevel::Standard,
34            comment_preservation: CommentPreservationConfig::default(),
35            processing_instruction_preservation: false,
36            extension_preservation: ExtensionPreservationConfig::default(),
37            attribute_preservation: AttributePreservationConfig::default(),
38            namespace_preservation: NamespacePreservationConfig::default(),
39            whitespace_preservation: WhitespacePreservationConfig::default(),
40        }
41    }
42}
43
44/// Preservation levels for different use cases
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub enum PreservationLevel {
47    /// Basic preservation (DDEX elements only)
48    Basic,
49    /// Standard preservation (DDEX + known extensions)
50    Standard,
51    /// Perfect preservation (everything including comments, whitespace)
52    Perfect,
53    /// Custom preservation with fine-grained control
54    Custom,
55}
56
57/// Comment preservation configuration
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct CommentPreservationConfig {
60    /// Whether to preserve comments at all
61    pub enabled: bool,
62    /// Preserve document-level comments (outside root element)
63    pub preserve_document_comments: bool,
64    /// Preserve element-level comments (between elements)
65    pub preserve_element_comments: bool,
66    /// Preserve inline comments (within elements)
67    pub preserve_inline_comments: bool,
68    /// Preserve comment positioning relative to elements
69    pub preserve_comment_positioning: bool,
70    /// Maximum comment length to preserve (0 = unlimited)
71    pub max_comment_length: usize,
72}
73
74impl Default for CommentPreservationConfig {
75    fn default() -> Self {
76        Self {
77            enabled: false,
78            preserve_document_comments: true,
79            preserve_element_comments: true,
80            preserve_inline_comments: false,
81            preserve_comment_positioning: true,
82            max_comment_length: 0,
83        }
84    }
85}
86
87/// Extension preservation configuration
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct ExtensionPreservationConfig {
90    /// Whether to preserve extension elements
91    pub enabled: bool,
92    /// Known extension namespaces to always preserve
93    pub known_extensions: Vec<String>,
94    /// Unknown extension handling
95    pub unknown_extension_handling: UnknownExtensionHandling,
96    /// Preserve extension attributes
97    pub preserve_extension_attributes: bool,
98    /// Extension validation rules
99    pub extension_validation: ExtensionValidationConfig,
100}
101
102impl Default for ExtensionPreservationConfig {
103    fn default() -> Self {
104        Self {
105            enabled: true,
106            known_extensions: vec![
107                "http://spotify.com/ddex".to_string(),
108                "http://apple.com/ddex".to_string(),
109                "http://youtube.com/ddex".to_string(),
110                "http://amazon.com/ddex".to_string(),
111            ],
112            unknown_extension_handling: UnknownExtensionHandling::Preserve,
113            preserve_extension_attributes: true,
114            extension_validation: ExtensionValidationConfig::default(),
115        }
116    }
117}
118
119/// How to handle unknown extensions
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121pub enum UnknownExtensionHandling {
122    /// Preserve unknown extensions as-is
123    Preserve,
124    /// Drop unknown extensions
125    Drop,
126    /// Validate against schema and preserve if valid
127    ValidateAndPreserve,
128    /// Convert to generic extension format
129    Generalize,
130}
131
132/// Extension validation configuration
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct ExtensionValidationConfig {
135    /// Validate extension URIs
136    pub validate_uris: bool,
137    /// Validate extension schema compliance
138    pub validate_schema: bool,
139    /// Maximum extension nesting depth
140    pub max_nesting_depth: usize,
141    /// Maximum extension element count per document
142    pub max_extension_count: usize,
143}
144
145impl Default for ExtensionValidationConfig {
146    fn default() -> Self {
147        Self {
148            validate_uris: true,
149            validate_schema: false,
150            max_nesting_depth: 10,
151            max_extension_count: 1000,
152        }
153    }
154}
155
156/// Attribute preservation configuration
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct AttributePreservationConfig {
159    /// Preserve original attribute ordering
160    pub preserve_ordering: bool,
161    /// Preserve unknown attributes
162    pub preserve_unknown_attributes: bool,
163    /// Preserve empty attributes
164    pub preserve_empty_attributes: bool,
165    /// Attribute normalization rules
166    pub normalization: AttributeNormalizationConfig,
167    /// Attribute validation rules
168    pub validation: AttributeValidationConfig,
169}
170
171impl Default for AttributePreservationConfig {
172    fn default() -> Self {
173        Self {
174            preserve_ordering: false,
175            preserve_unknown_attributes: true,
176            preserve_empty_attributes: false,
177            normalization: AttributeNormalizationConfig::default(),
178            validation: AttributeValidationConfig::default(),
179        }
180    }
181}
182
183/// Attribute normalization configuration
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct AttributeNormalizationConfig {
186    /// Normalize boolean attributes (true/false vs 1/0)
187    pub normalize_booleans: bool,
188    /// Normalize numeric formats
189    pub normalize_numbers: bool,
190    /// Normalize date/time formats
191    pub normalize_datetime: bool,
192    /// Normalize whitespace in attribute values
193    pub normalize_whitespace: bool,
194}
195
196impl Default for AttributeNormalizationConfig {
197    fn default() -> Self {
198        Self {
199            normalize_booleans: true,
200            normalize_numbers: true,
201            normalize_datetime: true,
202            normalize_whitespace: true,
203        }
204    }
205}
206
207/// Attribute validation configuration
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct AttributeValidationConfig {
210    /// Validate attribute value formats
211    pub validate_formats: bool,
212    /// Validate attribute value constraints
213    pub validate_constraints: bool,
214    /// Custom validation rules per attribute
215    pub custom_rules: HashMap<String, AttributeValidationRule>,
216}
217
218impl Default for AttributeValidationConfig {
219    fn default() -> Self {
220        Self {
221            validate_formats: true,
222            validate_constraints: true,
223            custom_rules: HashMap::new(),
224        }
225    }
226}
227
228/// Custom attribute validation rule
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct AttributeValidationRule {
231    /// Regular expression pattern for validation
232    pub pattern: Option<String>,
233    /// Minimum value (for numeric attributes)
234    pub min_value: Option<f64>,
235    /// Maximum value (for numeric attributes)
236    pub max_value: Option<f64>,
237    /// Allowed values (enum validation)
238    pub allowed_values: Option<Vec<String>>,
239    /// Custom validation message
240    pub validation_message: Option<String>,
241}
242
243/// Namespace preservation configuration
244#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct NamespacePreservationConfig {
246    /// Preserve original namespace prefixes
247    pub preserve_prefixes: bool,
248    /// Preserve unused namespace declarations
249    pub preserve_unused_declarations: bool,
250    /// Namespace minimization strategy
251    pub minimization_strategy: NamespaceMinimizationStrategy,
252    /// Default namespace handling
253    pub default_namespace_handling: DefaultNamespaceHandling,
254}
255
256impl Default for NamespacePreservationConfig {
257    fn default() -> Self {
258        Self {
259            preserve_prefixes: false,
260            preserve_unused_declarations: false,
261            minimization_strategy: NamespaceMinimizationStrategy::Aggressive,
262            default_namespace_handling: DefaultNamespaceHandling::Preserve,
263        }
264    }
265}
266
267/// Namespace minimization strategies
268#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
269pub enum NamespaceMinimizationStrategy {
270    /// No minimization (preserve all declarations)
271    None,
272    /// Conservative minimization (remove only clearly unused)
273    Conservative,
274    /// Aggressive minimization (minimize to essential declarations)
275    Aggressive,
276    /// Custom minimization based on rules
277    Custom(NamespaceMinimizationRules),
278}
279
280/// Custom namespace minimization rules
281#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
282pub struct NamespaceMinimizationRules {
283    /// Namespaces to always preserve
284    pub always_preserve: Vec<String>,
285    /// Namespaces that can be safely removed if unused
286    pub can_remove: Vec<String>,
287    /// Preferred prefix mappings
288    pub preferred_prefixes: HashMap<String, String>,
289}
290
291/// Default namespace handling
292#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
293pub enum DefaultNamespaceHandling {
294    /// Preserve default namespace as-is
295    Preserve,
296    /// Remove default namespace if possible
297    Remove,
298    /// Convert to explicit prefix
299    ConvertToPrefix,
300}
301
302/// Whitespace preservation configuration
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct WhitespacePreservationConfig {
305    /// Preserve significant whitespace
306    pub preserve_significant: bool,
307    /// Preserve formatting whitespace (indentation, etc.)
308    pub preserve_formatting: bool,
309    /// Normalize line endings
310    pub normalize_line_endings: bool,
311    /// Target line ending style
312    pub line_ending_style: LineEndingStyle,
313    /// Indentation style for formatted output
314    pub indentation_style: IndentationStyle,
315}
316
317impl Default for WhitespacePreservationConfig {
318    fn default() -> Self {
319        Self {
320            preserve_significant: true,
321            preserve_formatting: false,
322            normalize_line_endings: true,
323            line_ending_style: LineEndingStyle::Unix,
324            indentation_style: IndentationStyle::Spaces(2),
325        }
326    }
327}
328
329/// Line ending styles
330#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
331pub enum LineEndingStyle {
332    /// Unix style (\n)
333    Unix,
334    /// Windows style (\r\n)
335    Windows,
336    /// Classic Mac style (\r)
337    Mac,
338    /// Preserve original
339    Preserve,
340}
341
342/// Indentation styles
343#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
344pub enum IndentationStyle {
345    /// Spaces with specified count
346    Spaces(usize),
347    /// Tabs
348    Tabs,
349    /// No indentation
350    None,
351}
352
353/// Fidelity statistics collected during processing
354#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct FidelityStatistics {
356    /// Elements preserved by category
357    pub elements_preserved: HashMap<String, usize>,
358    /// Attributes preserved by category
359    pub attributes_preserved: HashMap<String, usize>,
360    /// Comments preserved
361    pub comments_preserved: usize,
362    /// Processing instructions preserved
363    pub processing_instructions_preserved: usize,
364    /// Extensions preserved by namespace
365    pub extensions_preserved: HashMap<String, usize>,
366    /// Namespaces processed
367    pub namespaces_processed: usize,
368    /// Fidelity processing time
369    pub processing_time: Duration,
370    /// Memory usage for fidelity features
371    pub memory_usage: usize,
372}
373
374impl Default for FidelityStatistics {
375    fn default() -> Self {
376        Self {
377            elements_preserved: HashMap::new(),
378            attributes_preserved: HashMap::new(),
379            comments_preserved: 0,
380            processing_instructions_preserved: 0,
381            extensions_preserved: HashMap::new(),
382            namespaces_processed: 0,
383            processing_time: Duration::ZERO,
384            memory_usage: 0,
385        }
386    }
387}
388
389/// Fidelity processing result
390#[derive(Debug, Clone, Serialize, Deserialize)]
391pub struct FidelityResult {
392    /// Whether fidelity processing was successful
393    pub success: bool,
394    /// Fidelity level achieved
395    pub achieved_level: PreservationLevel,
396    /// Processing statistics
397    pub statistics: FidelityStatistics,
398    /// Issues encountered during processing
399    pub issues: Vec<FidelityIssue>,
400    /// Recommendations for improving fidelity
401    pub recommendations: Vec<String>,
402}
403
404/// Fidelity processing issue
405#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct FidelityIssue {
407    /// Issue severity
408    pub severity: FidelitySeverity,
409    /// Issue category
410    pub category: String,
411    /// Issue description
412    pub message: String,
413    /// XML path where issue occurred
414    pub path: Option<String>,
415    /// Suggested resolution
416    pub suggestion: Option<String>,
417}
418
419/// Fidelity issue severity
420#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
421pub enum FidelitySeverity {
422    /// Critical issue that prevents fidelity
423    Critical,
424    /// Major issue that significantly impacts fidelity
425    Major,
426    /// Minor issue with minimal fidelity impact
427    Minor,
428    /// Informational message
429    Info,
430}
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435
436    #[test]
437    fn test_fidelity_config_default() {
438        let config = FidelityConfig::default();
439        assert_eq!(config.preservation_level, PreservationLevel::Standard);
440        assert!(!config.comment_preservation.enabled);
441        assert!(!config.processing_instruction_preservation);
442        assert!(config.extension_preservation.enabled);
443    }
444
445    #[test]
446    fn test_preservation_levels() {
447        let basic = PreservationLevel::Basic;
448        let standard = PreservationLevel::Standard;
449        let perfect = PreservationLevel::Perfect;
450        let custom = PreservationLevel::Custom;
451
452        assert_ne!(basic, standard);
453        assert_ne!(standard, perfect);
454        assert_ne!(perfect, custom);
455    }
456
457    #[test]
458    fn test_comment_preservation_config() {
459        let mut config = CommentPreservationConfig::default();
460        assert!(!config.enabled);
461
462        config.enabled = true;
463        assert!(config.enabled);
464        assert!(config.preserve_document_comments);
465        assert!(config.preserve_element_comments);
466        assert!(!config.preserve_inline_comments);
467    }
468
469    #[test]
470    fn test_extension_preservation() {
471        let config = ExtensionPreservationConfig::default();
472        assert!(config.enabled);
473        assert!(!config.known_extensions.is_empty());
474        assert_eq!(
475            config.unknown_extension_handling,
476            UnknownExtensionHandling::Preserve
477        );
478    }
479
480    #[test]
481    fn test_namespace_minimization() {
482        let strategy = NamespaceMinimizationStrategy::Aggressive;
483        match strategy {
484            NamespaceMinimizationStrategy::Aggressive => {}
485            _ => panic!("Expected Aggressive strategy"),
486        }
487    }
488}