deb822_fast/
lib.rs

1//! Lossy parser for deb822 format.
2//!
3//! This parser is lossy in the sense that it will discard whitespace and comments
4//! in the input.
5
6#[cfg(feature = "derive")]
7pub use deb822_derive::{FromDeb822, ToDeb822};
8
9pub mod convert;
10pub use convert::{FromDeb822Paragraph, ToDeb822Paragraph};
11
12/// Canonical field order for source paragraphs in debian/control files
13pub const SOURCE_FIELD_ORDER: &[&str] = &[
14    "Source",
15    "Section",
16    "Priority",
17    "Maintainer",
18    "Uploaders",
19    "Build-Depends",
20    "Build-Depends-Indep",
21    "Build-Depends-Arch",
22    "Build-Conflicts",
23    "Build-Conflicts-Indep",
24    "Build-Conflicts-Arch",
25    "Standards-Version",
26    "Vcs-Browser",
27    "Vcs-Git",
28    "Vcs-Svn",
29    "Vcs-Bzr",
30    "Vcs-Hg",
31    "Vcs-Darcs",
32    "Vcs-Cvs",
33    "Vcs-Arch",
34    "Vcs-Mtn",
35    "Homepage",
36    "Rules-Requires-Root",
37    "Testsuite",
38    "Testsuite-Triggers",
39];
40
41/// Canonical field order for binary packages in debian/control files
42pub const BINARY_FIELD_ORDER: &[&str] = &[
43    "Package",
44    "Architecture",
45    "Section",
46    "Priority",
47    "Multi-Arch",
48    "Essential",
49    "Build-Profiles",
50    "Built-Using",
51    "Pre-Depends",
52    "Depends",
53    "Recommends",
54    "Suggests",
55    "Enhances",
56    "Conflicts",
57    "Breaks",
58    "Replaces",
59    "Provides",
60    "Description",
61];
62
63/// Error type for the parser.
64#[derive(Debug)]
65pub enum Error {
66    /// An unexpected token was encountered.
67    UnexpectedToken(String),
68
69    /// Unexpected end-of-file.
70    UnexpectedEof,
71
72    /// Expected end-of-file.
73    ExpectedEof,
74
75    /// IO error.
76    Io(std::io::Error),
77}
78
79impl From<std::io::Error> for Error {
80    fn from(e: std::io::Error) -> Self {
81        Self::Io(e)
82    }
83}
84
85impl std::fmt::Display for Error {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
87        match self {
88            Self::UnexpectedToken(t) => write!(f, "Unexpected token: {}", t),
89            Self::UnexpectedEof => f.write_str("Unexpected end-of-file"),
90            Self::Io(e) => write!(f, "IO error: {}", e),
91            Self::ExpectedEof => f.write_str("Expected end-of-file"),
92        }
93    }
94}
95
96impl std::error::Error for Error {
97    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
98        match self {
99            Self::Io(e) => Some(e),
100            _ => None,
101        }
102    }
103}
104
105/// A field in a deb822 paragraph.
106#[derive(Debug, PartialEq, Eq, Clone)]
107pub struct Field {
108    /// The name of the field.
109    pub name: String,
110
111    /// The value of the field.
112    pub value: String,
113}
114
115/// A deb822 paragraph.
116#[derive(Debug, PartialEq, Eq, Clone)]
117pub struct Paragraph {
118    /// Fields in the paragraph.
119    pub fields: Vec<Field>,
120}
121
122impl Paragraph {
123    /// Get the value of a field by name.
124    ///
125    /// Returns `None` if the field does not exist.
126    pub fn get(&self, name: &str) -> Option<&str> {
127        for field in &self.fields {
128            if field.name == name {
129                return Some(&field.value);
130            }
131        }
132        None
133    }
134
135    /// Check if the paragraph is empty.
136    pub fn is_empty(&self) -> bool {
137        self.fields.is_empty()
138    }
139
140    /// Return the number of fields in the paragraph.
141    pub fn len(&self) -> usize {
142        self.fields.len()
143    }
144
145    /// Iterate over the fields in the paragraph.
146    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
147        self.fields
148            .iter()
149            .map(|field| (field.name.as_str(), field.value.as_str()))
150    }
151
152    /// Iterate over the fields in the paragraph, mutably.
153    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut String)> {
154        self.fields
155            .iter_mut()
156            .map(|field| (field.name.as_str(), &mut field.value))
157    }
158
159    /// Insert a field into the paragraph.
160    ///
161    /// If a field with the same name already exists, a
162    /// new field will be added.
163    pub fn insert(&mut self, name: &str, value: &str) {
164        self.fields.push(Field {
165            name: name.to_string(),
166            value: value.to_string(),
167        });
168    }
169
170    /// Set the value of a field, inserting at the appropriate location if new.
171    ///
172    /// If a field with the same name already exists, its value will be updated.
173    /// If the field doesn't exist, it will be inserted at the appropriate position
174    /// based on canonical field ordering.
175    pub fn set(&mut self, name: &str, value: &str) {
176        // Check if field already exists and update it
177        for field in &mut self.fields {
178            if field.name == name {
179                field.value = value.to_string();
180                return;
181            }
182        }
183
184        // Field doesn't exist, insert at appropriate location
185        // Try to detect if this is a source or binary package paragraph
186        let field_order = if self.fields.iter().any(|f| f.name == "Source") {
187            SOURCE_FIELD_ORDER
188        } else if self.fields.iter().any(|f| f.name == "Package") {
189            BINARY_FIELD_ORDER
190        } else {
191            // Default based on what we're inserting
192            if name == "Source" {
193                SOURCE_FIELD_ORDER
194            } else if name == "Package" {
195                BINARY_FIELD_ORDER
196            } else {
197                // Try to determine based on existing fields
198                let has_source_fields = self.fields.iter().any(|f| {
199                    SOURCE_FIELD_ORDER.contains(&f.name.as_str())
200                        && !BINARY_FIELD_ORDER.contains(&f.name.as_str())
201                });
202                if has_source_fields {
203                    SOURCE_FIELD_ORDER
204                } else {
205                    BINARY_FIELD_ORDER
206                }
207            }
208        };
209
210        let insertion_index = self.find_insertion_index(name, field_order);
211        self.fields.insert(
212            insertion_index,
213            Field {
214                name: name.to_string(),
215                value: value.to_string(),
216            },
217        );
218    }
219
220    /// Set a field using a specific field ordering.
221    pub fn set_with_field_order(&mut self, name: &str, value: &str, field_order: &[&str]) {
222        // Check if field already exists and update it
223        for field in &mut self.fields {
224            if field.name == name {
225                field.value = value.to_string();
226                return;
227            }
228        }
229
230        let insertion_index = self.find_insertion_index(name, field_order);
231        self.fields.insert(
232            insertion_index,
233            Field {
234                name: name.to_string(),
235                value: value.to_string(),
236            },
237        );
238    }
239
240    /// Find the appropriate insertion index for a new field based on field ordering.
241    fn find_insertion_index(&self, name: &str, field_order: &[&str]) -> usize {
242        // Find position of the new field in the canonical order
243        let new_field_position = field_order.iter().position(|&field| field == name);
244
245        let mut insertion_index = self.fields.len();
246
247        // Find the right position based on canonical field order
248        for (i, field) in self.fields.iter().enumerate() {
249            let existing_position = field_order.iter().position(|&f| f == field.name);
250
251            match (new_field_position, existing_position) {
252                // Both fields are in the canonical order
253                (Some(new_pos), Some(existing_pos)) => {
254                    if new_pos < existing_pos {
255                        insertion_index = i;
256                        break;
257                    }
258                }
259                // New field is in canonical order, existing is not
260                (Some(_), None) => {
261                    // Continue looking - unknown fields go after known ones
262                }
263                // New field is not in canonical order, existing is
264                (None, Some(_)) => {
265                    // Continue until we find all known fields
266                }
267                // Neither field is in canonical order, maintain alphabetical
268                (None, None) => {
269                    if name < &field.name {
270                        insertion_index = i;
271                        break;
272                    }
273                }
274            }
275        }
276
277        // If we have a position in canonical order but haven't found where to insert yet,
278        // we need to insert after all known fields that come before it
279        if new_field_position.is_some() && insertion_index == self.fields.len() {
280            // Look for the position after the last known field that comes before our field
281            for (i, field) in self.fields.iter().enumerate().rev() {
282                if field_order.iter().any(|&f| f == field.name) {
283                    // Found a known field, insert after it
284                    insertion_index = i + 1;
285                    break;
286                }
287            }
288        }
289
290        insertion_index
291    }
292
293    /// Remove a field from the paragraph.
294    pub fn remove(&mut self, name: &str) {
295        self.fields.retain(|field| field.name != name);
296    }
297}
298
299impl std::fmt::Display for Field {
300    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
301        let lines = self.value.lines().collect::<Vec<_>>();
302        if lines.len() > 1 {
303            write!(f, "{}:", self.name)?;
304            for line in lines {
305                writeln!(f, " {}", line)?;
306            }
307            Ok(())
308        } else {
309            writeln!(f, "{}: {}", self.name, self.value)
310        }
311    }
312}
313
314impl std::fmt::Display for Paragraph {
315    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
316        for field in &self.fields {
317            field.fmt(f)?;
318        }
319        Ok(())
320    }
321}
322
323impl std::fmt::Display for Deb822 {
324    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
325        for (i, paragraph) in self.0.iter().enumerate() {
326            if i > 0 {
327                writeln!(f)?;
328            }
329            write!(f, "{}", paragraph)?;
330        }
331        Ok(())
332    }
333}
334
335impl std::str::FromStr for Paragraph {
336    type Err = Error;
337
338    fn from_str(s: &str) -> Result<Self, Self::Err> {
339        let doc: Deb822 = s.parse()?;
340        if doc.is_empty() {
341            Err(Error::UnexpectedEof)
342        } else if doc.len() > 1 {
343            Err(Error::ExpectedEof)
344        } else {
345            Ok(doc.0.into_iter().next().unwrap())
346        }
347    }
348}
349
350impl From<Vec<(String, String)>> for Paragraph {
351    fn from(fields: Vec<(String, String)>) -> Self {
352        fields.into_iter().collect()
353    }
354}
355
356impl FromIterator<(String, String)> for Paragraph {
357    fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
358        let fields = iter
359            .into_iter()
360            .map(|(name, value)| Field { name, value })
361            .collect();
362        Paragraph { fields }
363    }
364}
365
366impl IntoIterator for Paragraph {
367    type Item = (String, String);
368    type IntoIter = std::iter::Map<std::vec::IntoIter<Field>, fn(Field) -> (String, String)>;
369
370    fn into_iter(self) -> Self::IntoIter {
371        self.fields
372            .into_iter()
373            .map(|field| (field.name, field.value))
374    }
375}
376
377/// A deb822 document.
378#[derive(Debug, PartialEq, Eq, Clone)]
379pub struct Deb822(Vec<Paragraph>);
380
381impl From<Deb822> for Vec<Paragraph> {
382    fn from(doc: Deb822) -> Self {
383        doc.0
384    }
385}
386
387impl IntoIterator for Deb822 {
388    type Item = Paragraph;
389    type IntoIter = std::vec::IntoIter<Paragraph>;
390
391    fn into_iter(self) -> Self::IntoIter {
392        self.0.into_iter()
393    }
394}
395
396impl Deb822 {
397    /// Number of paragraphs in the document.
398    pub fn len(&self) -> usize {
399        self.0.len()
400    }
401
402    /// Check if the document is empty.
403    pub fn is_empty(&self) -> bool {
404        self.0.is_empty()
405    }
406
407    /// Iterate over the paragraphs in the document.
408    pub fn iter(&self) -> impl Iterator<Item = &Paragraph> {
409        self.0.iter()
410    }
411
412    /// Iterate over the paragraphs in the document, mutably.
413    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Paragraph> {
414        self.0.iter_mut()
415    }
416
417    /// Read from a reader.
418    pub fn from_reader<R: std::io::Read>(mut r: R) -> Result<Self, Error> {
419        let mut buf = String::new();
420        r.read_to_string(&mut buf)?;
421        buf.parse()
422    }
423
424    /// Stream paragraphs from a reader.
425    ///
426    /// This returns an iterator that reads and parses paragraphs one at a time,
427    /// which is more memory-efficient for large files.
428    pub fn iter_paragraphs_from_reader<R: std::io::BufRead>(reader: R) -> ParagraphReader<R> {
429        ParagraphReader::new(reader)
430    }
431}
432
433/// Reader that streams paragraphs from a buffered reader.
434pub struct ParagraphReader<R: std::io::BufRead> {
435    reader: R,
436    buffer: String,
437    finished: bool,
438}
439
440impl<R: std::io::BufRead> ParagraphReader<R> {
441    /// Create a new paragraph reader from a buffered reader.
442    pub fn new(reader: R) -> Self {
443        Self {
444            reader,
445            buffer: String::new(),
446            finished: false,
447        }
448    }
449}
450
451impl<R: std::io::BufRead> Iterator for ParagraphReader<R> {
452    type Item = Result<Paragraph, Error>;
453
454    fn next(&mut self) -> Option<Self::Item> {
455        if self.finished {
456            return None;
457        }
458
459        self.buffer.clear();
460        let mut found_content = false;
461
462        loop {
463            let mut line = String::new();
464            match self.reader.read_line(&mut line) {
465                Ok(0) => {
466                    // End of file
467                    self.finished = true;
468                    if found_content {
469                        // Parse the buffered paragraph
470                        return Some(self.buffer.parse());
471                    }
472                    return None;
473                }
474                Ok(_) => {
475                    // Check if this is a blank line (paragraph separator)
476                    if line.trim().is_empty() && found_content {
477                        // End of current paragraph
478                        return Some(self.buffer.parse());
479                    }
480
481                    // Skip leading blank lines and comments before first field
482                    if !found_content
483                        && (line.trim().is_empty() || line.trim_start().starts_with('#'))
484                    {
485                        continue;
486                    }
487
488                    // Check if this starts a new field (not indented)
489                    if !line.starts_with(|c: char| c.is_whitespace()) && line.contains(':') {
490                        found_content = true;
491                    } else if found_content {
492                        // Continuation line or comment within paragraph
493                    } else if !line.trim_start().starts_with('#') {
494                        // Non-blank, non-comment line before any field - this is content
495                        found_content = true;
496                    }
497
498                    self.buffer.push_str(&line);
499                }
500                Err(e) => {
501                    self.finished = true;
502                    return Some(Err(Error::Io(e)));
503                }
504            }
505        }
506    }
507}
508
509impl std::str::FromStr for Deb822 {
510    type Err = Error;
511
512    fn from_str(s: &str) -> Result<Self, Self::Err> {
513        // Optimized zero-copy byte-level parser
514        let bytes = s.as_bytes();
515        let mut paragraphs = Vec::new();
516        let mut pos = 0;
517        let len = bytes.len();
518
519        while pos < len {
520            // Skip leading newlines and comments between paragraphs
521            while pos < len {
522                let b = bytes[pos];
523                if b == b'#' {
524                    while pos < len && bytes[pos] != b'\n' {
525                        pos += 1;
526                    }
527                    if pos < len {
528                        pos += 1;
529                    }
530                } else if b == b'\n' || b == b'\r' {
531                    pos += 1;
532                } else {
533                    break;
534                }
535            }
536
537            if pos >= len {
538                break;
539            }
540
541            // Check for unexpected leading space/tab before paragraph
542            if bytes[pos] == b' ' || bytes[pos] == b'\t' {
543                let line_start = pos;
544                while pos < len && bytes[pos] != b'\n' {
545                    pos += 1;
546                }
547                let token = unsafe { std::str::from_utf8_unchecked(&bytes[line_start..pos]) };
548                return Err(Error::UnexpectedToken(token.to_string()));
549            }
550
551            // Parse paragraph
552            let mut fields: Vec<Field> = Vec::new();
553
554            loop {
555                if pos >= len {
556                    break;
557                }
558
559                // Check for blank line (end of paragraph)
560                if bytes[pos] == b'\n' {
561                    pos += 1;
562                    break;
563                }
564
565                // Skip comment lines
566                if bytes[pos] == b'#' {
567                    while pos < len && bytes[pos] != b'\n' {
568                        pos += 1;
569                    }
570                    if pos < len {
571                        pos += 1;
572                    }
573                    continue;
574                }
575
576                // Check for continuation line (starts with space/tab)
577                if bytes[pos] == b' ' || bytes[pos] == b'\t' {
578                    if fields.is_empty() {
579                        // Indented line before any field - this is an error
580                        let line_start = pos;
581                        while pos < len && bytes[pos] != b'\n' {
582                            pos += 1;
583                        }
584                        let token =
585                            unsafe { std::str::from_utf8_unchecked(&bytes[line_start..pos]) };
586                        return Err(Error::UnexpectedToken(token.to_string()));
587                    }
588
589                    // Skip all leading whitespace (deb822 format strips leading spaces)
590                    while pos < len && (bytes[pos] == b' ' || bytes[pos] == b'\t') {
591                        pos += 1;
592                    }
593
594                    // Read the rest of the continuation line
595                    let line_start = pos;
596                    while pos < len && bytes[pos] != b'\n' {
597                        pos += 1;
598                    }
599
600                    // Add to previous field value
601                    if let Some(last_field) = fields.last_mut() {
602                        last_field.value.push('\n');
603                        last_field.value.push_str(unsafe {
604                            std::str::from_utf8_unchecked(&bytes[line_start..pos])
605                        });
606                    }
607
608                    if pos < len {
609                        pos += 1; // Skip newline
610                    }
611                    continue;
612                }
613
614                // Parse field name
615                let name_start = pos;
616                while pos < len && bytes[pos] != b':' && bytes[pos] != b'\n' {
617                    pos += 1;
618                }
619
620                if pos >= len || bytes[pos] != b':' {
621                    // Invalid line - missing colon or value without key
622                    let line_start = name_start;
623                    while pos < len && bytes[pos] != b'\n' {
624                        pos += 1;
625                    }
626                    let token = unsafe { std::str::from_utf8_unchecked(&bytes[line_start..pos]) };
627                    return Err(Error::UnexpectedToken(token.to_string()));
628                }
629
630                let name = unsafe { std::str::from_utf8_unchecked(&bytes[name_start..pos]) };
631
632                // Check for empty field name (e.g., line starting with ':')
633                if name.is_empty() {
634                    let line_start = name_start;
635                    let mut end = pos;
636                    while end < len && bytes[end] != b'\n' {
637                        end += 1;
638                    }
639                    let token = unsafe { std::str::from_utf8_unchecked(&bytes[line_start..end]) };
640                    return Err(Error::UnexpectedToken(token.to_string()));
641                }
642
643                pos += 1; // Skip colon
644
645                // Skip whitespace after colon
646                while pos < len && (bytes[pos] == b' ' || bytes[pos] == b'\t') {
647                    pos += 1;
648                }
649
650                // Read field value (rest of line)
651                let value_start = pos;
652                while pos < len && bytes[pos] != b'\n' {
653                    pos += 1;
654                }
655
656                let value = unsafe { std::str::from_utf8_unchecked(&bytes[value_start..pos]) };
657
658                fields.push(Field {
659                    name: name.to_string(),
660                    value: value.to_string(),
661                });
662
663                if pos < len {
664                    pos += 1; // Skip newline
665                }
666            }
667
668            if !fields.is_empty() {
669                paragraphs.push(Paragraph { fields });
670            }
671        }
672
673        Ok(Deb822(paragraphs))
674    }
675}
676
677#[cfg(test)]
678mod tests {
679    use super::*;
680
681    #[test]
682    fn test_error_display() {
683        let err = Error::UnexpectedToken("invalid".to_string());
684        assert_eq!(err.to_string(), "Unexpected token: invalid");
685
686        let err = Error::UnexpectedEof;
687        assert_eq!(err.to_string(), "Unexpected end-of-file");
688
689        let err = Error::ExpectedEof;
690        assert_eq!(err.to_string(), "Expected end-of-file");
691
692        let io_err = std::io::Error::other("test error");
693        let err = Error::Io(io_err);
694        assert!(err.to_string().contains("IO error: test error"));
695    }
696
697    #[test]
698    fn test_parse() {
699        let input = r#"Package: hello
700Version: 2.10
701Description: A program that says hello
702 Some more text
703
704Package: world
705Version: 1.0
706Description: A program that says world
707 And some more text
708Another-Field: value
709
710# A comment
711
712"#;
713
714        let mut deb822: Deb822 = input.parse().unwrap();
715        assert_eq!(
716            deb822,
717            Deb822(vec![
718                Paragraph {
719                    fields: vec![
720                        Field {
721                            name: "Package".to_string(),
722                            value: "hello".to_string(),
723                        },
724                        Field {
725                            name: "Version".to_string(),
726                            value: "2.10".to_string(),
727                        },
728                        Field {
729                            name: "Description".to_string(),
730                            value: "A program that says hello\nSome more text".to_string(),
731                        },
732                    ],
733                },
734                Paragraph {
735                    fields: vec![
736                        Field {
737                            name: "Package".to_string(),
738                            value: "world".to_string(),
739                        },
740                        Field {
741                            name: "Version".to_string(),
742                            value: "1.0".to_string(),
743                        },
744                        Field {
745                            name: "Description".to_string(),
746                            value: "A program that says world\nAnd some more text".to_string(),
747                        },
748                        Field {
749                            name: "Another-Field".to_string(),
750                            value: "value".to_string(),
751                        },
752                    ],
753                },
754            ])
755        );
756        assert_eq!(deb822.len(), 2);
757        assert!(!deb822.is_empty());
758        assert_eq!(deb822.iter().count(), 2);
759
760        let para = deb822.iter().next().unwrap();
761        assert_eq!(para.get("Package"), Some("hello"));
762        assert_eq!(para.get("Version"), Some("2.10"));
763        assert_eq!(
764            para.get("Description"),
765            Some("A program that says hello\nSome more text")
766        );
767        assert_eq!(para.get("Another-Field"), None);
768        assert!(!para.is_empty());
769        assert_eq!(para.len(), 3);
770        assert_eq!(
771            para.iter().collect::<Vec<_>>(),
772            vec![
773                ("Package", "hello"),
774                ("Version", "2.10"),
775                ("Description", "A program that says hello\nSome more text"),
776            ]
777        );
778        let para = deb822.iter_mut().next().unwrap();
779        para.insert("Another-Field", "value");
780        assert_eq!(para.get("Another-Field"), Some("value"));
781
782        let mut newpara = Paragraph { fields: vec![] };
783        newpara.insert("Package", "new");
784        assert_eq!(newpara.to_string(), "Package: new\n");
785    }
786
787    #[test]
788    fn test_paragraph_iter() {
789        let input = r#"Package: hello
790Version: 2.10
791"#;
792        let para: Paragraph = input.parse().unwrap();
793        let mut iter = para.into_iter();
794        assert_eq!(
795            iter.next(),
796            Some(("Package".to_string(), "hello".to_string()))
797        );
798        assert_eq!(
799            iter.next(),
800            Some(("Version".to_string(), "2.10".to_string()))
801        );
802        assert_eq!(iter.next(), None);
803    }
804
805    #[test]
806    fn test_format_multiline() {
807        let para = Paragraph {
808            fields: vec![Field {
809                name: "Description".to_string(),
810                value: "A program that says hello\nSome more text".to_string(),
811            }],
812        };
813
814        assert_eq!(
815            para.to_string(),
816            "Description: A program that says hello\n Some more text\n"
817        );
818    }
819
820    #[test]
821    fn test_paragraph_from_str_errors() {
822        // Test ExpectedEof error
823        let result = "Package: foo\n\nPackage: bar\n".parse::<Paragraph>();
824        assert!(matches!(result, Err(Error::ExpectedEof)));
825
826        // Test UnexpectedEof error
827        let result = "".parse::<Paragraph>();
828        assert!(matches!(result, Err(Error::UnexpectedEof)));
829    }
830
831    #[test]
832    fn test_from_vec() {
833        let fields = vec![
834            ("Package".to_string(), "hello".to_string()),
835            ("Version".to_string(), "1.0".to_string()),
836        ];
837
838        let para: Paragraph = fields.into();
839        assert_eq!(para.get("Package"), Some("hello"));
840        assert_eq!(para.get("Version"), Some("1.0"));
841    }
842
843    #[test]
844    fn test_unexpected_tokens() {
845        // Test parsing with unexpected tokens
846        let input = "Value before key\nPackage: hello\n";
847        let result = input.parse::<Deb822>();
848        assert!(matches!(result, Err(Error::UnexpectedToken(_))));
849
850        // Test parsing with missing colon after key
851        let input = "Package hello\n";
852        let result = input.parse::<Deb822>();
853        assert!(matches!(result, Err(Error::UnexpectedToken(_))));
854
855        // Test parsing with unexpected indent
856        let input = " Indented: value\n";
857        let result = input.parse::<Deb822>();
858        assert!(matches!(result, Err(Error::UnexpectedToken(_))));
859
860        // Test parsing with unexpected value
861        let input = "Key: value\nvalue without key\n";
862        let result = input.parse::<Deb822>();
863        assert!(matches!(result, Err(Error::UnexpectedToken(_))));
864
865        // Test parsing with unexpected colon
866        let input = "Key: value\n:\n";
867        let result = input.parse::<Deb822>();
868        assert!(matches!(result, Err(Error::UnexpectedToken(_))));
869    }
870
871    #[test]
872    fn test_parse_continuation_with_colon() {
873        // Test that continuation lines with colons are properly parsed
874        let input = "Package: test\nDescription: short\n line: with colon\n";
875        let result = input.parse::<Deb822>();
876        assert!(result.is_ok());
877
878        let deb822 = result.unwrap();
879        assert_eq!(deb822.0.len(), 1);
880        assert_eq!(deb822.0[0].fields.len(), 2);
881        assert_eq!(deb822.0[0].fields[0].name, "Package");
882        assert_eq!(deb822.0[0].fields[0].value, "test");
883        assert_eq!(deb822.0[0].fields[1].name, "Description");
884        assert_eq!(deb822.0[0].fields[1].value, "short\nline: with colon");
885    }
886
887    #[test]
888    fn test_parse_continuation_starting_with_colon() {
889        // Test continuation line STARTING with a colon (issue #315)
890        let input = "Package: test\nDescription: short\n :value\n";
891        let result = input.parse::<Deb822>();
892        assert!(result.is_ok());
893
894        let deb822 = result.unwrap();
895        assert_eq!(deb822.0.len(), 1);
896        assert_eq!(deb822.0[0].fields.len(), 2);
897        assert_eq!(deb822.0[0].fields[0].name, "Package");
898        assert_eq!(deb822.0[0].fields[0].value, "test");
899        assert_eq!(deb822.0[0].fields[1].name, "Description");
900        assert_eq!(deb822.0[0].fields[1].value, "short\n:value");
901    }
902
903    #[test]
904    fn test_from_reader() {
905        // Test Deb822::from_reader with valid input
906        let input = "Package: hello\nVersion: 1.0\n";
907        let result = Deb822::from_reader(input.as_bytes()).unwrap();
908        assert_eq!(result.len(), 1);
909        let para = result.iter().next().unwrap();
910        assert_eq!(para.get("Package"), Some("hello"));
911
912        // Test with IO error
913        use std::io::Error as IoError;
914        struct FailingReader;
915        impl std::io::Read for FailingReader {
916            fn read(&mut self, _: &mut [u8]) -> std::io::Result<usize> {
917                Err(IoError::other("test error"))
918            }
919        }
920
921        let result = Deb822::from_reader(FailingReader);
922        assert!(matches!(result, Err(Error::Io(_))));
923    }
924
925    #[test]
926    fn test_deb822_vec_conversion() {
927        let paragraphs = vec![
928            Paragraph {
929                fields: vec![Field {
930                    name: "Package".to_string(),
931                    value: "hello".to_string(),
932                }],
933            },
934            Paragraph {
935                fields: vec![Field {
936                    name: "Package".to_string(),
937                    value: "world".to_string(),
938                }],
939            },
940        ];
941
942        let deb822 = Deb822(paragraphs.clone());
943        let vec: Vec<Paragraph> = deb822.into();
944        assert_eq!(vec, paragraphs);
945    }
946
947    #[test]
948    fn test_deb822_iteration() {
949        let paragraphs = vec![
950            Paragraph {
951                fields: vec![Field {
952                    name: "Package".to_string(),
953                    value: "hello".to_string(),
954                }],
955            },
956            Paragraph {
957                fields: vec![Field {
958                    name: "Package".to_string(),
959                    value: "world".to_string(),
960                }],
961            },
962        ];
963
964        let deb822 = Deb822(paragraphs.clone());
965
966        // Test IntoIterator implementation
967        let collected: Vec<_> = deb822.into_iter().collect();
968        assert_eq!(collected, paragraphs);
969
970        // Test iter() and iter_mut()
971        let deb822 = Deb822(paragraphs.clone());
972        let iter_refs: Vec<&Paragraph> = deb822.iter().collect();
973        assert_eq!(iter_refs.len(), 2);
974        assert_eq!(iter_refs[0].get("Package"), Some("hello"));
975
976        let mut deb822 = Deb822(paragraphs.clone());
977        for para in deb822.iter_mut() {
978            if para.get("Package") == Some("hello") {
979                para.set("Version", "1.0");
980            }
981        }
982        assert_eq!(deb822.iter().next().unwrap().get("Version"), Some("1.0"));
983    }
984
985    #[test]
986    fn test_empty_collections() {
987        // Test empty Deb822
988        let deb822 = Deb822(vec![]);
989        assert!(deb822.is_empty());
990        assert_eq!(deb822.len(), 0);
991        assert_eq!(deb822.iter().count(), 0);
992
993        // Test empty Paragraph
994        let para = Paragraph { fields: vec![] };
995        assert!(para.is_empty());
996        assert_eq!(para.len(), 0);
997        assert_eq!(para.iter().count(), 0);
998        assert_eq!(para.get("Any"), None);
999
1000        // Test formatting of empty paragraph
1001        assert_eq!(para.to_string(), "");
1002
1003        // Test formatting of empty Deb822
1004        assert_eq!(deb822.to_string(), "");
1005    }
1006
1007    #[test]
1008    fn test_paragraph_mutable_iteration() {
1009        let mut para = Paragraph {
1010            fields: vec![
1011                Field {
1012                    name: "First".to_string(),
1013                    value: "1".to_string(),
1014                },
1015                Field {
1016                    name: "Second".to_string(),
1017                    value: "2".to_string(),
1018                },
1019            ],
1020        };
1021
1022        // Test iter_mut
1023        for (_, value) in para.iter_mut() {
1024            *value = format!("{}0", value);
1025        }
1026
1027        assert_eq!(para.get("First"), Some("10"));
1028        assert_eq!(para.get("Second"), Some("20"));
1029    }
1030
1031    #[test]
1032    fn test_insert_duplicate_key() {
1033        let mut para = Paragraph {
1034            fields: vec![Field {
1035                name: "Key".to_string(),
1036                value: "Value1".to_string(),
1037            }],
1038        };
1039
1040        // Insert will add a new field, even if the key already exists
1041        para.insert("Key", "Value2");
1042
1043        assert_eq!(para.fields.len(), 2);
1044        assert_eq!(para.fields[0].value, "Value1");
1045        assert_eq!(para.fields[1].value, "Value2");
1046
1047        // But get() will return the first occurrence
1048        assert_eq!(para.get("Key"), Some("Value1"));
1049    }
1050
1051    #[test]
1052    fn test_multiline_field_format() {
1053        // Test display formatting for multiline field values
1054        let field = Field {
1055            name: "MultiField".to_string(),
1056            value: "line1\nline2\nline3".to_string(),
1057        };
1058
1059        let formatted = format!("{}", field);
1060        assert_eq!(formatted, "MultiField: line1\n line2\n line3\n");
1061
1062        // Test formatting within paragraph context
1063        let para = Paragraph {
1064            fields: vec![field],
1065        };
1066
1067        let formatted = format!("{}", para);
1068        assert_eq!(formatted, "MultiField: line1\n line2\n line3\n");
1069    }
1070
1071    #[test]
1072    fn test_paragraph_parsing_edge_cases() {
1073        // Test parsing empty value
1074        let input = "Key:\n";
1075        let para: Paragraph = input.parse().unwrap();
1076        assert_eq!(para.get("Key"), Some(""));
1077
1078        // Test parsing value with just whitespace
1079        // Note: whitespace after the colon appears to be trimmed by the parser
1080        let input = "Key:    \n";
1081        let para: Paragraph = input.parse().unwrap();
1082        assert_eq!(para.get("Key"), Some(""));
1083
1084        // Test parsing multiple empty lines between paragraphs
1085        let input = "Key1: value1\n\n\n\nKey2: value2\n";
1086        let deb822: Deb822 = input.parse().unwrap();
1087        assert_eq!(deb822.len(), 2);
1088
1089        // Test parsing complex indentation
1090        // The parser preserves the indentation from the original file
1091        let input = "Key: value\n with\n  indentation\n   levels\n";
1092        let para: Paragraph = input.parse().unwrap();
1093        assert_eq!(para.get("Key"), Some("value\nwith\nindentation\nlevels"));
1094    }
1095
1096    #[test]
1097    fn test_parse_complex() {
1098        // Test various edge cases in the parser
1099        let input = "# Comment at start\nKey1: val1\nKey2: \n indented\nKey3: val3\n\n# Comment between paragraphs\n\nKey4: val4\n";
1100        let deb822: Deb822 = input.parse().unwrap();
1101
1102        assert_eq!(deb822.len(), 2);
1103        let paragraphs: Vec<Paragraph> = deb822.into();
1104
1105        assert_eq!(paragraphs[0].get("Key2"), Some("\nindented"));
1106        assert_eq!(paragraphs[1].get("Key4"), Some("val4"));
1107
1108        // Test parsing with an indented line immediately after a key
1109        let input = "Key:\n indented value\n";
1110        let para: Paragraph = input.parse().unwrap();
1111        assert_eq!(para.get("Key"), Some("\nindented value"));
1112    }
1113
1114    #[test]
1115    fn test_deb822_display() {
1116        // Test the Deb822::fmt Display implementation (lines 158-164)
1117        let para1 = Paragraph {
1118            fields: vec![Field {
1119                name: "Key1".to_string(),
1120                value: "Value1".to_string(),
1121            }],
1122        };
1123
1124        let para2 = Paragraph {
1125            fields: vec![Field {
1126                name: "Key2".to_string(),
1127                value: "Value2".to_string(),
1128            }],
1129        };
1130
1131        let deb822 = Deb822(vec![para1, para2]);
1132        let formatted = format!("{}", deb822);
1133
1134        assert_eq!(formatted, "Key1: Value1\n\nKey2: Value2\n");
1135    }
1136
1137    #[test]
1138    fn test_parser_edge_cases() {
1139        // Let's focus on testing various parser behaviors rather than expecting errors
1140
1141        // Test comment handling
1142        let input = "# Comment\nKey: value";
1143        let deb822: Deb822 = input.parse().unwrap();
1144        assert_eq!(deb822.len(), 1);
1145
1146        // Test for unexpected token at line 303
1147        let input = "Key: value\n .indented";
1148        let deb822: Deb822 = input.parse().unwrap();
1149        assert_eq!(
1150            deb822.iter().next().unwrap().get("Key"),
1151            Some("value\n.indented")
1152        );
1153
1154        // Test multi-line values
1155        let input = "Key: value\n line1\n line2\n\nNextKey: value";
1156        let deb822: Deb822 = input.parse().unwrap();
1157        assert_eq!(deb822.len(), 2);
1158        assert_eq!(
1159            deb822.iter().next().unwrap().get("Key"),
1160            Some("value\nline1\nline2")
1161        );
1162    }
1163
1164    #[test]
1165    fn test_iter_paragraphs_from_reader() {
1166        use std::io::BufReader;
1167
1168        let input = r#"Package: hello
1169Version: 2.10
1170Description: A program that says hello
1171 Some more text
1172
1173Package: world
1174Version: 1.0
1175Description: A program that says world
1176 And some more text
1177Another-Field: value
1178
1179# A comment
1180
1181"#;
1182
1183        let reader = BufReader::new(input.as_bytes());
1184        let paragraphs: Result<Vec<_>, _> = Deb822::iter_paragraphs_from_reader(reader).collect();
1185        let paragraphs = paragraphs.unwrap();
1186
1187        assert_eq!(paragraphs.len(), 2);
1188
1189        assert_eq!(paragraphs[0].get("Package"), Some("hello"));
1190        assert_eq!(paragraphs[0].get("Version"), Some("2.10"));
1191        assert_eq!(
1192            paragraphs[0].get("Description"),
1193            Some("A program that says hello\nSome more text")
1194        );
1195
1196        assert_eq!(paragraphs[1].get("Package"), Some("world"));
1197        assert_eq!(paragraphs[1].get("Version"), Some("1.0"));
1198        assert_eq!(
1199            paragraphs[1].get("Description"),
1200            Some("A program that says world\nAnd some more text")
1201        );
1202        assert_eq!(paragraphs[1].get("Another-Field"), Some("value"));
1203    }
1204
1205    #[test]
1206    fn test_iter_paragraphs_from_reader_empty() {
1207        use std::io::BufReader;
1208
1209        let input = "";
1210        let reader = BufReader::new(input.as_bytes());
1211        let paragraphs: Result<Vec<_>, _> = Deb822::iter_paragraphs_from_reader(reader).collect();
1212        let paragraphs = paragraphs.unwrap();
1213
1214        assert_eq!(paragraphs.len(), 0);
1215    }
1216
1217    #[test]
1218    fn test_iter_paragraphs_from_reader_with_leading_comments() {
1219        use std::io::BufReader;
1220
1221        let input = r#"# Leading comment
1222# Another comment
1223
1224Package: test
1225Version: 1.0
1226"#;
1227
1228        let reader = BufReader::new(input.as_bytes());
1229        let paragraphs: Result<Vec<_>, _> = Deb822::iter_paragraphs_from_reader(reader).collect();
1230        let paragraphs = paragraphs.unwrap();
1231
1232        assert_eq!(paragraphs.len(), 1);
1233        assert_eq!(paragraphs[0].get("Package"), Some("test"));
1234    }
1235}