Skip to main content

structured_email_address/
error.rs

1//! Error types for email address parsing and validation.
2
3use core::fmt;
4
5/// Error returned when parsing or validating an email address fails.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Error {
8    kind: ErrorKind,
9    position: usize,
10}
11
12/// The specific kind of error that occurred.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum ErrorKind {
15    /// Input is empty or whitespace-only.
16    Empty,
17    /// Missing `@` separator.
18    MissingAtSign,
19    /// Local part is empty (nothing before `@`).
20    EmptyLocalPart,
21    /// Domain is empty (nothing after `@`).
22    EmptyDomain,
23    /// Local part exceeds 64 octets (RFC 5321 §4.5.3.1.1).
24    LocalPartTooLong { len: usize },
25    /// Total address exceeds 254 octets (RFC 5321 §4.5.3.1.3).
26    AddressTooLong { len: usize },
27    /// Domain label exceeds 63 octets (RFC 1035 §2.3.4).
28    DomainLabelTooLong { label: String, len: usize },
29    /// Invalid character in local part.
30    InvalidLocalPartChar { ch: char },
31    /// Invalid character in domain.
32    InvalidDomainChar { ch: char },
33    /// Domain label starts or ends with hyphen.
34    DomainLabelHyphen,
35    /// Domain has no dot (single label, not a valid internet domain).
36    DomainNoDot,
37    /// Unterminated quoted string.
38    UnterminatedQuotedString,
39    /// Invalid quoted-pair sequence.
40    InvalidQuotedPair,
41    /// Unterminated comment.
42    UnterminatedComment,
43    /// Unterminated domain literal `[...]`.
44    UnterminatedDomainLiteral,
45    /// IDNA encoding failed for domain.
46    IdnaError(String),
47    /// Domain not in Public Suffix List (when PSL validation enabled).
48    UnknownTld(String),
49    /// Generic parse failure at position.
50    Unexpected { ch: char },
51}
52
53impl Error {
54    /// Create a new error of the given kind at the given byte position.
55    pub(crate) fn new(kind: ErrorKind, position: usize) -> Self {
56        Self { kind, position }
57    }
58
59    /// The kind of error.
60    pub fn kind(&self) -> &ErrorKind {
61        &self.kind
62    }
63
64    /// Byte offset in the input where the error was detected.
65    pub fn position(&self) -> usize {
66        self.position
67    }
68}
69
70impl fmt::Display for Error {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        match &self.kind {
73            ErrorKind::Empty => write!(f, "empty input"),
74            ErrorKind::MissingAtSign => write!(f, "missing '@' separator"),
75            ErrorKind::EmptyLocalPart => write!(f, "empty local part"),
76            ErrorKind::EmptyDomain => write!(f, "empty domain"),
77            ErrorKind::LocalPartTooLong { len } => {
78                write!(f, "local part too long: {len} octets (max 64)")
79            }
80            ErrorKind::AddressTooLong { len } => {
81                write!(f, "address too long: {len} octets (max 254)")
82            }
83            ErrorKind::DomainLabelTooLong { label, len } => {
84                write!(f, "domain label '{label}' too long: {len} octets (max 63)")
85            }
86            ErrorKind::InvalidLocalPartChar { ch } => {
87                write!(f, "invalid character in local part: '{ch}'")
88            }
89            ErrorKind::InvalidDomainChar { ch } => {
90                write!(f, "invalid character in domain: '{ch}'")
91            }
92            ErrorKind::DomainLabelHyphen => {
93                write!(f, "domain label starts or ends with hyphen")
94            }
95            ErrorKind::DomainNoDot => write!(f, "domain has no dot"),
96            ErrorKind::UnterminatedQuotedString => write!(f, "unterminated quoted string"),
97            ErrorKind::InvalidQuotedPair => write!(f, "invalid quoted-pair escape"),
98            ErrorKind::UnterminatedComment => write!(f, "unterminated comment"),
99            ErrorKind::UnterminatedDomainLiteral => write!(f, "unterminated domain literal"),
100            ErrorKind::IdnaError(msg) => write!(f, "IDNA encoding failed: {msg}"),
101            ErrorKind::UnknownTld(tld) => write!(f, "unknown TLD: .{tld}"),
102            ErrorKind::Unexpected { ch } => {
103                write!(
104                    f,
105                    "unexpected character '{ch}' at position {}",
106                    self.position
107                )
108            }
109        }
110    }
111}
112
113impl std::error::Error for Error {}