ddex_builder/versions/
ern_382.rs

1//! ERN 3.8.2 (Legacy) version specification and handling
2//!
3//! ERN 3.8.2 is a legacy DDEX version that predates many modern features.
4//! This module handles the specific requirements and constraints of ERN 3.8.2.
5
6use super::*;
7use crate::presets::DdexVersion;
8
9/// Get ERN 3.8.2 version specification
10pub fn get_version_spec() -> VersionSpec {
11    let mut element_mappings = IndexMap::new();
12    let mut namespace_prefixes = IndexMap::new();
13    
14    // Legacy namespace mappings
15    namespace_prefixes.insert("ern".to_string(), "http://ddex.net/xml/ern/382".to_string());
16    namespace_prefixes.insert("avs".to_string(), "http://ddex.net/xml/avs/avs".to_string());
17    
18    // Common element mappings from 3.8.2 to modern versions
19    element_mappings.insert("NewReleaseMessage".to_string(), "NewReleaseMessage".to_string());
20    element_mappings.insert("MessageHeader".to_string(), "MessageHeader".to_string());
21    element_mappings.insert("MessageId".to_string(), "MessageId".to_string());
22    element_mappings.insert("MessageSender".to_string(), "MessageSender".to_string());
23    element_mappings.insert("MessageRecipient".to_string(), "MessageRecipient".to_string());
24    element_mappings.insert("MessageCreatedDateTime".to_string(), "MessageCreatedDateTime".to_string());
25    
26    // Resource-related mappings
27    element_mappings.insert("ResourceList".to_string(), "ResourceList".to_string());
28    element_mappings.insert("SoundRecording".to_string(), "SoundRecording".to_string());
29    element_mappings.insert("ResourceReference".to_string(), "ResourceReference".to_string());
30    element_mappings.insert("ResourceId".to_string(), "ResourceId".to_string());
31    element_mappings.insert("ReferenceTitle".to_string(), "ReferenceTitle".to_string());
32    element_mappings.insert("DisplayArtist".to_string(), "DisplayArtist".to_string());
33    element_mappings.insert("ISRC".to_string(), "ISRC".to_string());
34    element_mappings.insert("Duration".to_string(), "Duration".to_string());
35    
36    // Release-related mappings
37    element_mappings.insert("ReleaseList".to_string(), "ReleaseList".to_string());
38    element_mappings.insert("Release".to_string(), "Release".to_string());
39    element_mappings.insert("ReleaseId".to_string(), "ReleaseId".to_string());
40    element_mappings.insert("Title".to_string(), "Title".to_string());
41    element_mappings.insert("DisplayArtist".to_string(), "DisplayArtist".to_string());
42    element_mappings.insert("LabelName".to_string(), "LabelName".to_string());
43    element_mappings.insert("UPC".to_string(), "UPC".to_string());
44    element_mappings.insert("ReleaseDate".to_string(), "ReleaseDate".to_string());
45    element_mappings.insert("Genre".to_string(), "Genre".to_string());
46    
47    // Deal-related mappings (limited in 3.8.2)
48    element_mappings.insert("DealList".to_string(), "DealList".to_string());
49    element_mappings.insert("ReleaseDeal".to_string(), "ReleaseDeal".to_string());
50    element_mappings.insert("DealTerms".to_string(), "DealTerms".to_string());
51    element_mappings.insert("CommercialModelType".to_string(), "CommercialModelType".to_string());
52    element_mappings.insert("TerritoryCode".to_string(), "TerritoryCode".to_string());
53    
54    // Legacy-specific elements that don't exist in newer versions
55    element_mappings.insert("LegacyTechnicalDetails".to_string(), "TechnicalResourceDetails".to_string());
56    element_mappings.insert("BasicPrice".to_string(), "Price".to_string());
57    element_mappings.insert("SimpleTerritory".to_string(), "Territory".to_string());
58    
59    VersionSpec {
60        version: DdexVersion::Ern382,
61        namespace: "http://ddex.net/xml/ern/382".to_string(),
62        schema_location: Some("http://ddex.net/xml/ern/382 http://ddex.net/xml/ern/382/release-notification.xsd".to_string()),
63        message_schema_version_id: "ern/382".to_string(),
64        supported_message_types: vec![
65            "NewReleaseMessage".to_string(),
66            "CatalogListMessage".to_string(),
67        ],
68        element_mappings,
69        required_elements: vec![
70            "MessageId".to_string(),
71            "MessageSender".to_string(),
72            "MessageRecipient".to_string(),
73            "MessageCreatedDateTime".to_string(),
74            "ResourceList".to_string(),
75            "ReleaseList".to_string(),
76        ],
77        deprecated_elements: vec![
78            // These elements exist in 3.8.2 but are deprecated in newer versions
79            "LegacyTechnicalDetails".to_string(),
80            "BasicPrice".to_string(),
81            "SimpleTerritory".to_string(),
82            "OldStyleGenre".to_string(),
83        ],
84        new_elements: vec![
85            // Elements that are new in 3.8.2 (none, as this is the baseline)
86        ],
87        namespace_prefixes,
88    }
89}
90
91/// ERN 3.8.2 specific constraints and validation rules
92pub struct Ern382Constraints {
93    /// Maximum allowed resources per release
94    pub max_resources_per_release: usize,
95    /// Supported image formats
96    pub supported_image_formats: Vec<String>,
97    /// Supported audio formats
98    pub supported_audio_formats: Vec<String>,
99    /// Maximum deal complexity
100    pub max_deal_terms: usize,
101}
102
103impl Default for Ern382Constraints {
104    fn default() -> Self {
105        Self {
106            max_resources_per_release: 100,
107            supported_image_formats: vec!["JPEG".to_string(), "PNG".to_string()],
108            supported_audio_formats: vec!["MP3".to_string(), "WAV".to_string(), "FLAC".to_string()],
109            max_deal_terms: 10,
110        }
111    }
112}
113
114/// Get ERN 3.8.2 specific element validation rules
115pub fn get_validation_rules() -> IndexMap<String, ValidationRule> {
116    let mut rules = IndexMap::new();
117    
118    // Message ID format (simpler in 3.8.2)
119    rules.insert("MessageId".to_string(), ValidationRule {
120        rule_type: ValidationRuleType::Pattern,
121        pattern: Some("^[A-Za-z0-9_-]{1,50}$".to_string()),
122        enum_values: None,
123        required: true,
124        description: "Message identifier format for ERN 3.8.2".to_string(),
125    });
126    
127    // ISRC format (standard)
128    rules.insert("ISRC".to_string(), ValidationRule {
129        rule_type: ValidationRuleType::Pattern,
130        pattern: Some("^[A-Z]{2}[A-Z0-9]{3}\\d{7}$".to_string()),
131        enum_values: None,
132        required: true,
133        description: "ISRC format validation".to_string(),
134    });
135    
136    // UPC format (12 digits)
137    rules.insert("UPC".to_string(), ValidationRule {
138        rule_type: ValidationRuleType::Pattern,
139        pattern: Some("^\\d{12}$".to_string()),
140        enum_values: None,
141        required: false,
142        description: "UPC format validation".to_string(),
143    });
144    
145    // Duration format (simpler in 3.8.2)
146    rules.insert("Duration".to_string(), ValidationRule {
147        rule_type: ValidationRuleType::Pattern,
148        pattern: Some("^PT\\d+M\\d+S$".to_string()),
149        enum_values: None,
150        required: false,
151        description: "Duration in PTnMnS format".to_string(),
152    });
153    
154    // Release date format
155    rules.insert("ReleaseDate".to_string(), ValidationRule {
156        rule_type: ValidationRuleType::Pattern,
157        pattern: Some("^\\d{4}-\\d{2}-\\d{2}$".to_string()),
158        enum_values: None,
159        required: false,
160        description: "Release date in YYYY-MM-DD format".to_string(),
161    });
162    
163    // Territory code (simpler set in 3.8.2)
164    rules.insert("TerritoryCode".to_string(), ValidationRule {
165        rule_type: ValidationRuleType::Enum,
166        pattern: None,
167        enum_values: Some(vec![
168            "US".to_string(),
169            "GB".to_string(),
170            "DE".to_string(),
171            "FR".to_string(),
172            "JP".to_string(),
173            "Worldwide".to_string(),
174        ]),
175        required: true,
176        description: "Supported territory codes in ERN 3.8.2".to_string(),
177    });
178    
179    rules
180}
181
182/// Validation rule definition
183#[derive(Debug, Clone)]
184pub struct ValidationRule {
185    /// Type of validation rule
186    pub rule_type: ValidationRuleType,
187    /// Regex pattern for pattern validation
188    pub pattern: Option<String>,
189    /// Enum values for enum validation
190    pub enum_values: Option<Vec<String>>,
191    /// Whether field is required
192    pub required: bool,
193    /// Description of the rule
194    pub description: String,
195}
196
197/// Type of validation rule
198#[derive(Debug, Clone)]
199pub enum ValidationRuleType {
200    /// Pattern-based validation
201    Pattern,
202    /// Enum value validation
203    Enum,
204    /// Length validation
205    Length { min: Option<usize>, max: Option<usize> },
206    /// Custom validation function
207    Custom(String),
208}
209
210/// Get ERN 3.8.2 namespace mappings
211pub fn get_namespace_mappings() -> IndexMap<String, String> {
212    let mut mappings = IndexMap::new();
213    
214    mappings.insert("ern".to_string(), "http://ddex.net/xml/ern/382".to_string());
215    mappings.insert("avs".to_string(), "http://ddex.net/xml/avs/avs".to_string());
216    mappings.insert("drm".to_string(), "http://ddex.net/xml/drm/drm".to_string());
217    
218    mappings
219}
220
221/// Get ERN 3.8.2 specific XML template
222pub fn get_xml_template() -> &'static str {
223    r#"<?xml version="1.0" encoding="UTF-8"?>
224<NewReleaseMessage xmlns="http://ddex.net/xml/ern/382" 
225                  xmlns:avs="http://ddex.net/xml/avs/avs"
226                  MessageSchemaVersionId="ern/382">
227    <MessageHeader>
228        <MessageId>{message_id}</MessageId>
229        <MessageSender>
230            <PartyName>{sender_name}</PartyName>
231        </MessageSender>
232        <MessageRecipient>
233            <PartyName>{recipient_name}</PartyName>
234        </MessageRecipient>
235        <MessageCreatedDateTime>{created_datetime}</MessageCreatedDateTime>
236    </MessageHeader>
237    
238    <ResourceList>
239        <!-- Resources will be populated here -->
240    </ResourceList>
241    
242    <ReleaseList>
243        <!-- Releases will be populated here -->
244    </ReleaseList>
245    
246    <DealList>
247        <!-- Deals will be populated here -->
248    </DealList>
249</NewReleaseMessage>"#
250}
251
252/// ERN 3.8.2 specific element builders
253pub mod builders {
254    use super::*;
255    use crate::ast::{Element, Node};
256    
257    /// Build ERN 3.8.2 message header
258    pub fn build_message_header(
259        message_id: &str,
260        sender_name: &str,
261        recipient_name: &str,
262        created_datetime: &str,
263    ) -> Element {
264        let mut header = Element::new("MessageHeader");
265        
266        // Message ID
267        let mut msg_id = Element::new("MessageId");
268        msg_id.add_text(message_id);
269        header.add_child(msg_id);
270        
271        // Message Sender
272        let mut sender = Element::new("MessageSender");
273        let mut sender_party = Element::new("PartyName");
274        sender_party.add_text(sender_name);
275        sender.add_child(sender_party);
276        header.add_child(sender);
277        
278        // Message Recipient
279        let mut recipient = Element::new("MessageRecipient");
280        let mut recipient_party = Element::new("PartyName");
281        recipient_party.add_text(recipient_name);
282        recipient.add_child(recipient_party);
283        header.add_child(recipient);
284        
285        // Created DateTime
286        let mut created = Element::new("MessageCreatedDateTime");
287        created.add_text(created_datetime);
288        header.add_child(created);
289        
290        header
291    }
292    
293    /// Build ERN 3.8.2 sound recording resource
294    pub fn build_sound_recording(
295        resource_ref: &str,
296        resource_id: &str,
297        title: &str,
298        artist: &str,
299        isrc: &str,
300        duration: &str,
301    ) -> Element {
302        let mut sound_recording = Element::new("SoundRecording");
303        
304        // Resource Reference
305        let mut res_ref = Element::new("ResourceReference");
306        res_ref.add_text(resource_ref);
307        sound_recording.add_child(res_ref);
308        
309        // Type (always SoundRecording in this context)
310        let mut res_type = Element::new("Type");
311        res_type.add_text("SoundRecording");
312        sound_recording.add_child(res_type);
313        
314        // Resource ID
315        let mut res_id = Element::new("ResourceId");
316        res_id.add_text(resource_id);
317        sound_recording.add_child(res_id);
318        
319        // Reference Title
320        let mut ref_title = Element::new("ReferenceTitle");
321        ref_title.add_text(title);
322        sound_recording.add_child(ref_title);
323        
324        // Display Artist
325        let mut display_artist = Element::new("DisplayArtist");
326        display_artist.add_text(artist);
327        sound_recording.add_child(display_artist);
328        
329        // ISRC
330        let mut isrc_elem = Element::new("ISRC");
331        isrc_elem.add_text(isrc);
332        sound_recording.add_child(isrc_elem);
333        
334        // Duration
335        let mut duration_elem = Element::new("Duration");
336        duration_elem.add_text(duration);
337        sound_recording.add_child(duration_elem);
338        
339        sound_recording
340    }
341    
342    /// Build ERN 3.8.2 release
343    pub fn build_release(
344        release_ref: &str,
345        release_id: &str,
346        title: &str,
347        artist: &str,
348        label: &str,
349        upc: Option<&str>,
350        release_date: Option<&str>,
351        genre: Option<&str>,
352        resource_refs: &[String],
353    ) -> Element {
354        let mut release = Element::new("Release");
355        
356        // Release Reference
357        let mut rel_ref = Element::new("ReleaseReference");
358        rel_ref.add_text(release_ref);
359        release.add_child(rel_ref);
360        
361        // Release ID
362        let mut rel_id = Element::new("ReleaseId");
363        rel_id.add_text(release_id);
364        release.add_child(rel_id);
365        
366        // Release Type (default to Album for ERN 3.8.2)
367        let mut rel_type = Element::new("ReleaseType");
368        rel_type.add_text("Album");
369        release.add_child(rel_type);
370        
371        // Title
372        let mut title_elem = Element::new("Title");
373        title_elem.add_text(title);
374        release.add_child(title_elem);
375        
376        // Display Artist
377        let mut artist_elem = Element::new("DisplayArtist");
378        artist_elem.add_text(artist);
379        release.add_child(artist_elem);
380        
381        // Label Name
382        let mut label_elem = Element::new("LabelName");
383        label_elem.add_text(label);
384        release.add_child(label_elem);
385        
386        // UPC (optional)
387        if let Some(upc_val) = upc {
388            let mut upc_elem = Element::new("UPC");
389            upc_elem.add_text(upc_val);
390            release.add_child(upc_elem);
391        }
392        
393        // Release Date (optional)
394        if let Some(date) = release_date {
395            let mut date_elem = Element::new("ReleaseDate");
396            date_elem.add_text(date);
397            release.add_child(date_elem);
398        }
399        
400        // Genre (optional)
401        if let Some(genre_val) = genre {
402            let mut genre_elem = Element::new("Genre");
403            genre_elem.add_text(genre_val);
404            release.add_child(genre_elem);
405        }
406        
407        // Resource Group (simplified in 3.8.2)
408        if !resource_refs.is_empty() {
409            let mut resource_group = Element::new("ResourceGroup");
410            for res_ref in resource_refs {
411                let mut ref_elem = Element::new("ResourceReference");
412                ref_elem.add_text(res_ref);
413                resource_group.add_child(ref_elem);
414            }
415            release.add_child(resource_group);
416        }
417        
418        release
419    }
420    
421    /// Build ERN 3.8.2 simple deal
422    pub fn build_simple_deal(
423        deal_ref: &str,
424        commercial_model: &str,
425        territory: &str,
426        start_date: Option<&str>,
427        price: Option<f64>,
428        currency: Option<&str>,
429        release_refs: &[String],
430    ) -> Element {
431        let mut deal = Element::new("ReleaseDeal");
432        
433        // Deal Reference
434        let mut deal_ref_elem = Element::new("DealReference");
435        deal_ref_elem.add_text(deal_ref);
436        deal.add_child(deal_ref_elem);
437        
438        // Deal Terms (simplified in 3.8.2)
439        let mut deal_terms = Element::new("DealTerms");
440        
441        // Commercial Model Type
442        let mut model_elem = Element::new("CommercialModelType");
443        model_elem.add_text(commercial_model);
444        deal_terms.add_child(model_elem);
445        
446        // Territory Code
447        let mut territory_elem = Element::new("TerritoryCode");
448        territory_elem.add_text(territory);
449        deal_terms.add_child(territory_elem);
450        
451        // Validity Period (if start date provided)
452        if let Some(start) = start_date {
453            let mut validity = Element::new("ValidityPeriod");
454            let mut start_elem = Element::new("StartDate");
455            start_elem.add_text(start);
456            validity.add_child(start_elem);
457            deal_terms.add_child(validity);
458        }
459        
460        // Price (if provided)
461        if let Some(price_val) = price {
462            let mut price_elem = Element::new("Price");
463            let mut amount_elem = Element::new("PriceAmount");
464            amount_elem.add_text(&price_val.to_string());
465            price_elem.add_child(amount_elem);
466            
467            if let Some(currency_code) = currency {
468                let mut currency_elem = Element::new("PriceCurrencyCode");
469                currency_elem.add_text(currency_code);
470                price_elem.add_child(currency_elem);
471            }
472            
473            deal_terms.add_child(price_elem);
474        }
475        
476        deal.add_child(deal_terms);
477        
478        // Release References
479        for rel_ref in release_refs {
480            let mut ref_elem = Element::new("ReleaseReference");
481            ref_elem.add_text(rel_ref);
482            deal.add_child(ref_elem);
483        }
484        
485        deal
486    }
487}
488
489/// ERN 3.8.2 validation functions
490pub mod validation {
491    use super::*;
492    use regex::Regex;
493    use once_cell::sync::Lazy;
494    
495    // Precompiled regex patterns for ERN 3.8.2
496    static ISRC_PATTERN_382: Lazy<Regex> = Lazy::new(|| {
497        Regex::new(r"^[A-Z]{2}[A-Z0-9]{3}\d{7}$").unwrap()
498    });
499    
500    static UPC_PATTERN_382: Lazy<Regex> = Lazy::new(|| {
501        Regex::new(r"^\d{12}$").unwrap()
502    });
503    
504    static DURATION_PATTERN_382: Lazy<Regex> = Lazy::new(|| {
505        Regex::new(r"^PT\d+M\d+S$").unwrap()
506    });
507    
508    static DATE_PATTERN_382: Lazy<Regex> = Lazy::new(|| {
509        Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap()
510    });
511    
512    /// Validate ISRC format for ERN 3.8.2
513    pub fn validate_isrc(isrc: &str) -> bool {
514        ISRC_PATTERN_382.is_match(isrc)
515    }
516    
517    /// Validate UPC format for ERN 3.8.2
518    pub fn validate_upc(upc: &str) -> bool {
519        UPC_PATTERN_382.is_match(upc)
520    }
521    
522    /// Validate duration format for ERN 3.8.2
523    pub fn validate_duration(duration: &str) -> bool {
524        DURATION_PATTERN_382.is_match(duration)
525    }
526    
527    /// Validate date format for ERN 3.8.2
528    pub fn validate_date(date: &str) -> bool {
529        DATE_PATTERN_382.is_match(date)
530    }
531    
532    /// Validate territory code for ERN 3.8.2
533    pub fn validate_territory_code(territory: &str) -> bool {
534        matches!(territory, "US" | "GB" | "DE" | "FR" | "JP" | "Worldwide")
535    }
536    
537    /// Validate commercial model type for ERN 3.8.2
538    pub fn validate_commercial_model(model: &str) -> bool {
539        matches!(model, 
540            "SubscriptionModel" | 
541            "PurchaseModel" | 
542            "AdSupportedModel" |
543            "FreeOfChargeModel"
544        )
545    }
546    
547    /// Get all validation errors for an ERN 3.8.2 message
548    pub fn validate_ern_382_message(xml_content: &str) -> Vec<String> {
549        let mut errors = Vec::new();
550        
551        // Check required namespace
552        if !xml_content.contains("http://ddex.net/xml/ern/382") {
553            errors.push("Missing ERN 3.8.2 namespace".to_string());
554        }
555        
556        // Check message schema version ID
557        if !xml_content.contains("ern/382") {
558            errors.push("Missing or incorrect MessageSchemaVersionId".to_string());
559        }
560        
561        // Check for required elements
562        let required_elements = [
563            "MessageId",
564            "MessageSender", 
565            "MessageRecipient",
566            "MessageCreatedDateTime",
567            "ResourceList",
568            "ReleaseList",
569        ];
570        
571        for element in &required_elements {
572            if !xml_content.contains(&format!("<{}", element)) {
573                errors.push(format!("Missing required element: {}", element));
574            }
575        }
576        
577        errors
578    }
579}