structured_email_address/
error.rs1use core::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Error {
8 kind: ErrorKind,
9 position: usize,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum ErrorKind {
15 Empty,
17 MissingAtSign,
19 EmptyLocalPart,
21 EmptyDomain,
23 LocalPartTooLong { len: usize },
25 AddressTooLong { len: usize },
27 DomainLabelTooLong { label: String, len: usize },
29 InvalidLocalPartChar { ch: char },
31 InvalidDomainChar { ch: char },
33 DomainLabelHyphen,
35 DomainNoDot,
37 UnterminatedQuotedString,
39 InvalidQuotedPair,
41 UnterminatedComment,
43 UnterminatedDomainLiteral,
45 InvalidAddressLiteral,
48 IdnaError(String),
50 UnknownTld(String),
52 Unexpected { ch: char },
54}
55
56impl Error {
57 pub(crate) fn new(kind: ErrorKind, position: usize) -> Self {
59 Self { kind, position }
60 }
61
62 pub fn kind(&self) -> &ErrorKind {
64 &self.kind
65 }
66
67 pub fn position(&self) -> usize {
69 self.position
70 }
71}
72
73impl fmt::Display for Error {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match &self.kind {
76 ErrorKind::Empty => write!(f, "empty input"),
77 ErrorKind::MissingAtSign => write!(f, "missing '@' separator"),
78 ErrorKind::EmptyLocalPart => write!(f, "empty local part"),
79 ErrorKind::EmptyDomain => write!(f, "empty domain"),
80 ErrorKind::LocalPartTooLong { len } => {
81 write!(f, "local part too long: {len} octets (max 64)")
82 }
83 ErrorKind::AddressTooLong { len } => {
84 write!(f, "address too long: {len} octets (max 254)")
85 }
86 ErrorKind::DomainLabelTooLong { label, len } => {
87 write!(f, "domain label '{label}' too long: {len} octets (max 63)")
88 }
89 ErrorKind::InvalidLocalPartChar { ch } => {
90 write!(f, "invalid character in local part: '{ch}'")
91 }
92 ErrorKind::InvalidDomainChar { ch } => {
93 write!(f, "invalid character in domain: '{ch}'")
94 }
95 ErrorKind::DomainLabelHyphen => {
96 write!(f, "domain label starts or ends with hyphen")
97 }
98 ErrorKind::DomainNoDot => write!(f, "domain has no dot"),
99 ErrorKind::UnterminatedQuotedString => write!(f, "unterminated quoted string"),
100 ErrorKind::InvalidQuotedPair => write!(f, "invalid quoted-pair escape"),
101 ErrorKind::UnterminatedComment => write!(f, "unterminated comment"),
102 ErrorKind::UnterminatedDomainLiteral => write!(f, "unterminated domain literal"),
103 ErrorKind::InvalidAddressLiteral => {
104 write!(
105 f,
106 "domain literal is not a valid IPv4 or IPv6 address literal"
107 )
108 }
109 ErrorKind::IdnaError(msg) => write!(f, "IDNA encoding failed: {msg}"),
110 ErrorKind::UnknownTld(tld) => write!(f, "unknown TLD: .{tld}"),
111 ErrorKind::Unexpected { ch } => {
112 write!(
113 f,
114 "unexpected character '{ch}' at position {}",
115 self.position
116 )
117 }
118 }
119 }
120}
121
122impl std::error::Error for Error {}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn display_and_accessors_cover_all_kinds() {
130 let kinds = [
133 ErrorKind::Empty,
134 ErrorKind::MissingAtSign,
135 ErrorKind::EmptyLocalPart,
136 ErrorKind::EmptyDomain,
137 ErrorKind::LocalPartTooLong { len: 65 },
138 ErrorKind::AddressTooLong { len: 300 },
139 ErrorKind::DomainLabelTooLong {
140 label: "x".to_string(),
141 len: 64,
142 },
143 ErrorKind::InvalidLocalPartChar { ch: '(' },
144 ErrorKind::InvalidDomainChar { ch: '[' },
145 ErrorKind::DomainLabelHyphen,
146 ErrorKind::DomainNoDot,
147 ErrorKind::UnterminatedQuotedString,
148 ErrorKind::InvalidQuotedPair,
149 ErrorKind::UnterminatedComment,
150 ErrorKind::UnterminatedDomainLiteral,
151 ErrorKind::InvalidAddressLiteral,
152 ErrorKind::IdnaError("boom".to_string()),
153 ErrorKind::UnknownTld("zzz".to_string()),
154 ErrorKind::Unexpected { ch: '!' },
155 ];
156 for (pos, kind) in kinds.into_iter().enumerate() {
157 let err = Error::new(kind.clone(), pos);
158 assert!(!err.to_string().is_empty(), "empty Display for {kind:?}");
159 assert_eq!(err.kind(), &kind);
160 assert_eq!(err.position(), pos);
161 }
162 }
163}