1use crate::error::{ErrorLocation, 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: None,
74 value: "SENDER001".to_string(),
75 }],
76 party_name: vec![LocalizedString {
77 text: "Sample Sender".to_string(),
78 language_code: Some("en".to_string()),
79 script: None,
80 }],
81 trading_name: Some("Sender Corp".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: None,
90 value: "MSG001".to_string(),
91 };
92
93 FixedStreamingElement::Header {
94 sender,
95 message_id,
96 created_date_time: "2023-01-01T00:00: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: "REL001".to_string(),
104 release_id: vec![Identifier {
105 id_type: IdentifierType::UPC,
106 namespace: Some("UPC".to_string()),
107 value: "123456789012".to_string(),
108 }],
109 release_title: vec![LocalizedString {
110 text: "Sample Release".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: "Rock".to_string(),
118 sub_genre: Some("Alternative".to_string()),
119 attributes: None,
120 extensions: None,
121 comments: None,
122 }],
123 release_resource_reference_list: vec![ReleaseResourceReference {
124 resource_reference: "RES001".to_string(),
125 sequence_number: Some(1),
126 disc_number: Some(1),
127 track_number: Some(1),
128 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("ARTIST001".to_string()),
136 artist_role: vec!["MainArtist".to_string()],
137 display_artist_name: vec![LocalizedString {
138 text: "Sample Artist".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: "RES001".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: "USRC17607839".to_string(),
170 }],
171 reference_title: vec![LocalizedString {
172 text: "Sample Track".to_string(),
173 language_code: Some("en".to_string()),
174 script: None,
175 }],
176 duration: Some(std::time::Duration::from_secs(180)), technical_details: vec![TechnicalDetails {
178 technical_resource_details_reference: "TECH001".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(7200000), extensions: None,
185 }],
186 rights_controller: vec!["RIGHTS001".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) -> ErrorLocation {
298 ErrorLocation {
299 line,
300 column,
301 byte_offset: None,
302 path,
303 }
304 }
305
306 pub fn build_message_sender(name: String) -> MessageSender {
308 MessageSender {
309 party_id: vec![],
310 party_name: vec![LocalizedString::new(name)],
311 trading_name: None,
312 attributes: None,
313 extensions: None,
314 comments: None,
315 }
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::type_conversion_examples::*;
322 use super::*;
323 use std::io::Cursor;
324
325 #[test]
326 fn test_fixed_streaming_parser_type_conversions() {
327 let xml = r#"<test/>"#;
328 let cursor = Cursor::new(xml.as_bytes());
329 let iterator = FixedStreamIterator::new(cursor, ERNVersion::V4_3);
330
331 let elements: Result<Vec<_>, _> = iterator.collect();
332 assert!(elements.is_ok());
333
334 let elements = elements.unwrap();
335 assert!(elements.len() >= 3); let has_header = elements
339 .iter()
340 .any(|e| matches!(e, FixedStreamingElement::Header { .. }));
341 let has_release = elements
342 .iter()
343 .any(|e| matches!(e, FixedStreamingElement::Release(_)));
344 let has_resource = elements
345 .iter()
346 .any(|e| matches!(e, FixedStreamingElement::Resource(_)));
347
348 assert!(
349 has_header,
350 "Should have header with proper MessageSender type"
351 );
352 assert!(
353 has_release,
354 "Should have release with proper LocalizedString and Genre types"
355 );
356 assert!(
357 has_resource,
358 "Should have resource with proper TechnicalDetails type"
359 );
360 }
361
362 #[test]
363 fn test_type_conversion_examples() {
364 let strings = vec!["Hello".to_string(), "World".to_string()];
366 let localized = convert_strings_to_localized_strings(strings);
367 assert_eq!(localized.len(), 2);
368 assert_eq!(localized[0].text, "Hello");
369 assert!(localized[0].language_code.is_none());
370
371 let genres_str = vec!["Rock".to_string(), "Pop".to_string()];
373 let genres = convert_strings_to_genres(genres_str);
374 assert_eq!(genres.len(), 2);
375 assert_eq!(genres[0].genre_text, "Rock");
376 assert!(genres[0].sub_genre.is_none());
377
378 let id = create_identifier("TEST123".to_string(), IdentifierType::Proprietary);
380 assert_eq!(id.value, "TEST123");
381 assert_eq!(id.id_type, IdentifierType::Proprietary);
382 assert!(id.namespace.is_none());
383
384 let location = create_error_location(10, 5, "test.xml".to_string());
386 assert_eq!(location.line, 10);
387 assert_eq!(location.column, 5);
388 assert_eq!(location.path, "test.xml");
389
390 let sender = build_message_sender("Test Sender".to_string());
392 assert_eq!(sender.party_name[0].text, "Test Sender");
393 assert!(sender.trading_name.is_none());
394 }
395
396 #[test]
397 fn test_resource_with_technical_details() {
398 let xml = r#"<test/>"#;
399 let cursor = Cursor::new(xml.as_bytes());
400 let mut iterator = FixedStreamIterator::new(cursor, ERNVersion::V4_3);
401
402 iterator.next(); iterator.next(); let resource_result = iterator.next(); assert!(resource_result.is_some());
408 if let Some(Ok(FixedStreamingElement::Resource(resource))) = resource_result {
409 assert_eq!(resource.resource_reference, "RES001");
410 assert!(!resource.technical_details.is_empty());
411 assert_eq!(
412 resource.technical_details[0].audio_codec,
413 Some("MP3".to_string())
414 );
415 assert_eq!(resource.technical_details[0].bitrate, Some(320));
416 assert!(!resource.reference_title.is_empty());
417 assert_eq!(resource.reference_title[0].text, "Sample Track");
418 } else {
419 panic!("Expected resource element");
420 }
421 }
422}