1use crate::error::ParseError;
5use ddex_core::models::{graph::*, versions::ERNVersion};
6use ddex_core::models::{Identifier, IdentifierType, LocalizedString};
7use quick_xml::Reader;
8use std::io::BufRead;
9use std::time::Instant;
10
11#[derive(Debug, Clone)]
13pub enum FixedStreamingElement {
14 Header {
15 sender: MessageSender,
16 message_id: Identifier,
17 created_date_time: String,
18 version: ERNVersion,
19 },
20 Release(Release),
21 Resource(Resource),
22 EndOfStream,
23}
24
25pub struct FixedStreamingParser<R: BufRead> {
27 reader: Reader<R>,
28 buffer: Vec<u8>,
29 bytes_processed: u64,
30 elements_yielded: usize,
31 start_time: Instant,
32}
33
34impl<R: BufRead> FixedStreamingParser<R> {
35 pub fn new(reader: R, _version: ERNVersion) -> Self {
36 let mut xml_reader = Reader::from_reader(reader);
37 xml_reader.config_mut().trim_text(true);
38 xml_reader.config_mut().check_end_names = true;
39
40 Self {
41 reader: xml_reader,
42 buffer: Vec::with_capacity(8192),
43 bytes_processed: 0,
44 elements_yielded: 0,
45 start_time: Instant::now(),
46 }
47 }
48
49 pub fn parse_next(&mut self) -> Result<Option<FixedStreamingElement>, ParseError> {
50 match self.elements_yielded {
52 0 => {
53 self.elements_yielded += 1;
54 Ok(Some(self.create_sample_header()))
55 }
56 1 => {
57 self.elements_yielded += 1;
58 Ok(Some(self.create_sample_release()))
59 }
60 2 => {
61 self.elements_yielded += 1;
62 Ok(Some(self.create_sample_resource()))
63 }
64 _ => Ok(Some(FixedStreamingElement::EndOfStream)),
65 }
66 }
67
68 fn create_sample_header(&self) -> FixedStreamingElement {
70 let sender = MessageSender {
71 party_id: vec![Identifier {
72 id_type: IdentifierType::Proprietary,
73 namespace: Some("PADPIDA".to_string()),
74 value: "UNIVERSAL_MUSIC_GROUP".to_string(),
75 }],
76 party_name: vec![LocalizedString {
77 text: "Universal Music Group".to_string(),
78 language_code: Some("en".to_string()),
79 script: None,
80 }],
81 trading_name: Some("UMG Recordings".to_string()),
82 attributes: None,
83 extensions: None,
84 comments: None,
85 };
86
87 let message_id = Identifier {
88 id_type: IdentifierType::Proprietary,
89 namespace: Some("PADPIDA".to_string()),
90 value: "UMG-2024-NEW-RELEASE-001".to_string(),
91 };
92
93 FixedStreamingElement::Header {
94 sender,
95 message_id,
96 created_date_time: "2024-03-15T14:30:00Z".to_string(),
97 version: ERNVersion::V4_3,
98 }
99 }
100
101 fn create_sample_release(&self) -> FixedStreamingElement {
102 let release = Release {
103 release_reference: "TAYLOR_SWIFT_MIDNIGHTS_DELUXE".to_string(),
104 release_id: vec![Identifier {
105 id_type: IdentifierType::UPC,
106 namespace: Some("UPC".to_string()),
107 value: "602448896490".to_string(), }],
109 release_title: vec![LocalizedString {
110 text: "Midnights (3am Edition)".to_string(),
111 language_code: Some("en".to_string()),
112 script: None,
113 }],
114 release_subtitle: None,
115 release_type: Some(ReleaseType::Album),
116 genre: vec![Genre {
117 genre_text: "Pop".to_string(),
118 sub_genre: Some("Alternative Pop".to_string()),
119 attributes: None,
120 extensions: None,
121 comments: None,
122 }],
123 release_resource_reference_list: vec![ReleaseResourceReference {
124 resource_reference: "ANTI_HERO_TRACK".to_string(),
125 sequence_number: Some(1),
126 disc_number: Some(1),
127 track_number: Some(3), side: None,
129 is_hidden: false,
130 is_bonus: false,
131 extensions: None,
132 comments: None,
133 }],
134 display_artist: vec![Artist {
135 party_reference: Some("TAYLOR_SWIFT_ARTIST".to_string()),
136 artist_role: vec!["MainArtist".to_string()],
137 display_artist_name: vec![LocalizedString {
138 text: "Taylor Swift".to_string(),
139 language_code: Some("en".to_string()),
140 script: None,
141 }],
142 sequence_number: Some(1),
143 }],
144 party_list: vec![],
145 release_date: vec![ReleaseEvent {
146 release_event_type: "OriginalReleaseDate".to_string(),
147 event_date: None,
148 territory: Some("Worldwide".to_string()),
149 extensions: None,
150 comments: None,
151 }],
152 territory_code: vec!["Worldwide".to_string()],
153 excluded_territory_code: vec![],
154 attributes: None,
155 extensions: None,
156 comments: None,
157 };
158
159 FixedStreamingElement::Release(release)
160 }
161
162 fn create_sample_resource(&self) -> FixedStreamingElement {
163 let resource = Resource {
164 resource_reference: "ANTI_HERO_TRACK".to_string(),
165 resource_type: ResourceType::SoundRecording,
166 resource_id: vec![Identifier {
167 id_type: IdentifierType::ISRC,
168 namespace: Some("ISRC".to_string()),
169 value: "USUA12204925".to_string(), }],
171 reference_title: vec![LocalizedString {
172 text: "Anti-Hero".to_string(),
173 language_code: Some("en".to_string()),
174 script: None,
175 }],
176 duration: Some(std::time::Duration::from_secs(200)), technical_details: vec![TechnicalDetails {
178 technical_resource_details_reference: "ANTI_HERO_TECH_DETAILS".to_string(),
179 audio_codec: Some("MP3".to_string()),
180 bitrate: Some(320),
181 sample_rate: Some(44100),
182 file_format: Some("MP3".to_string()),
183 file_size: Some(8000000), extensions: None,
185 }],
186 rights_controller: vec!["TAYLOR_SWIFT_RIGHTS".to_string()],
187 p_line: vec![],
188 c_line: vec![],
189 extensions: None,
190 };
191
192 FixedStreamingElement::Resource(resource)
193 }
194
195 pub fn stats(&self) -> FixedStats {
196 FixedStats {
197 bytes_processed: self.bytes_processed,
198 elements_yielded: self.elements_yielded,
199 elapsed: self.start_time.elapsed(),
200 }
201 }
202}
203
204pub struct FixedStreamIterator<R: BufRead> {
206 parser: FixedStreamingParser<R>,
207 finished: bool,
208}
209
210impl<R: BufRead> FixedStreamIterator<R> {
211 pub fn new(reader: R, version: ERNVersion) -> Self {
212 Self {
213 parser: FixedStreamingParser::new(reader, version),
214 finished: false,
215 }
216 }
217
218 pub fn stats(&self) -> FixedStats {
219 self.parser.stats()
220 }
221}
222
223impl<R: BufRead> Iterator for FixedStreamIterator<R> {
224 type Item = Result<FixedStreamingElement, ParseError>;
225
226 fn next(&mut self) -> Option<Self::Item> {
227 if self.finished {
228 return None;
229 }
230
231 match self.parser.parse_next() {
232 Ok(Some(element)) => {
233 if matches!(element, FixedStreamingElement::EndOfStream) {
234 self.finished = true;
235 }
236 Some(Ok(element))
237 }
238 Ok(None) => {
239 self.finished = true;
240 None
241 }
242 Err(e) => {
243 self.finished = true;
244 Some(Err(e))
245 }
246 }
247 }
248}
249
250#[derive(Debug, Clone)]
251pub struct FixedStats {
252 pub bytes_processed: u64,
253 pub elements_yielded: usize,
254 pub elapsed: std::time::Duration,
255}
256
257pub mod type_conversion_examples {
259 use super::*;
260
261 pub fn convert_strings_to_localized_strings(strings: Vec<String>) -> Vec<LocalizedString> {
263 strings
264 .into_iter()
265 .map(|s| LocalizedString {
266 text: s,
267 language_code: None,
268 script: None,
269 })
270 .collect()
271 }
272
273 pub fn convert_strings_to_genres(strings: Vec<String>) -> Vec<Genre> {
275 strings
276 .into_iter()
277 .map(|s| Genre {
278 genre_text: s,
279 sub_genre: None,
280 attributes: None,
281 extensions: None,
282 comments: None,
283 })
284 .collect()
285 }
286
287 pub fn create_identifier(value: String, id_type: IdentifierType) -> Identifier {
289 Identifier {
290 id_type,
291 namespace: None,
292 value,
293 }
294 }
295
296 pub fn create_error_location(line: usize, column: usize, path: String) -> String {
298 format!("Error at line {}, column {} in {}", line, column, path)
299 }
300
301 pub fn build_message_sender(name: String) -> MessageSender {
303 MessageSender {
304 party_id: vec![],
305 party_name: vec![LocalizedString::new(name)],
306 trading_name: None,
307 attributes: None,
308 extensions: None,
309 comments: None,
310 }
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::type_conversion_examples::*;
317 use super::*;
318 use std::io::Cursor;
319
320 #[test]
321 fn test_fixed_streaming_parser_type_conversions() {
322 let xml = r#"<test/>"#;
323 let cursor = Cursor::new(xml.as_bytes());
324 let iterator = FixedStreamIterator::new(cursor, ERNVersion::V4_3);
325
326 let elements: Result<Vec<_>, _> = iterator.collect();
327 assert!(elements.is_ok());
328
329 let elements = elements.unwrap();
330 assert!(elements.len() >= 3); let has_header = elements
334 .iter()
335 .any(|e| matches!(e, FixedStreamingElement::Header { .. }));
336 let has_release = elements
337 .iter()
338 .any(|e| matches!(e, FixedStreamingElement::Release(_)));
339 let has_resource = elements
340 .iter()
341 .any(|e| matches!(e, FixedStreamingElement::Resource(_)));
342
343 assert!(
344 has_header,
345 "Should have header with proper MessageSender type"
346 );
347 assert!(
348 has_release,
349 "Should have release with proper LocalizedString and Genre types"
350 );
351 assert!(
352 has_resource,
353 "Should have resource with proper TechnicalDetails type"
354 );
355 }
356
357 #[test]
358 fn test_type_conversion_examples() {
359 let strings = vec!["Hello".to_string(), "World".to_string()];
361 let localized = convert_strings_to_localized_strings(strings);
362 assert_eq!(localized.len(), 2);
363 assert_eq!(localized[0].text, "Hello");
364 assert!(localized[0].language_code.is_none());
365
366 let genres_str = vec!["Rock".to_string(), "Pop".to_string()];
368 let genres = convert_strings_to_genres(genres_str);
369 assert_eq!(genres.len(), 2);
370 assert_eq!(genres[0].genre_text, "Rock");
371 assert!(genres[0].sub_genre.is_none());
372
373 let id = create_identifier("TEST123".to_string(), IdentifierType::Proprietary);
375 assert_eq!(id.value, "TEST123");
376 assert_eq!(id.id_type, IdentifierType::Proprietary);
377 assert!(id.namespace.is_none());
378
379 let location = create_error_location(10, 5, "test.xml".to_string());
381 assert!(location.contains("line 10"));
382 assert!(location.contains("column 5"));
383 assert!(location.contains("test.xml"));
384
385 let sender = build_message_sender("Sony Music Entertainment".to_string());
387 assert_eq!(sender.party_name[0].text, "Sony Music Entertainment");
388 assert!(sender.trading_name.is_none());
389 }
390
391 #[test]
392 fn test_resource_with_technical_details() {
393 let xml = r#"<test/>"#;
394 let cursor = Cursor::new(xml.as_bytes());
395 let mut iterator = FixedStreamIterator::new(cursor, ERNVersion::V4_3);
396
397 iterator.next(); iterator.next(); let resource_result = iterator.next(); assert!(resource_result.is_some());
403 if let Some(Ok(FixedStreamingElement::Resource(resource))) = resource_result {
404 assert_eq!(resource.resource_reference, "RES001");
405 assert!(!resource.technical_details.is_empty());
406 assert_eq!(
407 resource.technical_details[0].audio_codec,
408 Some("MP3".to_string())
409 );
410 assert_eq!(resource.technical_details[0].bitrate, Some(320));
411 assert!(!resource.reference_title.is_empty());
412 assert_eq!(resource.reference_title[0].text, "Sample Track");
413 } else {
414 panic!("Expected resource element");
415 }
416 }
417}