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