#[cfg(not(feature = "std"))]
use alloc::string::String;
use core::fmt;
#[cfg(feature = "std")]
use std::string::String;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError {
MissingSeparator {
line: usize,
content: String,
},
EmptyKey {
line: usize,
},
InvalidKey {
line: usize,
key: String,
},
UnterminatedQuote {
line: usize,
quote: char,
},
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingSeparator {
line,
..
} => {
write!(f, "line {line}: missing `=` separator")
}
Self::EmptyKey {
line,
} => {
write!(f, "line {line}: key is empty")
}
Self::InvalidKey {
line,
key,
} => {
write!(
f,
"line {line}: invalid key `{key}` — keys must be \
ASCII alphanumeric, underscores, or dots"
)
}
Self::UnterminatedQuote {
line,
quote,
} => {
write!(f, "line {line}: unterminated {quote}-quoted value")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseError {}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::ParseError;
#[test]
fn missing_separator_display_redacts_content() {
let err = ParseError::MissingSeparator {
line: 3,
content: "API_KEY abc123".into(),
};
let msg = format!("{err}");
assert!(msg.contains("line 3"));
assert!(msg.contains("missing `=` separator"));
assert!(!msg.contains("API_KEY"));
assert!(!msg.contains("abc123"));
}
#[test]
fn display_invalid_key() {
let err = ParseError::InvalidKey {
line: 4,
key: "BAD-KEY".into(),
};
let msg = format!("{err}");
assert!(msg.contains("line 4"));
assert!(msg.contains("BAD-KEY"));
assert!(msg.contains("underscores"));
}
#[test]
fn display_unterminated_quote() {
let err = ParseError::UnterminatedQuote {
line: 7,
quote: '"',
};
let msg = format!("{err}");
assert!(msg.contains("line 7"));
assert!(msg.contains("unterminated"));
assert!(msg.contains('"'));
}
}