lettre/transport/smtp/
error.rs

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