deb822_fast/
lib.rs

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