1use std::sync::Arc;
7
8use crate::types::SmtpResponse;
9
10#[non_exhaustive]
17#[derive(Debug, Clone, thiserror::Error)]
18pub enum Error {
19 #[error("I/O error: {0}")]
23 Io(#[source] Arc<std::io::Error>),
24
25 #[error("authentication failed: {message}")]
30 Auth {
31 message: String,
32 response: SmtpResponse,
33 },
34
35 #[error("permanent failure ({code}): {message}")]
37 Permanent {
38 code: u16,
39 message: String,
40 response: SmtpResponse,
41 },
42
43 #[error("transient failure ({code}): {message}")]
45 Transient {
46 code: u16,
47 message: String,
48 response: SmtpResponse,
49 },
50
51 #[error("protocol error: {0}")]
53 Protocol(String),
54
55 #[error("parse error: {0}")]
57 Parse(String),
58
59 #[error("operation timed out")]
61 Timeout,
62
63 #[error("connection closed")]
65 Closed,
66
67 #[error("STARTTLS not supported by server")]
69 StartTlsUnavailable,
70
71 #[error("all {count} recipients were rejected")]
73 AllRecipientsFailed {
74 count: usize,
75 responses: Vec<SmtpResponse>,
76 },
77
78 #[error(
81 "message requires SMTPUTF8 but the server does not advertise it \
82 (RFC 6531 Sections 3.1, 3.3, 3.4)"
83 )]
84 SmtpUtf8Required,
85}
86
87impl PartialEq for Error {
93 fn eq(&self, other: &Self) -> bool {
94 match (self, other) {
95 (Self::Io(a), Self::Io(b)) => a.kind() == b.kind(),
96 (
97 Self::Auth {
98 message: m1,
99 response: r1,
100 },
101 Self::Auth {
102 message: m2,
103 response: r2,
104 },
105 ) => m1 == m2 && r1 == r2,
106 (
107 Self::Permanent {
108 code: c1,
109 message: m1,
110 response: r1,
111 },
112 Self::Permanent {
113 code: c2,
114 message: m2,
115 response: r2,
116 },
117 )
118 | (
119 Self::Transient {
120 code: c1,
121 message: m1,
122 response: r1,
123 },
124 Self::Transient {
125 code: c2,
126 message: m2,
127 response: r2,
128 },
129 ) => c1 == c2 && m1 == m2 && r1 == r2,
130 (Self::Protocol(a), Self::Protocol(b)) | (Self::Parse(a), Self::Parse(b)) => a == b,
131 (Self::Timeout, Self::Timeout)
132 | (Self::Closed, Self::Closed)
133 | (Self::StartTlsUnavailable, Self::StartTlsUnavailable)
134 | (Self::SmtpUtf8Required, Self::SmtpUtf8Required) => true,
135 (
136 Self::AllRecipientsFailed {
137 count: c1,
138 responses: r1,
139 },
140 Self::AllRecipientsFailed {
141 count: c2,
142 responses: r2,
143 },
144 ) => c1 == c2 && r1 == r2,
145 _ => false,
146 }
147 }
148}
149
150impl Eq for Error {}
151
152impl From<std::io::Error> for Error {
153 fn from(e: std::io::Error) -> Self {
154 Self::Io(Arc::new(e))
155 }
156}
157
158impl From<crate::types::ValidationError> for Error {
159 fn from(e: crate::types::ValidationError) -> Self {
160 Self::Protocol(e.to_string())
161 }
162}
163
164impl Error {
165 pub fn is_transient(&self) -> bool {
170 match self {
171 Self::Transient { .. } | Self::Io(_) | Self::Timeout => true,
172 Self::Auth { response, .. } => response.is_transient_error(),
174 _ => false,
175 }
176 }
177
178 pub fn is_permanent(&self) -> bool {
183 match self {
184 Self::Permanent { .. } => true,
185 Self::Auth { response, .. } => response.is_permanent_error(),
187 _ => false,
188 }
189 }
190}
191
192#[cfg(feature = "serde")]
197mod serde_support {
198 use super::{Arc, Error, SmtpResponse};
199 use serde::{Deserialize, Deserializer, Serialize, Serializer};
200
201 fn error_kind_to_str(kind: std::io::ErrorKind) -> &'static str {
204 match kind {
205 std::io::ErrorKind::NotFound => "NotFound",
206 std::io::ErrorKind::PermissionDenied => "PermissionDenied",
207 std::io::ErrorKind::ConnectionRefused => "ConnectionRefused",
208 std::io::ErrorKind::ConnectionReset => "ConnectionReset",
209 std::io::ErrorKind::ConnectionAborted => "ConnectionAborted",
210 std::io::ErrorKind::NotConnected => "NotConnected",
211 std::io::ErrorKind::AddrInUse => "AddrInUse",
212 std::io::ErrorKind::AddrNotAvailable => "AddrNotAvailable",
213 std::io::ErrorKind::BrokenPipe => "BrokenPipe",
214 std::io::ErrorKind::AlreadyExists => "AlreadyExists",
215 std::io::ErrorKind::WouldBlock => "WouldBlock",
216 std::io::ErrorKind::InvalidInput => "InvalidInput",
217 std::io::ErrorKind::InvalidData => "InvalidData",
218 std::io::ErrorKind::TimedOut => "TimedOut",
219 std::io::ErrorKind::WriteZero => "WriteZero",
220 std::io::ErrorKind::Interrupted => "Interrupted",
221 std::io::ErrorKind::Unsupported => "Unsupported",
222 std::io::ErrorKind::UnexpectedEof => "UnexpectedEof",
223 std::io::ErrorKind::OutOfMemory => "OutOfMemory",
224 _ => "Other",
225 }
226 }
227
228 fn error_kind_from_str(s: &str) -> std::io::ErrorKind {
231 match s {
232 "NotFound" => std::io::ErrorKind::NotFound,
233 "PermissionDenied" => std::io::ErrorKind::PermissionDenied,
234 "ConnectionRefused" => std::io::ErrorKind::ConnectionRefused,
235 "ConnectionReset" => std::io::ErrorKind::ConnectionReset,
236 "ConnectionAborted" => std::io::ErrorKind::ConnectionAborted,
237 "NotConnected" => std::io::ErrorKind::NotConnected,
238 "AddrInUse" => std::io::ErrorKind::AddrInUse,
239 "AddrNotAvailable" => std::io::ErrorKind::AddrNotAvailable,
240 "BrokenPipe" => std::io::ErrorKind::BrokenPipe,
241 "AlreadyExists" => std::io::ErrorKind::AlreadyExists,
242 "WouldBlock" => std::io::ErrorKind::WouldBlock,
243 "InvalidInput" => std::io::ErrorKind::InvalidInput,
244 "InvalidData" => std::io::ErrorKind::InvalidData,
245 "TimedOut" => std::io::ErrorKind::TimedOut,
246 "WriteZero" => std::io::ErrorKind::WriteZero,
247 "Interrupted" => std::io::ErrorKind::Interrupted,
248 "Unsupported" => std::io::ErrorKind::Unsupported,
249 "UnexpectedEof" => std::io::ErrorKind::UnexpectedEof,
250 "OutOfMemory" => std::io::ErrorKind::OutOfMemory,
251 _ => std::io::ErrorKind::Other,
252 }
253 }
254
255 #[derive(Serialize, Deserialize)]
257 struct IoFields {
258 kind: String,
259 message: String,
260 }
261
262 #[derive(Serialize, Deserialize)]
267 #[serde(tag = "type", content = "data")]
268 enum ErrorRepr {
269 Io(IoFields),
270 Auth {
271 message: String,
272 response: SmtpResponse,
273 },
274 Permanent {
275 code: u16,
276 message: String,
277 response: SmtpResponse,
278 },
279 Transient {
280 code: u16,
281 message: String,
282 response: SmtpResponse,
283 },
284 Protocol {
285 message: String,
286 },
287 Parse {
288 message: String,
289 },
290 Timeout,
291 Closed,
292 StartTlsUnavailable,
293 AllRecipientsFailed {
294 count: usize,
295 responses: Vec<SmtpResponse>,
296 },
297 SmtpUtf8Required,
298 }
299
300 impl From<&Error> for ErrorRepr {
301 fn from(err: &Error) -> Self {
302 match err {
303 Error::Io(e) => Self::Io(IoFields {
304 kind: error_kind_to_str(e.kind()).to_owned(),
305 message: e.to_string(),
306 }),
307 Error::Auth { message, response } => Self::Auth {
308 message: message.clone(),
309 response: response.clone(),
310 },
311 Error::Permanent {
312 code,
313 message,
314 response,
315 } => Self::Permanent {
316 code: *code,
317 message: message.clone(),
318 response: response.clone(),
319 },
320 Error::Transient {
321 code,
322 message,
323 response,
324 } => Self::Transient {
325 code: *code,
326 message: message.clone(),
327 response: response.clone(),
328 },
329 Error::Protocol(msg) => Self::Protocol {
330 message: msg.clone(),
331 },
332 Error::Parse(msg) => Self::Parse {
333 message: msg.clone(),
334 },
335 Error::Timeout => Self::Timeout,
336 Error::Closed => Self::Closed,
337 Error::StartTlsUnavailable => Self::StartTlsUnavailable,
338 Error::AllRecipientsFailed { count, responses } => Self::AllRecipientsFailed {
339 count: *count,
340 responses: responses.clone(),
341 },
342 Error::SmtpUtf8Required => Self::SmtpUtf8Required,
343 }
344 }
345 }
346
347 impl From<ErrorRepr> for Error {
348 fn from(repr: ErrorRepr) -> Self {
349 match repr {
350 ErrorRepr::Io(fields) => {
351 let kind = error_kind_from_str(&fields.kind);
352 Self::Io(Arc::new(std::io::Error::new(kind, fields.message)))
353 }
354 ErrorRepr::Auth { message, response } => Self::Auth { message, response },
355 ErrorRepr::Permanent {
356 code,
357 message,
358 response,
359 } => Self::Permanent {
360 code,
361 message,
362 response,
363 },
364 ErrorRepr::Transient {
365 code,
366 message,
367 response,
368 } => Self::Transient {
369 code,
370 message,
371 response,
372 },
373 ErrorRepr::Protocol { message } => Self::Protocol(message),
374 ErrorRepr::Parse { message } => Self::Parse(message),
375 ErrorRepr::Timeout => Self::Timeout,
376 ErrorRepr::Closed => Self::Closed,
377 ErrorRepr::StartTlsUnavailable => Self::StartTlsUnavailable,
378 ErrorRepr::AllRecipientsFailed { count, responses } => {
379 Self::AllRecipientsFailed { count, responses }
380 }
381 ErrorRepr::SmtpUtf8Required => Self::SmtpUtf8Required,
382 }
383 }
384 }
385
386 impl Serialize for Error {
387 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
388 ErrorRepr::from(self).serialize(serializer)
389 }
390 }
391
392 impl<'de> Deserialize<'de> for Error {
393 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
394 ErrorRepr::deserialize(deserializer).map(Self::from)
395 }
396 }
397}
398
399#[cfg(test)]
400#[path = "error_tests.rs"]
401mod tests;