1 //! Error and result type for SMTP clients
2
3 use crate::{
4 transport::smtp::response::{Code, Severity},
5 BoxError,
6 };
7 use std::{error::Error as StdError, fmt};
8
9 // Inspired by https://github.com/seanmonstar/reqwest/blob/a8566383168c0ef06c21f38cbc9213af6ff6db31/src/error.rs
10
11 /// The Errors that may occur when sending an email over SMTP
12 pub struct Error {
13 inner: Box<Inner>,
14 }
15
16 struct Inner {
17 kind: Kind,
18 source: Option<BoxError>,
19 }
20
21 impl Error {
new<E>(kind: Kind, source: Option<E>) -> Error where E: Into<BoxError>,22 pub(crate) fn new<E>(kind: Kind, source: Option<E>) -> Error
23 where
24 E: Into<BoxError>,
25 {
26 Error {
27 inner: Box::new(Inner {
28 kind,
29 source: source.map(Into::into),
30 }),
31 }
32 }
33
34 /// Returns true if the error is from response
is_response(&self) -> bool35 pub fn is_response(&self) -> bool {
36 matches!(self.inner.kind, Kind::Response)
37 }
38
39 /// Returns true if the error is from client
is_client(&self) -> bool40 pub fn is_client(&self) -> bool {
41 matches!(self.inner.kind, Kind::Client)
42 }
43
44 /// Returns true if the error is a transient SMTP error
is_transient(&self) -> bool45 pub fn is_transient(&self) -> bool {
46 matches!(self.inner.kind, Kind::Transient(_))
47 }
48
49 /// Returns true if the error is a permanent SMTP error
is_permanent(&self) -> bool50 pub fn is_permanent(&self) -> bool {
51 matches!(self.inner.kind, Kind::Permanent(_))
52 }
53
54 /// Returns true if the error is caused by a timeout
is_timeout(&self) -> bool55 pub fn is_timeout(&self) -> bool {
56 let mut source = self.source();
57
58 while let Some(err) = source {
59 if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
60 return io_err.kind() == std::io::ErrorKind::TimedOut;
61 }
62
63 source = err.source();
64 }
65
66 false
67 }
68
69 /// Returns true if the error is from TLS
70 #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
71 #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
is_tls(&self) -> bool72 pub fn is_tls(&self) -> bool {
73 matches!(self.inner.kind, Kind::Tls)
74 }
75
76 /// Returns the status code, if the error was generated from a response.
status(&self) -> Option<Code>77 pub fn status(&self) -> Option<Code> {
78 match self.inner.kind {
79 Kind::Transient(code) | Kind::Permanent(code) => Some(code),
80 _ => None,
81 }
82 }
83 }
84
85 #[derive(Debug)]
86 pub(crate) enum Kind {
87 /// Transient SMTP error, 4xx reply code
88 ///
89 /// [RFC 5321, section 4.2.1](https://tools.ietf.org/html/rfc5321#section-4.2.1)
90 Transient(Code),
91 /// Permanent SMTP error, 5xx reply code
92 ///
93 /// [RFC 5321, section 4.2.1](https://tools.ietf.org/html/rfc5321#section-4.2.1)
94 Permanent(Code),
95 /// Error parsing a response
96 Response,
97 /// Internal client error
98 Client,
99 /// Connection error
100 Connection,
101 /// Underlying network i/o error
102 Network,
103 /// TLS error
104 #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
105 #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
106 Tls,
107 }
108
109 impl fmt::Debug for Error {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 let mut builder = f.debug_struct("lettre::transport::smtp::Error");
112
113 builder.field("kind", &self.inner.kind);
114
115 if let Some(ref source) = self.inner.source {
116 builder.field("source", source);
117 }
118
119 builder.finish()
120 }
121 }
122
123 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 match self.inner.kind {
126 Kind::Response => f.write_str("response error")?,
127 Kind::Client => f.write_str("internal client error")?,
128 Kind::Network => f.write_str("network error")?,
129 Kind::Connection => f.write_str("Connection error")?,
130 #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
131 Kind::Tls => f.write_str("tls error")?,
132 Kind::Transient(ref code) => {
133 write!(f, "transient error ({})", code)?;
134 }
135 Kind::Permanent(ref code) => {
136 write!(f, "permanent error ({})", code)?;
137 }
138 };
139
140 if let Some(ref e) = self.inner.source {
141 write!(f, ": {}", e)?;
142 }
143
144 Ok(())
145 }
146 }
147
148 impl StdError for Error {
source(&self) -> Option<&(dyn StdError + 'static)>149 fn source(&self) -> Option<&(dyn StdError + 'static)> {
150 self.inner.source.as_ref().map(|e| {
151 let r: &(dyn std::error::Error + 'static) = &**e;
152 r
153 })
154 }
155 }
156
code(c: Code) -> Error157 pub(crate) fn code(c: Code) -> Error {
158 match c.severity {
159 Severity::TransientNegativeCompletion => Error::new::<Error>(Kind::Transient(c), None),
160 Severity::PermanentNegativeCompletion => Error::new::<Error>(Kind::Permanent(c), None),
161 _ => client("Unknown error code"),
162 }
163 }
164
response<E: Into<BoxError>>(e: E) -> Error165 pub(crate) fn response<E: Into<BoxError>>(e: E) -> Error {
166 Error::new(Kind::Response, Some(e))
167 }
168
client<E: Into<BoxError>>(e: E) -> Error169 pub(crate) fn client<E: Into<BoxError>>(e: E) -> Error {
170 Error::new(Kind::Client, Some(e))
171 }
172
network<E: Into<BoxError>>(e: E) -> Error173 pub(crate) fn network<E: Into<BoxError>>(e: E) -> Error {
174 Error::new(Kind::Network, Some(e))
175 }
176
connection<E: Into<BoxError>>(e: E) -> Error177 pub(crate) fn connection<E: Into<BoxError>>(e: E) -> Error {
178 Error::new(Kind::Connection, Some(e))
179 }
180
181 #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
tls<E: Into<BoxError>>(e: E) -> Error182 pub(crate) fn tls<E: Into<BoxError>>(e: E) -> Error {
183 Error::new(Kind::Tls, Some(e))
184 }
185