1use super::*;
7use crate::presets::DdexVersion;
8
9pub fn get_version_spec() -> VersionSpec {
11 let mut element_mappings = IndexMap::new();
12 let mut namespace_prefixes = IndexMap::new();
13
14 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 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 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 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 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 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 "LegacyTechnicalDetails".to_string(),
80 "BasicPrice".to_string(),
81 "SimpleTerritory".to_string(),
82 "OldStyleGenre".to_string(),
83 ],
84 new_elements: vec![
85 ],
87 namespace_prefixes,
88 }
89}
90
91pub struct Ern382Constraints {
93 pub max_resources_per_release: usize,
95 pub supported_image_formats: Vec<String>,
97 pub supported_audio_formats: Vec<String>,
99 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
114pub fn get_validation_rules() -> IndexMap<String, ValidationRule> {
116 let mut rules = IndexMap::new();
117
118 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 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 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 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 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 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#[derive(Debug, Clone)]
184pub struct ValidationRule {
185 pub rule_type: ValidationRuleType,
187 pub pattern: Option<String>,
189 pub enum_values: Option<Vec<String>>,
191 pub required: bool,
193 pub description: String,
195}
196
197#[derive(Debug, Clone)]
199pub enum ValidationRuleType {
200 Pattern,
202 Enum,
204 Length { min: Option<usize>, max: Option<usize> },
206 Custom(String),
208}
209
210pub 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
221pub 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
252pub mod builders {
254 use super::*;
255 use crate::ast::{Element, Node};
256
257 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 let mut msg_id = Element::new("MessageId");
268 msg_id.add_text(message_id);
269 header.add_child(msg_id);
270
271 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 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 let mut created = Element::new("MessageCreatedDateTime");
287 created.add_text(created_datetime);
288 header.add_child(created);
289
290 header
291 }
292
293 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 let mut res_ref = Element::new("ResourceReference");
306 res_ref.add_text(resource_ref);
307 sound_recording.add_child(res_ref);
308
309 let mut res_type = Element::new("Type");
311 res_type.add_text("SoundRecording");
312 sound_recording.add_child(res_type);
313
314 let mut res_id = Element::new("ResourceId");
316 res_id.add_text(resource_id);
317 sound_recording.add_child(res_id);
318
319 let mut ref_title = Element::new("ReferenceTitle");
321 ref_title.add_text(title);
322 sound_recording.add_child(ref_title);
323
324 let mut display_artist = Element::new("DisplayArtist");
326 display_artist.add_text(artist);
327 sound_recording.add_child(display_artist);
328
329 let mut isrc_elem = Element::new("ISRC");
331 isrc_elem.add_text(isrc);
332 sound_recording.add_child(isrc_elem);
333
334 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 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 let mut rel_ref = Element::new("ReleaseReference");
358 rel_ref.add_text(release_ref);
359 release.add_child(rel_ref);
360
361 let mut rel_id = Element::new("ReleaseId");
363 rel_id.add_text(release_id);
364 release.add_child(rel_id);
365
366 let mut rel_type = Element::new("ReleaseType");
368 rel_type.add_text("Album");
369 release.add_child(rel_type);
370
371 let mut title_elem = Element::new("Title");
373 title_elem.add_text(title);
374 release.add_child(title_elem);
375
376 let mut artist_elem = Element::new("DisplayArtist");
378 artist_elem.add_text(artist);
379 release.add_child(artist_elem);
380
381 let mut label_elem = Element::new("LabelName");
383 label_elem.add_text(label);
384 release.add_child(label_elem);
385
386 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 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 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 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 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 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 let mut deal_terms = Element::new("DealTerms");
440
441 let mut model_elem = Element::new("CommercialModelType");
443 model_elem.add_text(commercial_model);
444 deal_terms.add_child(model_elem);
445
446 let mut territory_elem = Element::new("TerritoryCode");
448 territory_elem.add_text(territory);
449 deal_terms.add_child(territory_elem);
450
451 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 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 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
489pub mod validation {
491 use super::*;
492 use regex::Regex;
493 use once_cell::sync::Lazy;
494
495 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 pub fn validate_isrc(isrc: &str) -> bool {
514 ISRC_PATTERN_382.is_match(isrc)
515 }
516
517 pub fn validate_upc(upc: &str) -> bool {
519 UPC_PATTERN_382.is_match(upc)
520 }
521
522 pub fn validate_duration(duration: &str) -> bool {
524 DURATION_PATTERN_382.is_match(duration)
525 }
526
527 pub fn validate_date(date: &str) -> bool {
529 DATE_PATTERN_382.is_match(date)
530 }
531
532 pub fn validate_territory_code(territory: &str) -> bool {
534 matches!(territory, "US" | "GB" | "DE" | "FR" | "JP" | "Worldwide")
535 }
536
537 pub fn validate_commercial_model(model: &str) -> bool {
539 matches!(model,
540 "SubscriptionModel" |
541 "PurchaseModel" |
542 "AdSupportedModel" |
543 "FreeOfChargeModel"
544 )
545 }
546
547 pub fn validate_ern_382_message(xml_content: &str) -> Vec<String> {
549 let mut errors = Vec::new();
550
551 if !xml_content.contains("http://ddex.net/xml/ern/382") {
553 errors.push("Missing ERN 3.8.2 namespace".to_string());
554 }
555
556 if !xml_content.contains("ern/382") {
558 errors.push("Missing or incorrect MessageSchemaVersionId".to_string());
559 }
560
561 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}