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::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: 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(), // Real UPC for Taylor Swift - Midnights
108            }],
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), // Anti-Hero is track 3
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("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(), // Real ISRC for Anti-Hero
170            }],
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)), // 3:20 for Anti-Hero
177            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), // ~8MB for high quality
184                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
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 String with all required fields
297    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    /// Build MessageSender with proper field structure
302    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); // Header, Release, Resource, EndOfStream
331
332        // Verify type conversions work properly
333        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        // Test LocalizedString conversion
360        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        // Test Genre conversion
367        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        // Test Identifier creation
374        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        // Test String creation
380        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        // Test MessageSender creation
386        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        // Skip to resource element
398        iterator.next(); // header
399        iterator.next(); // release
400        let resource_result = iterator.next(); // resource
401
402        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}