use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error {
kind: ErrorKind,
position: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorKind {
Empty,
MissingAtSign,
EmptyLocalPart,
EmptyDomain,
LocalPartTooLong { len: usize },
AddressTooLong { len: usize },
DomainLabelTooLong { label: String, len: usize },
InvalidLocalPartChar { ch: char },
InvalidDomainChar { ch: char },
DomainLabelHyphen,
DomainNoDot,
UnterminatedQuotedString,
InvalidQuotedPair,
UnterminatedComment,
UnterminatedDomainLiteral,
InvalidAddressLiteral,
IdnaError(String),
UnknownTld(String),
Unexpected { ch: char },
}
impl Error {
pub(crate) fn new(kind: ErrorKind, position: usize) -> Self {
Self { kind, position }
}
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
pub fn position(&self) -> usize {
self.position
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
ErrorKind::Empty => write!(f, "empty input"),
ErrorKind::MissingAtSign => write!(f, "missing '@' separator"),
ErrorKind::EmptyLocalPart => write!(f, "empty local part"),
ErrorKind::EmptyDomain => write!(f, "empty domain"),
ErrorKind::LocalPartTooLong { len } => {
write!(f, "local part too long: {len} octets (max 64)")
}
ErrorKind::AddressTooLong { len } => {
write!(f, "address too long: {len} octets (max 254)")
}
ErrorKind::DomainLabelTooLong { label, len } => {
write!(f, "domain label '{label}' too long: {len} octets (max 63)")
}
ErrorKind::InvalidLocalPartChar { ch } => {
write!(f, "invalid character in local part: '{ch}'")
}
ErrorKind::InvalidDomainChar { ch } => {
write!(f, "invalid character in domain: '{ch}'")
}
ErrorKind::DomainLabelHyphen => {
write!(f, "domain label starts or ends with hyphen")
}
ErrorKind::DomainNoDot => write!(f, "domain has no dot"),
ErrorKind::UnterminatedQuotedString => write!(f, "unterminated quoted string"),
ErrorKind::InvalidQuotedPair => write!(f, "invalid quoted-pair escape"),
ErrorKind::UnterminatedComment => write!(f, "unterminated comment"),
ErrorKind::UnterminatedDomainLiteral => write!(f, "unterminated domain literal"),
ErrorKind::InvalidAddressLiteral => {
write!(
f,
"domain literal is not a valid IPv4 or IPv6 address literal"
)
}
ErrorKind::IdnaError(msg) => write!(f, "IDNA encoding failed: {msg}"),
ErrorKind::UnknownTld(tld) => write!(f, "unknown TLD: .{tld}"),
ErrorKind::Unexpected { ch } => {
write!(
f,
"unexpected character '{ch}' at position {}",
self.position
)
}
}
}
}
impl std::error::Error for Error {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_and_accessors_cover_all_kinds() {
let kinds = [
ErrorKind::Empty,
ErrorKind::MissingAtSign,
ErrorKind::EmptyLocalPart,
ErrorKind::EmptyDomain,
ErrorKind::LocalPartTooLong { len: 65 },
ErrorKind::AddressTooLong { len: 300 },
ErrorKind::DomainLabelTooLong {
label: "x".to_string(),
len: 64,
},
ErrorKind::InvalidLocalPartChar { ch: '(' },
ErrorKind::InvalidDomainChar { ch: '[' },
ErrorKind::DomainLabelHyphen,
ErrorKind::DomainNoDot,
ErrorKind::UnterminatedQuotedString,
ErrorKind::InvalidQuotedPair,
ErrorKind::UnterminatedComment,
ErrorKind::UnterminatedDomainLiteral,
ErrorKind::InvalidAddressLiteral,
ErrorKind::IdnaError("boom".to_string()),
ErrorKind::UnknownTld("zzz".to_string()),
ErrorKind::Unexpected { ch: '!' },
];
for (pos, kind) in kinds.into_iter().enumerate() {
let err = Error::new(kind.clone(), pos);
assert!(!err.to_string().is_empty(), "empty Display for {kind:?}");
assert_eq!(err.kind(), &kind);
assert_eq!(err.position(), pos);
}
}
}