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