1#[cfg(not(feature = "std"))]
4use alloc::string::String;
5use core::fmt;
6#[cfg(feature = "std")]
7use std::string::String;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum ParseError {
12 MissingSeparator {
14 line: usize,
16 content: String,
18 },
19
20 EmptyKey {
22 line: usize,
24 },
25
26 InvalidKey {
28 line: usize,
30 key: String,
32 },
33
34 UnterminatedQuote {
36 line: usize,
38 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}