ddex_builder/versions/
mod.rs

1//! Multi-version DDEX support and conversion
2//!
3//! This module provides comprehensive support for multiple DDEX ERN versions
4//! including 3.8.2, 4.2, and 4.3 with automatic conversion capabilities.
5//!
6//! # Supported Versions
7//! 
8//! - **ERN 3.8.2**: Legacy version with different namespaces and element structures
9//! - **ERN 4.2**: Intermediate version with some modern features
10//! - **ERN 4.3**: Current recommended version with full feature set
11//!
12//! # Version Conversion
13//!
14//! The system supports both upgrade and downgrade paths:
15//! - Upgrade: 3.8.2 → 4.2 → 4.3 (with feature enhancement)
16//! - Downgrade: 4.3 → 4.2 → 3.8.2 (with compatibility warnings)
17//!
18//! # Examples
19//!
20//! ```rust
21//! use ddex_builder::versions::{VersionConverter, DdexVersion};
22//! 
23//! let converter = VersionConverter::new();
24//! let result = converter.convert_version(ddex_xml, DdexVersion::Ern382, DdexVersion::Ern43)?;
25//! ```
26
27use crate::error::BuildError;
28use crate::presets::DdexVersion;
29use indexmap::IndexMap;
30use serde::{Deserialize, Serialize};
31
32mod ern_382;
33mod ern_42;
34mod ern_43;
35mod converter;
36
37pub use ern_382::*;
38pub use ern_42::*;
39pub use ern_43::*;
40pub use converter::{VersionConverter, ConversionResult as ConverterResult, ConversionReport as ConverterReport, ConversionWarning as ConverterWarning, ConversionWarningType};
41
42/// Version-specific DDEX metadata and constraints
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct VersionSpec {
45    /// Version identifier
46    pub version: DdexVersion,
47    /// XML namespace URI
48    pub namespace: String,
49    /// Schema location hint
50    pub schema_location: Option<String>,
51    /// Message schema version ID
52    pub message_schema_version_id: String,
53    /// Supported message types
54    pub supported_message_types: Vec<String>,
55    /// Version-specific element mappings
56    pub element_mappings: IndexMap<String, String>,
57    /// Required elements for this version
58    pub required_elements: Vec<String>,
59    /// Deprecated elements (for downgrades)
60    pub deprecated_elements: Vec<String>,
61    /// New elements (not in older versions)
62    pub new_elements: Vec<String>,
63    /// Namespace prefix mappings
64    pub namespace_prefixes: IndexMap<String, String>,
65}
66
67/// Version conversion result with detailed reporting
68#[derive(Debug, Clone)]
69pub struct ConversionResult {
70    /// Converted XML content
71    pub converted_xml: String,
72    /// Source version
73    pub source_version: DdexVersion,
74    /// Target version
75    pub target_version: DdexVersion,
76    /// Conversion report
77    pub report: ConversionReport,
78    /// Conversion metadata
79    pub metadata: ConversionMetadata,
80}
81
82/// Detailed conversion report
83#[derive(Debug, Clone)]
84pub struct ConversionReport {
85    /// Successful conversions
86    pub conversions: Vec<ElementConversion>,
87    /// Warnings generated during conversion
88    pub warnings: Vec<ConversionWarning>,
89    /// Errors encountered (non-fatal)
90    pub errors: Vec<ConversionError>,
91    /// Elements that couldn't be converted
92    pub unconvertible_elements: Vec<String>,
93    /// Data loss warnings
94    pub data_loss_warnings: Vec<String>,
95    /// Feature compatibility notes
96    pub compatibility_notes: Vec<String>,
97}
98
99/// Individual element conversion record
100#[derive(Debug, Clone)]
101pub struct ElementConversion {
102    /// Original element path
103    pub source_path: String,
104    /// Converted element path
105    pub target_path: String,
106    /// Conversion type performed
107    pub conversion_type: ConversionType,
108    /// Additional notes
109    pub notes: Option<String>,
110}
111
112/// Type of conversion performed
113#[derive(Debug, Clone, PartialEq)]
114pub enum ConversionType {
115    /// Direct mapping (same element name)
116    DirectMapping,
117    /// Element renamed
118    Renamed { old_name: String, new_name: String },
119    /// Element structure changed
120    Restructured { description: String },
121    /// New element added (upgrade)
122    Added { default_value: Option<String> },
123    /// Element removed (downgrade)
124    Removed { reason: String },
125    /// Element moved to different location
126    Moved { old_path: String, new_path: String },
127    /// Complex transformation
128    Transformed { description: String },
129}
130
131/// Conversion warning
132#[derive(Debug, Clone)]
133pub struct ConversionWarning {
134    /// Warning code
135    pub code: String,
136    /// Warning message
137    pub message: String,
138    /// Element path that caused warning
139    pub element_path: Option<String>,
140    /// Suggested action
141    pub suggestion: Option<String>,
142    /// Impact level
143    pub impact: ImpactLevel,
144}
145
146/// Conversion error (non-fatal)
147#[derive(Debug, Clone)]
148pub struct ConversionError {
149    /// Error code
150    pub code: String,
151    /// Error message
152    pub message: String,
153    /// Element path that caused error
154    pub element_path: String,
155    /// Fallback action taken
156    pub fallback: Option<String>,
157}
158
159/// Impact level of warnings/changes
160#[derive(Debug, Clone, PartialEq)]
161pub enum ImpactLevel {
162    /// Low impact, cosmetic changes
163    Low,
164    /// Medium impact, functional changes
165    Medium,
166    /// High impact, potential data loss
167    High,
168    /// Critical impact, breaking changes
169    Critical,
170}
171
172/// Conversion metadata
173#[derive(Debug, Clone)]
174pub struct ConversionMetadata {
175    /// Conversion timestamp
176    pub converted_at: chrono::DateTime<chrono::Utc>,
177    /// Conversion duration
178    pub conversion_time: std::time::Duration,
179    /// Number of elements processed
180    pub elements_processed: usize,
181    /// Number of warnings generated
182    pub warning_count: usize,
183    /// Number of errors encountered
184    pub error_count: usize,
185    /// Estimated fidelity percentage
186    pub fidelity_percentage: f64,
187}
188
189/// Version detection result
190#[derive(Debug, Clone)]
191pub struct VersionDetection {
192    /// Detected version
193    pub detected_version: DdexVersion,
194    /// Confidence level (0.0 - 1.0)
195    pub confidence: f64,
196    /// Detection clues found
197    pub clues: Vec<DetectionClue>,
198    /// Ambiguities encountered
199    pub ambiguities: Vec<String>,
200}
201
202/// Clue used for version detection
203#[derive(Debug, Clone)]
204pub struct DetectionClue {
205    /// Type of clue
206    pub clue_type: ClueType,
207    /// Evidence found
208    pub evidence: String,
209    /// Confidence weight
210    pub weight: f64,
211}
212
213/// Type of version detection clue
214#[derive(Debug, Clone)]
215pub enum ClueType {
216    /// XML namespace
217    Namespace,
218    /// Schema location
219    SchemaLocation,
220    /// Message schema version ID
221    MessageSchemaVersionId,
222    /// Presence of version-specific elements
223    VersionSpecificElement,
224    /// Element structure patterns
225    StructuralPattern,
226    /// Namespace prefix usage
227    NamespacePrefix,
228}
229
230/// Version compatibility matrix
231#[derive(Debug, Clone)]
232pub struct CompatibilityMatrix {
233    /// Supported conversion paths
234    pub conversion_paths: Vec<ConversionPath>,
235    /// Feature compatibility table
236    pub feature_compatibility: IndexMap<String, FeatureSupport>,
237    /// Recommended conversion strategies
238    pub recommended_strategies: Vec<ConversionStrategy>,
239}
240
241/// Single conversion path between versions
242#[derive(Debug, Clone)]
243pub struct ConversionPath {
244    /// Source version
245    pub from: DdexVersion,
246    /// Target version
247    pub to: DdexVersion,
248    /// Conversion difficulty
249    pub difficulty: ConversionDifficulty,
250    /// Expected fidelity
251    pub fidelity: f64,
252    /// Major changes involved
253    pub major_changes: Vec<String>,
254    /// Recommended for production use
255    pub production_ready: bool,
256}
257
258/// Difficulty level of conversion
259#[derive(Debug, Clone, PartialEq)]
260pub enum ConversionDifficulty {
261    /// Simple mapping changes
262    Trivial,
263    /// Moderate structural changes
264    Moderate,
265    /// Complex transformations required
266    Complex,
267    /// Significant data model changes
268    Challenging,
269}
270
271/// Feature support across versions
272#[derive(Debug, Clone)]
273pub struct FeatureSupport {
274    /// Feature name
275    pub feature: String,
276    /// Support in ERN 3.8.2
277    pub ern_382: SupportLevel,
278    /// Support in ERN 4.2
279    pub ern_42: SupportLevel,
280    /// Support in ERN 4.3
281    pub ern_43: SupportLevel,
282    /// Migration notes
283    pub migration_notes: Option<String>,
284}
285
286/// Level of feature support
287#[derive(Debug, Clone, PartialEq)]
288pub enum SupportLevel {
289    /// Fully supported
290    Full,
291    /// Partially supported
292    Partial,
293    /// Not supported
294    None,
295    /// Deprecated
296    Deprecated,
297    /// New in this version
298    New,
299}
300
301/// Conversion strategy recommendation
302#[derive(Debug, Clone)]
303pub struct ConversionStrategy {
304    /// Strategy name
305    pub name: String,
306    /// Description
307    pub description: String,
308    /// Applicable scenarios
309    pub scenarios: Vec<String>,
310    /// Steps involved
311    pub steps: Vec<String>,
312    /// Expected outcomes
313    pub outcomes: Vec<String>,
314    /// Risk level
315    pub risk_level: RiskLevel,
316}
317
318/// Risk level of conversion strategy
319#[derive(Debug, Clone, PartialEq)]
320pub enum RiskLevel {
321    /// Low risk, safe for production
322    Low,
323    /// Medium risk, testing recommended
324    Medium,
325    /// High risk, careful validation needed
326    High,
327    /// Very high risk, not recommended
328    VeryHigh,
329}
330
331/// Main version management interface
332#[derive(Debug, Clone)]
333pub struct VersionManager {
334    /// Available version specifications
335    version_specs: IndexMap<DdexVersion, VersionSpec>,
336    /// Compatibility matrix
337    compatibility: CompatibilityMatrix,
338    /// Default conversion options
339    default_options: ConversionOptions,
340}
341
342/// Options for version conversion
343#[derive(Debug, Clone)]
344pub struct ConversionOptions {
345    /// Allow lossy conversions
346    pub allow_lossy: bool,
347    /// Generate detailed reports
348    pub detailed_reports: bool,
349    /// Preserve unknown elements
350    pub preserve_unknown: bool,
351    /// Add conversion metadata
352    pub add_metadata: bool,
353    /// Preserve XML comments during conversion
354    pub preserve_comments: bool,
355    /// Validation level after conversion
356    pub validation_level: ValidationLevel,
357    /// Custom element mappings
358    pub custom_mappings: IndexMap<String, String>,
359}
360
361/// Validation level for converted content
362#[derive(Debug, Clone, PartialEq)]
363pub enum ValidationLevel {
364    /// No validation
365    None,
366    /// Basic structure validation
367    Basic,
368    /// Schema validation
369    Schema,
370    /// Full semantic validation
371    Full,
372}
373
374impl Default for ConversionOptions {
375    fn default() -> Self {
376        Self {
377            allow_lossy: false,
378            detailed_reports: true,
379            preserve_unknown: false,
380            add_metadata: true,
381            preserve_comments: false,
382            validation_level: ValidationLevel::Schema,
383            custom_mappings: IndexMap::new(),
384        }
385    }
386}
387
388impl VersionManager {
389    /// Create a new version manager with default specifications
390    pub fn new() -> Self {
391        Self {
392            version_specs: Self::load_default_specs(),
393            compatibility: Self::build_compatibility_matrix(),
394            default_options: ConversionOptions::default(),
395        }
396    }
397    
398    /// Get version specification
399    pub fn get_version_spec(&self, version: DdexVersion) -> Option<&VersionSpec> {
400        self.version_specs.get(&version)
401    }
402    
403    /// Detect version from XML content
404    pub fn detect_version(&self, xml_content: &str) -> Result<VersionDetection, BuildError> {
405        let mut clues = Vec::new();
406        let mut version_scores = IndexMap::new();
407        
408        // Initialize scores
409        for version in [DdexVersion::Ern382, DdexVersion::Ern42, DdexVersion::Ern43] {
410            version_scores.insert(version, 0.0);
411        }
412        
413        // Analyze namespace
414        if let Some(namespace) = self.extract_namespace(xml_content) {
415            clues.push(DetectionClue {
416                clue_type: ClueType::Namespace,
417                evidence: namespace.clone(),
418                weight: 0.8,
419            });
420            
421            // Score based on namespace
422            for (version, spec) in &self.version_specs {
423                if spec.namespace == namespace {
424                    *version_scores.get_mut(version).unwrap() += 0.8;
425                }
426            }
427        }
428        
429        // Analyze message schema version ID
430        if let Some(schema_version) = self.extract_message_schema_version(xml_content) {
431            clues.push(DetectionClue {
432                clue_type: ClueType::MessageSchemaVersionId,
433                evidence: schema_version.clone(),
434                weight: 0.9,
435            });
436            
437            // Score based on schema version
438            for (version, spec) in &self.version_specs {
439                if spec.message_schema_version_id == schema_version {
440                    *version_scores.get_mut(version).unwrap() += 0.9;
441                }
442            }
443        }
444        
445        // Look for version-specific elements
446        for (version, spec) in &self.version_specs {
447            for element in &spec.new_elements {
448                if xml_content.contains(&format!("<{}", element)) {
449                    clues.push(DetectionClue {
450                        clue_type: ClueType::VersionSpecificElement,
451                        evidence: element.clone(),
452                        weight: 0.6,
453                    });
454                    *version_scores.get_mut(version).unwrap() += 0.6;
455                }
456            }
457        }
458        
459        // Determine best match
460        let (detected_version, confidence) = version_scores
461            .into_iter()
462            .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
463            .unwrap();
464        
465        let normalized_confidence = (confidence / 2.5_f64).min(1.0_f64); // Normalize to 0-1
466        
467        Ok(VersionDetection {
468            detected_version,
469            confidence: normalized_confidence,
470            clues,
471            ambiguities: Vec::new(), // TODO: Implement ambiguity detection
472        })
473    }
474    
475    /// Check if conversion is supported between versions
476    pub fn is_conversion_supported(&self, from: DdexVersion, to: DdexVersion) -> bool {
477        self.compatibility.conversion_paths.iter()
478            .any(|path| path.from == from && path.to == to)
479    }
480    
481    /// Get conversion path information
482    pub fn get_conversion_path(&self, from: DdexVersion, to: DdexVersion) -> Option<&ConversionPath> {
483        self.compatibility.conversion_paths.iter()
484            .find(|path| path.from == from && path.to == to)
485    }
486    
487    /// Get feature compatibility information
488    pub fn get_feature_compatibility(&self, feature: &str) -> Option<&FeatureSupport> {
489        self.compatibility.feature_compatibility.get(feature)
490    }
491    
492    /// Get recommended conversion strategy
493    pub fn get_recommended_strategy(&self, from: DdexVersion, to: DdexVersion) -> Option<&ConversionStrategy> {
494        let scenario = format!("{:?} to {:?}", from, to);
495        self.compatibility.recommended_strategies.iter()
496            .find(|strategy| strategy.scenarios.contains(&scenario))
497    }
498    
499    // Private helper methods
500    
501    fn load_default_specs() -> IndexMap<DdexVersion, VersionSpec> {
502        let mut specs = IndexMap::new();
503        
504        specs.insert(DdexVersion::Ern382, ern_382::get_version_spec());
505        specs.insert(DdexVersion::Ern42, ern_42::get_version_spec());
506        specs.insert(DdexVersion::Ern43, ern_43::get_version_spec());
507        
508        specs
509    }
510    
511    fn build_compatibility_matrix() -> CompatibilityMatrix {
512        let conversion_paths = vec![
513            // Upgrade paths
514            ConversionPath {
515                from: DdexVersion::Ern382,
516                to: DdexVersion::Ern42,
517                difficulty: ConversionDifficulty::Moderate,
518                fidelity: 0.85,
519                major_changes: vec![
520                    "Namespace migration".to_string(),
521                    "Element structure updates".to_string(),
522                    "New optional elements".to_string(),
523                ],
524                production_ready: true,
525            },
526            ConversionPath {
527                from: DdexVersion::Ern42,
528                to: DdexVersion::Ern43,
529                difficulty: ConversionDifficulty::Trivial,
530                fidelity: 0.95,
531                major_changes: vec![
532                    "Minor element additions".to_string(),
533                    "Enhanced validation rules".to_string(),
534                ],
535                production_ready: true,
536            },
537            ConversionPath {
538                from: DdexVersion::Ern382,
539                to: DdexVersion::Ern43,
540                difficulty: ConversionDifficulty::Complex,
541                fidelity: 0.80,
542                major_changes: vec![
543                    "Major namespace changes".to_string(),
544                    "Significant structural updates".to_string(),
545                    "New required elements".to_string(),
546                ],
547                production_ready: true,
548            },
549            // Downgrade paths
550            ConversionPath {
551                from: DdexVersion::Ern43,
552                to: DdexVersion::Ern42,
553                difficulty: ConversionDifficulty::Moderate,
554                fidelity: 0.90,
555                major_changes: vec![
556                    "Remove newer elements".to_string(),
557                    "Downgrade validation rules".to_string(),
558                ],
559                production_ready: true,
560            },
561            ConversionPath {
562                from: DdexVersion::Ern42,
563                to: DdexVersion::Ern382,
564                difficulty: ConversionDifficulty::Challenging,
565                fidelity: 0.75,
566                major_changes: vec![
567                    "Legacy namespace mapping".to_string(),
568                    "Remove modern elements".to_string(),
569                    "Structural downgrade".to_string(),
570                ],
571                production_ready: false,
572            },
573            ConversionPath {
574                from: DdexVersion::Ern43,
575                to: DdexVersion::Ern382,
576                difficulty: ConversionDifficulty::Challenging,
577                fidelity: 0.70,
578                major_changes: vec![
579                    "Major structural downgrade".to_string(),
580                    "Significant feature removal".to_string(),
581                    "Legacy compatibility layer".to_string(),
582                ],
583                production_ready: false,
584            },
585        ];
586        
587        let feature_compatibility = Self::build_feature_compatibility();
588        let recommended_strategies = Self::build_recommended_strategies();
589        
590        CompatibilityMatrix {
591            conversion_paths,
592            feature_compatibility,
593            recommended_strategies,
594        }
595    }
596    
597    fn build_feature_compatibility() -> IndexMap<String, FeatureSupport> {
598        let mut features = IndexMap::new();
599        
600        features.insert("ResourceReference".to_string(), FeatureSupport {
601            feature: "Resource Reference Elements".to_string(),
602            ern_382: SupportLevel::Partial,
603            ern_42: SupportLevel::Full,
604            ern_43: SupportLevel::Full,
605            migration_notes: Some("Enhanced in 4.2 with better linking".to_string()),
606        });
607        
608        features.insert("DetailedDealTerms".to_string(), FeatureSupport {
609            feature: "Detailed Deal Terms".to_string(),
610            ern_382: SupportLevel::None,
611            ern_42: SupportLevel::Partial,
612            ern_43: SupportLevel::Full,
613            migration_notes: Some("New detailed terms structure in 4.2+".to_string()),
614        });
615        
616        features.insert("EnhancedMetadata".to_string(), FeatureSupport {
617            feature: "Enhanced Metadata Fields".to_string(),
618            ern_382: SupportLevel::None,
619            ern_42: SupportLevel::None,
620            ern_43: SupportLevel::New,
621            migration_notes: Some("Completely new in 4.3".to_string()),
622        });
623        
624        features.insert("DeprecatedElements".to_string(), FeatureSupport {
625            feature: "Legacy Deprecated Elements".to_string(),
626            ern_382: SupportLevel::Full,
627            ern_42: SupportLevel::Deprecated,
628            ern_43: SupportLevel::None,
629            migration_notes: Some("Removed in 4.3, use modern equivalents".to_string()),
630        });
631        
632        features
633    }
634    
635    fn build_recommended_strategies() -> Vec<ConversionStrategy> {
636        vec![
637            ConversionStrategy {
638                name: "Conservative Upgrade".to_string(),
639                description: "Step-by-step version upgrade with validation".to_string(),
640                scenarios: vec!["Ern382 to Ern43".to_string()],
641                steps: vec![
642                    "Validate source ERN 3.8.2 message".to_string(),
643                    "Convert 3.8.2 → 4.2 with warnings".to_string(),
644                    "Validate intermediate 4.2 message".to_string(),
645                    "Convert 4.2 → 4.3 with enhancements".to_string(),
646                    "Final validation and report".to_string(),
647                ],
648                outcomes: vec![
649                    "High-fidelity conversion".to_string(),
650                    "Detailed conversion report".to_string(),
651                    "Step-by-step validation".to_string(),
652                ],
653                risk_level: RiskLevel::Low,
654            },
655            ConversionStrategy {
656                name: "Direct Upgrade".to_string(),
657                description: "Direct conversion between versions".to_string(),
658                scenarios: vec!["Ern42 to Ern43".to_string()],
659                steps: vec![
660                    "Validate source message".to_string(),
661                    "Apply direct conversion mappings".to_string(),
662                    "Add new optional elements".to_string(),
663                    "Validate target message".to_string(),
664                ],
665                outcomes: vec![
666                    "Fast conversion".to_string(),
667                    "Minimal data transformation".to_string(),
668                ],
669                risk_level: RiskLevel::Low,
670            },
671        ]
672    }
673    
674    fn extract_namespace(&self, xml_content: &str) -> Option<String> {
675        // Simple regex to extract namespace from XML
676        let re = regex::Regex::new(r#"xmlns="([^"]+)""#).ok()?;
677        re.captures(xml_content)?
678            .get(1)
679            .map(|m| m.as_str().to_string())
680    }
681    
682    fn extract_message_schema_version(&self, xml_content: &str) -> Option<String> {
683        let re = regex::Regex::new(r#"MessageSchemaVersionId="([^"]+)""#).ok()?;
684        re.captures(xml_content)?
685            .get(1)
686            .map(|m| m.as_str().to_string())
687    }
688}
689
690impl Default for VersionManager {
691    fn default() -> Self {
692        Self::new()
693    }
694}
695
696
697impl std::fmt::Display for ConversionDifficulty {
698    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
699        match self {
700            ConversionDifficulty::Trivial => write!(f, "Trivial"),
701            ConversionDifficulty::Moderate => write!(f, "Moderate"),
702            ConversionDifficulty::Complex => write!(f, "Complex"),
703            ConversionDifficulty::Challenging => write!(f, "Challenging"),
704        }
705    }
706}
707
708impl std::fmt::Display for SupportLevel {
709    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
710        match self {
711            SupportLevel::Full => write!(f, "Full"),
712            SupportLevel::Partial => write!(f, "Partial"),
713            SupportLevel::None => write!(f, "None"),
714            SupportLevel::Deprecated => write!(f, "Deprecated"),
715            SupportLevel::New => write!(f, "New"),
716        }
717    }
718}
719
720impl std::fmt::Display for ImpactLevel {
721    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
722        match self {
723            ImpactLevel::Low => write!(f, "Low"),
724            ImpactLevel::Medium => write!(f, "Medium"),
725            ImpactLevel::High => write!(f, "High"),
726            ImpactLevel::Critical => write!(f, "Critical"),
727        }
728    }
729}
730
731/// Utility functions for version handling
732pub mod utils {
733    use super::*;
734    
735    /// Get all supported versions
736    pub fn supported_versions() -> Vec<DdexVersion> {
737        vec![DdexVersion::Ern382, DdexVersion::Ern42, DdexVersion::Ern43]
738    }
739    
740    /// Check if version is legacy
741    pub fn is_legacy_version(version: DdexVersion) -> bool {
742        matches!(version, DdexVersion::Ern382)
743    }
744    
745    /// Check if version is modern
746    pub fn is_modern_version(version: DdexVersion) -> bool {
747        matches!(version, DdexVersion::Ern43)
748    }
749    
750    /// Get version release date
751    pub fn get_version_release_date(version: DdexVersion) -> chrono::NaiveDate {
752        match version {
753            DdexVersion::Ern382 => chrono::NaiveDate::from_ymd_opt(2018, 5, 1).unwrap(),
754            DdexVersion::Ern42 => chrono::NaiveDate::from_ymd_opt(2020, 8, 15).unwrap(),
755            DdexVersion::Ern43 => chrono::NaiveDate::from_ymd_opt(2023, 3, 1).unwrap(),
756            DdexVersion::Ern41 => chrono::NaiveDate::from_ymd_opt(2019, 11, 15).unwrap(),
757        }
758    }
759    
760    /// Get version description
761    pub fn get_version_description(version: DdexVersion) -> String {
762        match version {
763            DdexVersion::Ern382 => "Legacy version with basic features".to_string(),
764            DdexVersion::Ern42 => "Intermediate version with enhanced features".to_string(),
765            DdexVersion::Ern43 => "Current version with full feature set".to_string(),
766            DdexVersion::Ern41 => "Early 4.x version".to_string(),
767        }
768    }
769}