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/43".to_string());
16 namespace_prefixes.insert("avs".to_string(), "http://ddex.net/xml/avs/avs".to_string());
17 namespace_prefixes.insert(
18 "mead".to_string(),
19 "http://ddex.net/xml/mead/mead".to_string(),
20 );
21
22 element_mappings.insert(
24 "NewReleaseMessage".to_string(),
25 "NewReleaseMessage".to_string(),
26 );
27 element_mappings.insert(
28 "UpdateReleaseMessage".to_string(),
29 "UpdateReleaseMessage".to_string(),
30 );
31 element_mappings.insert("MessageHeader".to_string(), "MessageHeader".to_string());
32 element_mappings.insert("MessageId".to_string(), "MessageId".to_string());
33 element_mappings.insert("MessageSender".to_string(), "MessageSender".to_string());
34 element_mappings.insert(
35 "MessageRecipient".to_string(),
36 "MessageRecipient".to_string(),
37 );
38 element_mappings.insert(
39 "MessageCreatedDateTime".to_string(),
40 "MessageCreatedDateTime".to_string(),
41 );
42 element_mappings.insert(
43 "MessageControlType".to_string(),
44 "MessageControlType".to_string(),
45 );
46
47 element_mappings.insert("ResourceList".to_string(), "ResourceList".to_string());
49 element_mappings.insert("SoundRecording".to_string(), "SoundRecording".to_string());
50 element_mappings.insert("Video".to_string(), "Video".to_string());
51 element_mappings.insert("Image".to_string(), "Image".to_string());
52 element_mappings.insert(
53 "ResourceReference".to_string(),
54 "ResourceReference".to_string(),
55 );
56 element_mappings.insert("ResourceId".to_string(), "ResourceId".to_string());
57 element_mappings.insert("ReferenceTitle".to_string(), "ReferenceTitle".to_string());
58 element_mappings.insert("DisplayArtist".to_string(), "DisplayArtist".to_string());
59 element_mappings.insert("ISRC".to_string(), "ISRC".to_string());
60 element_mappings.insert("Duration".to_string(), "Duration".to_string());
61 element_mappings.insert(
62 "TechnicalResourceDetails".to_string(),
63 "TechnicalResourceDetails".to_string(),
64 );
65 element_mappings.insert("AudioCodecType".to_string(), "AudioCodecType".to_string());
66 element_mappings.insert("BitRate".to_string(), "BitRate".to_string());
67 element_mappings.insert("FileName".to_string(), "FileName".to_string());
68 element_mappings.insert("FileSize".to_string(), "FileSize".to_string());
69 element_mappings.insert("HashSum".to_string(), "HashSum".to_string());
70
71 element_mappings.insert("ReleaseList".to_string(), "ReleaseList".to_string());
73 element_mappings.insert("Release".to_string(), "Release".to_string());
74 element_mappings.insert(
75 "ReleaseReference".to_string(),
76 "ReleaseReference".to_string(),
77 );
78 element_mappings.insert("ReleaseId".to_string(), "ReleaseId".to_string());
79 element_mappings.insert("ReleaseType".to_string(), "ReleaseType".to_string());
80 element_mappings.insert("Title".to_string(), "Title".to_string());
81 element_mappings.insert("DisplayArtist".to_string(), "DisplayArtist".to_string());
82 element_mappings.insert("LabelName".to_string(), "LabelName".to_string());
83 element_mappings.insert("UPC".to_string(), "UPC".to_string());
84 element_mappings.insert("ReleaseDate".to_string(), "ReleaseDate".to_string());
85 element_mappings.insert(
86 "OriginalReleaseDate".to_string(),
87 "OriginalReleaseDate".to_string(),
88 );
89 element_mappings.insert("Genre".to_string(), "Genre".to_string());
90 element_mappings.insert("SubGenre".to_string(), "SubGenre".to_string());
91 element_mappings.insert("ResourceGroup".to_string(), "ResourceGroup".to_string());
92 element_mappings.insert(
93 "ReleaseResourceReference".to_string(),
94 "ReleaseResourceReference".to_string(),
95 );
96
97 element_mappings.insert("DealList".to_string(), "DealList".to_string());
99 element_mappings.insert("ReleaseDeal".to_string(), "ReleaseDeal".to_string());
100 element_mappings.insert("DealReference".to_string(), "DealReference".to_string());
101 element_mappings.insert("DealTerms".to_string(), "DealTerms".to_string());
102 element_mappings.insert(
103 "CommercialModelType".to_string(),
104 "CommercialModelType".to_string(),
105 );
106 element_mappings.insert("Territory".to_string(), "Territory".to_string());
107 element_mappings.insert("TerritoryCode".to_string(), "TerritoryCode".to_string());
108 element_mappings.insert(
109 "ExcludedTerritoryCode".to_string(),
110 "ExcludedTerritoryCode".to_string(),
111 );
112 element_mappings.insert("ValidityPeriod".to_string(), "ValidityPeriod".to_string());
113 element_mappings.insert("StartDate".to_string(), "StartDate".to_string());
114 element_mappings.insert("EndDate".to_string(), "EndDate".to_string());
115 element_mappings.insert("Price".to_string(), "Price".to_string());
116 element_mappings.insert("PriceAmount".to_string(), "PriceAmount".to_string());
117 element_mappings.insert(
118 "PriceCurrencyCode".to_string(),
119 "PriceCurrencyCode".to_string(),
120 );
121 element_mappings.insert("PriceType".to_string(), "PriceType".to_string());
122
123 element_mappings.insert("PartyId".to_string(), "PartyId".to_string());
125 element_mappings.insert("PartyReference".to_string(), "PartyReference".to_string());
126 element_mappings.insert("DetailedHashSum".to_string(), "DetailedHashSum".to_string());
127 element_mappings.insert("PreviewDetails".to_string(), "PreviewDetails".to_string());
128 element_mappings.insert("UsageType".to_string(), "UsageType".to_string());
129 element_mappings.insert("UseType".to_string(), "UseType".to_string());
130 element_mappings.insert(
131 "DistributionChannel".to_string(),
132 "DistributionChannel".to_string(),
133 );
134 element_mappings.insert(
135 "RightsController".to_string(),
136 "RightsController".to_string(),
137 );
138 element_mappings.insert("RemixType".to_string(), "RemixType".to_string());
139 element_mappings.insert("PLine".to_string(), "PLine".to_string());
140 element_mappings.insert("CLine".to_string(), "CLine".to_string());
141
142 VersionSpec {
143 version: DdexVersion::Ern43,
144 namespace: "http://ddex.net/xml/ern/43".to_string(),
145 schema_location: Some(
146 "http://ddex.net/xml/ern/43 http://ddex.net/xml/ern/43/release-notification.xsd"
147 .to_string(),
148 ),
149 message_schema_version_id: "ern/43".to_string(),
150 supported_message_types: vec![
151 "NewReleaseMessage".to_string(),
152 "UpdateReleaseMessage".to_string(),
153 "CatalogTransferMessage".to_string(),
154 "PurgeReleaseMessage".to_string(),
155 "ReleaseAvailabilityMessage".to_string(),
156 "SalesReportMessage".to_string(),
157 ],
158 element_mappings,
159 required_elements: vec![
160 "MessageId".to_string(),
161 "MessageSender".to_string(),
162 "MessageRecipient".to_string(),
163 "MessageCreatedDateTime".to_string(),
164 "ResourceList".to_string(),
165 "ReleaseList".to_string(),
166 ],
167 deprecated_elements: vec![
168 ],
170 new_elements: vec![
171 "Video".to_string(),
173 "Image".to_string(),
174 "FileSize".to_string(),
175 "HashSum".to_string(),
176 "DetailedHashSum".to_string(),
177 "OriginalReleaseDate".to_string(),
178 "SubGenre".to_string(),
179 "ReleaseResourceReference".to_string(),
180 "ExcludedTerritoryCode".to_string(),
181 "PriceType".to_string(),
182 "UseType".to_string(),
183 "DistributionChannel".to_string(),
184 "RightsController".to_string(),
185 "RemixType".to_string(),
186 "PLine".to_string(),
187 "CLine".to_string(),
188 ],
189 namespace_prefixes,
190 }
191}
192
193pub struct Ern43Constraints {
195 pub max_resources_per_release: usize,
197 pub supported_image_formats: Vec<String>,
199 pub supported_audio_formats: Vec<String>,
201 pub supported_video_formats: Vec<String>,
203 pub max_deal_terms: usize,
205 pub enhanced_validation: bool,
207 pub rights_management: bool,
209}
210
211impl Default for Ern43Constraints {
212 fn default() -> Self {
213 Self {
214 max_resources_per_release: 1000,
215 supported_image_formats: vec![
216 "JPEG".to_string(),
217 "PNG".to_string(),
218 "GIF".to_string(),
219 "TIFF".to_string(),
220 "BMP".to_string(),
221 "WEBP".to_string(),
222 ],
223 supported_audio_formats: vec![
224 "MP3".to_string(),
225 "WAV".to_string(),
226 "FLAC".to_string(),
227 "AAC".to_string(),
228 "OGG".to_string(),
229 "M4A".to_string(),
230 "AIFF".to_string(),
231 "WMA".to_string(),
232 ],
233 supported_video_formats: vec![
234 "MP4".to_string(),
235 "MOV".to_string(),
236 "AVI".to_string(),
237 "WMV".to_string(),
238 "MKV".to_string(),
239 "WEBM".to_string(),
240 ],
241 max_deal_terms: 100,
242 enhanced_validation: true,
243 rights_management: true,
244 }
245 }
246}
247
248pub fn get_namespace_mappings() -> IndexMap<String, String> {
250 let mut mappings = IndexMap::new();
251
252 mappings.insert("ern".to_string(), "http://ddex.net/xml/ern/43".to_string());
253 mappings.insert("avs".to_string(), "http://ddex.net/xml/avs/avs".to_string());
254 mappings.insert("drm".to_string(), "http://ddex.net/xml/drm/drm".to_string());
255 mappings.insert("mv".to_string(), "http://ddex.net/xml/mv/mv".to_string());
256 mappings.insert(
257 "mead".to_string(),
258 "http://ddex.net/xml/mead/mead".to_string(),
259 );
260
261 mappings
262}
263
264pub fn get_xml_template() -> &'static str {
266 r#"<?xml version="1.0" encoding="UTF-8"?>
267<NewReleaseMessage xmlns="http://ddex.net/xml/ern/43"
268 xmlns:avs="http://ddex.net/xml/avs/avs"
269 xmlns:mead="http://ddex.net/xml/mead/mead"
270 MessageSchemaVersionId="ern/43">
271 <MessageHeader>
272 <MessageId>{message_id}</MessageId>
273 <MessageSender>
274 <PartyName>{sender_name}</PartyName>
275 <PartyId>{sender_id}</PartyId>
276 <PartyReference>{sender_ref}</PartyReference>
277 </MessageSender>
278 <MessageRecipient>
279 <PartyName>{recipient_name}</PartyName>
280 <PartyId>{recipient_id}</PartyId>
281 <PartyReference>{recipient_ref}</PartyReference>
282 </MessageRecipient>
283 <MessageControlType>{control_type}</MessageControlType>
284 <MessageCreatedDateTime>{created_datetime}</MessageCreatedDateTime>
285 </MessageHeader>
286
287 <ResourceList>
288 <!-- Advanced resources will be populated here -->
289 </ResourceList>
290
291 <ReleaseList>
292 <!-- Advanced releases will be populated here -->
293 </ReleaseList>
294
295 <DealList>
296 <!-- Advanced deals will be populated here -->
297 </DealList>
298</NewReleaseMessage>"#
299}
300
301pub mod builders {
303
304 use crate::ast::Element;
305
306 pub fn build_advanced_message_header(
308 message_id: &str,
309 sender_name: &str,
310 sender_id: Option<&str>,
311 sender_ref: Option<&str>,
312 recipient_name: &str,
313 recipient_id: Option<&str>,
314 recipient_ref: Option<&str>,
315 control_type: Option<&str>,
316 created_datetime: &str,
317 ) -> Element {
318 let mut header = Element::new("MessageHeader");
319
320 let mut msg_id = Element::new("MessageId");
322 msg_id.add_text(message_id);
323 header.add_child(msg_id);
324
325 let mut sender = Element::new("MessageSender");
327 let mut sender_party = Element::new("PartyName");
328 sender_party.add_text(sender_name);
329 sender.add_child(sender_party);
330
331 if let Some(sid) = sender_id {
332 let mut sender_id_elem = Element::new("PartyId");
333 sender_id_elem.add_text(sid);
334 sender.add_child(sender_id_elem);
335 }
336
337 if let Some(sref) = sender_ref {
338 let mut sender_ref_elem = Element::new("PartyReference");
339 sender_ref_elem.add_text(sref);
340 sender.add_child(sender_ref_elem);
341 }
342 header.add_child(sender);
343
344 let mut recipient = Element::new("MessageRecipient");
346 let mut recipient_party = Element::new("PartyName");
347 recipient_party.add_text(recipient_name);
348 recipient.add_child(recipient_party);
349
350 if let Some(rid) = recipient_id {
351 let mut recipient_id_elem = Element::new("PartyId");
352 recipient_id_elem.add_text(rid);
353 recipient.add_child(recipient_id_elem);
354 }
355
356 if let Some(rref) = recipient_ref {
357 let mut recipient_ref_elem = Element::new("PartyReference");
358 recipient_ref_elem.add_text(rref);
359 recipient.add_child(recipient_ref_elem);
360 }
361 header.add_child(recipient);
362
363 if let Some(control) = control_type {
365 let mut control_elem = Element::new("MessageControlType");
366 control_elem.add_text(control);
367 header.add_child(control_elem);
368 }
369
370 let mut created = Element::new("MessageCreatedDateTime");
372 created.add_text(created_datetime);
373 header.add_child(created);
374
375 header
376 }
377
378 pub fn build_advanced_sound_recording(
380 resource_ref: &str,
381 resource_id: &str,
382 title: &str,
383 artist: &str,
384 isrc: &str,
385 duration: &str,
386 file_name: Option<&str>,
387 file_size: Option<u64>,
388 codec: Option<&str>,
389 bit_rate: Option<u32>,
390 hash_sum: Option<&str>,
391 hash_algorithm: Option<&str>,
392 ) -> Element {
393 let mut sound_recording = Element::new("SoundRecording");
394
395 let mut res_ref = Element::new("ResourceReference");
397 res_ref.add_text(resource_ref);
398 sound_recording.add_child(res_ref);
399
400 let mut res_type = Element::new("Type");
402 res_type.add_text("SoundRecording");
403 sound_recording.add_child(res_type);
404
405 let mut res_id = Element::new("ResourceId");
407 res_id.add_text(resource_id);
408 sound_recording.add_child(res_id);
409
410 let mut ref_title = Element::new("ReferenceTitle");
412 ref_title.add_text(title);
413 sound_recording.add_child(ref_title);
414
415 let mut display_artist = Element::new("DisplayArtist");
417 display_artist.add_text(artist);
418 sound_recording.add_child(display_artist);
419
420 let mut isrc_elem = Element::new("ISRC");
422 isrc_elem.add_text(isrc);
423 sound_recording.add_child(isrc_elem);
424
425 let mut duration_elem = Element::new("Duration");
427 duration_elem.add_text(duration);
428 sound_recording.add_child(duration_elem);
429
430 if file_name.is_some()
432 || file_size.is_some()
433 || codec.is_some()
434 || bit_rate.is_some()
435 || hash_sum.is_some()
436 {
437 let mut tech_details = Element::new("TechnicalResourceDetails");
438
439 if let Some(fname) = file_name {
440 let mut file_elem = Element::new("FileName");
441 file_elem.add_text(fname);
442 tech_details.add_child(file_elem);
443 }
444
445 if let Some(fsize) = file_size {
446 let mut size_elem = Element::new("FileSize");
447 size_elem.add_text(&fsize.to_string());
448 tech_details.add_child(size_elem);
449 }
450
451 if let Some(codec_type) = codec {
452 let mut codec_elem = Element::new("AudioCodecType");
453 codec_elem.add_text(codec_type);
454 tech_details.add_child(codec_elem);
455 }
456
457 if let Some(bitrate) = bit_rate {
458 let mut bitrate_elem = Element::new("BitRate");
459 bitrate_elem.add_text(&bitrate.to_string());
460 tech_details.add_child(bitrate_elem);
461 }
462
463 if let Some(hash) = hash_sum {
465 let mut hash_elem = Element::new("HashSum");
466
467 if let Some(algorithm) = hash_algorithm {
468 hash_elem
469 .attributes
470 .insert("Algorithm".to_string(), algorithm.to_string());
471 }
472
473 hash_elem.add_text(hash);
474 tech_details.add_child(hash_elem);
475 }
476
477 sound_recording.add_child(tech_details);
478 }
479
480 sound_recording
481 }
482
483 pub fn build_video_resource(
485 resource_ref: &str,
486 resource_id: &str,
487 title: &str,
488 artist: &str,
489 duration: &str,
490 file_name: Option<&str>,
491 file_size: Option<u64>,
492 codec: Option<&str>,
493 resolution: Option<&str>,
494 frame_rate: Option<f32>,
495 ) -> Element {
496 let mut video = Element::new("Video");
497
498 let mut res_ref = Element::new("ResourceReference");
500 res_ref.add_text(resource_ref);
501 video.add_child(res_ref);
502
503 let mut res_type = Element::new("Type");
505 res_type.add_text("Video");
506 video.add_child(res_type);
507
508 let mut res_id = Element::new("ResourceId");
510 res_id.add_text(resource_id);
511 video.add_child(res_id);
512
513 let mut ref_title = Element::new("ReferenceTitle");
515 ref_title.add_text(title);
516 video.add_child(ref_title);
517
518 let mut display_artist = Element::new("DisplayArtist");
520 display_artist.add_text(artist);
521 video.add_child(display_artist);
522
523 let mut duration_elem = Element::new("Duration");
525 duration_elem.add_text(duration);
526 video.add_child(duration_elem);
527
528 if file_name.is_some()
530 || file_size.is_some()
531 || codec.is_some()
532 || resolution.is_some()
533 || frame_rate.is_some()
534 {
535 let mut tech_details = Element::new("TechnicalResourceDetails");
536
537 if let Some(fname) = file_name {
538 let mut file_elem = Element::new("FileName");
539 file_elem.add_text(fname);
540 tech_details.add_child(file_elem);
541 }
542
543 if let Some(fsize) = file_size {
544 let mut size_elem = Element::new("FileSize");
545 size_elem.add_text(&fsize.to_string());
546 tech_details.add_child(size_elem);
547 }
548
549 if let Some(codec_type) = codec {
550 let mut codec_elem = Element::new("VideoCodecType");
551 codec_elem.add_text(codec_type);
552 tech_details.add_child(codec_elem);
553 }
554
555 if let Some(res) = resolution {
556 let mut res_elem = Element::new("VideoResolution");
557 res_elem.add_text(res);
558 tech_details.add_child(res_elem);
559 }
560
561 if let Some(fps) = frame_rate {
562 let mut fps_elem = Element::new("FrameRate");
563 fps_elem.add_text(&fps.to_string());
564 tech_details.add_child(fps_elem);
565 }
566
567 video.add_child(tech_details);
568 }
569
570 video
571 }
572}
573
574pub mod validation {
576
577 use once_cell::sync::Lazy;
578 use regex::Regex;
579
580 static ISRC_PATTERN_43: Lazy<Regex> =
582 Lazy::new(|| Regex::new(r"^[A-Z]{2}[A-Z0-9]{3}\d{7}$").unwrap());
583
584 static UPC_PATTERN_43: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\d{12}$").unwrap());
585
586 static EAN_PATTERN_43: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\d{13}$").unwrap());
587
588 static DURATION_PATTERN_43: Lazy<Regex> =
589 Lazy::new(|| Regex::new(r"^PT(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?$").unwrap());
590
591 static DATE_PATTERN_43: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap());
592
593 static DATETIME_PATTERN_43: Lazy<Regex> = Lazy::new(|| {
594 Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$").unwrap()
595 });
596
597 static HASH_PATTERN_43: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[A-Fa-f0-9]+$").unwrap());
598
599 pub fn validate_isrc(isrc: &str) -> bool {
601 ISRC_PATTERN_43.is_match(isrc)
602 }
603
604 pub fn validate_upc(upc: &str) -> bool {
606 UPC_PATTERN_43.is_match(upc)
607 }
608
609 pub fn validate_ean(ean: &str) -> bool {
611 EAN_PATTERN_43.is_match(ean)
612 }
613
614 pub fn validate_duration(duration: &str) -> bool {
616 DURATION_PATTERN_43.is_match(duration)
617 }
618
619 pub fn validate_date(date: &str) -> bool {
621 DATE_PATTERN_43.is_match(date)
622 }
623
624 pub fn validate_datetime(datetime: &str) -> bool {
626 DATETIME_PATTERN_43.is_match(datetime)
627 }
628
629 pub fn validate_hash_sum(hash: &str) -> bool {
631 HASH_PATTERN_43.is_match(hash) && hash.len() >= 8
632 }
633
634 pub fn validate_territory_code(territory: &str) -> bool {
636 matches!(
638 territory,
639 "US" | "GB" | "DE" | "FR" | "JP" | "CA" | "AU" | "IT" | "ES" | "NL" |
641 "SE" | "NO" | "DK" | "FI" | "BR" | "MX" | "AR" | "IN" | "CN" | "KR" |
642 "RU" | "PL" | "CZ" | "AT" | "CH" | "BE" | "PT" | "GR" | "TR" | "ZA" |
643 "Worldwide" | "WorldwideExceptUS" | "Europe" | "NorthAmerica" | "SouthAmerica" |
645 "Asia" | "Africa" | "Oceania"
646 )
647 }
648
649 pub fn validate_commercial_model(model: &str) -> bool {
651 matches!(
652 model,
653 "SubscriptionModel"
654 | "PurchaseModel"
655 | "AdSupportedModel"
656 | "FreeOfChargeModel"
657 | "StreamingModel"
658 | "DownloadModel"
659 | "ConditionalDownloadModel"
660 | "LimitedDownloadModel"
661 | "PayAsYouGoModel"
662 | "FreemiumModel"
663 )
664 }
665
666 pub fn validate_audio_codec(codec: &str) -> bool {
668 matches!(
669 codec,
670 "MP3"
671 | "AAC"
672 | "FLAC"
673 | "WAV"
674 | "OGG"
675 | "WMA"
676 | "MP4"
677 | "M4A"
678 | "AIFF"
679 | "DSD"
680 | "ALAC"
681 | "Opus"
682 | "AMR"
683 | "AC3"
684 | "DTS"
685 )
686 }
687
688 pub fn validate_video_codec(codec: &str) -> bool {
690 matches!(
691 codec,
692 "H.264"
693 | "H.265"
694 | "VP8"
695 | "VP9"
696 | "AV1"
697 | "MPEG-2"
698 | "MPEG-4"
699 | "DivX"
700 | "XviD"
701 | "WMV"
702 | "QuickTime"
703 | "ProRes"
704 )
705 }
706
707 pub fn validate_usage_type(usage: &str) -> bool {
709 matches!(
710 usage,
711 "Stream"
712 | "Download"
713 | "Preview"
714 | "ConditionalDownload"
715 | "DigitalPhonogramDelivery"
716 | "UserMadeClip"
717 | "Podcast"
718 | "RadioPlay"
719 | "BackgroundMusic"
720 | "Ringtone"
721 | "Synchronization"
722 )
723 }
724
725 pub fn validate_hash_algorithm(algorithm: &str) -> bool {
727 matches!(algorithm, "MD5" | "SHA-1" | "SHA-256" | "SHA-512" | "CRC32")
728 }
729
730 pub fn validate_ern_43_message(xml_content: &str) -> Vec<String> {
732 let mut errors = Vec::new();
733
734 if !xml_content.contains("http://ddex.net/xml/ern/43") {
736 errors.push("Missing ERN 4.3 namespace".to_string());
737 }
738
739 if !xml_content.contains("ern/43") {
741 errors.push("Missing or incorrect MessageSchemaVersionId".to_string());
742 }
743
744 let required_elements = [
746 "MessageId",
747 "MessageSender",
748 "MessageRecipient",
749 "MessageCreatedDateTime",
750 "ResourceList",
751 "ReleaseList",
752 ];
753
754 for element in &required_elements {
755 if !xml_content.contains(&format!("<{}", element)) {
756 errors.push(format!("Missing required element: {}", element));
757 }
758 }
759
760 if xml_content.contains("HashSum") {
762 if !xml_content.contains("Algorithm=") {
763 errors.push("HashSum should specify Algorithm attribute".to_string());
764 }
765 }
766
767 if xml_content.contains("TechnicalResourceDetails") {
768 if !xml_content.contains("FileName") && !xml_content.contains("FileSize") {
769 errors.push(
770 "TechnicalResourceDetails should contain FileName or FileSize".to_string(),
771 );
772 }
773 }
774
775 errors
776 }
777}