Skip to main content

dotenvpp_parser/
error.rs

1//! Error types for the DotenvPP parser.
2
3#[cfg(not(feature = "std"))]
4use alloc::string::String;
5use core::fmt;
6#[cfg(feature = "std")]
7use std::string::String;
8
9/// Errors that can occur while parsing `.env` content.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum ParseError {
12    /// A line is missing the `=` separator between key and value.
13    MissingSeparator {
14        /// The 1-based line number where the error occurred.
15        line: usize,
16        /// The raw content of the offending line.
17        content: String,
18    },
19
20    /// A key is empty (nothing before `=`).
21    EmptyKey {
22        /// The 1-based line number where the error occurred.
23        line: usize,
24    },
25
26    /// A key contains invalid characters.
27    InvalidKey {
28        /// The 1-based line number where the error occurred.
29        line: usize,
30        /// The invalid key.
31        key: String,
32    },
33
34    /// An unterminated quoted value (missing closing quote).
35    UnterminatedQuote {
36        /// The 1-based line number where the quote started.
37        line: usize,
38        /// The quote character (' or ").
39        quote: char,
40    },
41}
42
43impl fmt::Display for ParseError {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            Self::MissingSeparator {
47                line,
48                ..
49            } => {
50                write!(f, "line {line}: missing `=` separator")
51            }
52            Self::EmptyKey {
53                line,
54            } => {
55                write!(f, "line {line}: key is empty")
56            }
57            Self::InvalidKey {
58                line,
59                key,
60            } => {
61                write!(
62                    f,
63                    "line {line}: invalid key `{key}` — keys must be \
64                     ASCII alphanumeric, underscores, or dots"
65                )
66            }
67            Self::UnterminatedQuote {
68                line,
69                quote,
70            } => {
71                write!(f, "line {line}: unterminated {quote}-quoted value")
72            }
73        }
74    }
75}
76
77#[cfg(feature = "std")]
78impl std::error::Error for ParseError {}
79
80#[cfg(test)]
81mod tests {
82    #![allow(clippy::unwrap_used)]
83
84    use super::ParseError;
85
86    #[test]
87    fn missing_separator_display_redacts_content() {
88        let err = ParseError::MissingSeparator {
89            line: 3,
90            content: "API_KEY abc123".into(),
91        };
92        let msg = format!("{err}");
93
94        assert!(msg.contains("line 3"));
95        assert!(msg.contains("missing `=` separator"));
96        assert!(!msg.contains("API_KEY"));
97        assert!(!msg.contains("abc123"));
98    }
99
100    #[test]
101    fn display_invalid_key() {
102        let err = ParseError::InvalidKey {
103            line: 4,
104            key: "BAD-KEY".into(),
105        };
106        let msg = format!("{err}");
107        assert!(msg.contains("line 4"));
108        assert!(msg.contains("BAD-KEY"));
109        assert!(msg.contains("underscores"));
110    }
111
112    #[test]
113    fn display_unterminated_quote() {
114        let err = ParseError::UnterminatedQuote {
115            line: 7,
116            quote: '"',
117        };
118        let msg = format!("{err}");
119        assert!(msg.contains("line 7"));
120        assert!(msg.contains("unterminated"));
121        assert!(msg.contains('"'));
122    }
123}