ddex_parser/streaming/
fixed_comprehensive.rs

1// src/streaming/fixed_comprehensive.rs
2//! Fixed comprehensive streaming parser with resolved type mismatches
3
4use 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/// Fixed streaming element for demonstration
12#[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
25/// Simple fixed streaming parser demonstrating proper type conversions
26pub 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        // For demonstration, just create sample elements showing proper type conversions
51        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    // Demonstrate proper type conversion adapters
69    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)), // 3 minutes
177            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), // ~7.2MB
184                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
204/// Iterator wrapper for fixed streaming parser
205pub 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
257// Demonstration functions showing proper type conversion patterns
258pub mod type_conversion_examples {
259    use super::*;
260
261    /// Convert string vector to LocalizedString vector
262    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    /// Convert string vector to Genre vector
274    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    /// Create Identifier with proper fields
288    pub fn create_identifier(value: String, id_type: IdentifierType) -> Identifier {
289        Identifier {
290            id_type,
291            namespace: None,
292            value,
293        }
294    }
295
296    /// Create ErrorLocation with all required fields
297    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    /// Build MessageSender with proper field structure
307    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); // Header, Release, Resource, EndOfStream
336
337        // Verify type conversions work properly
338        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        // Test LocalizedString conversion
365        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        // Test Genre conversion
372        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        // Test Identifier creation
379        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        // Test ErrorLocation creation
385        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        // Test MessageSender creation
391        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        // Skip to resource element
403        iterator.next(); // header
404        iterator.next(); // release
405        let resource_result = iterator.next(); // resource
406
407        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}