async_smtp/
error.rs

1//! Error and result type for SMTP clients
2
3use self::Error::*;
4use crate::response::{Response, Severity};
5use base64::DecodeError;
6use std::io;
7use std::net::AddrParseError;
8use std::string::FromUtf8Error;
9
10/// An enum of all error kinds.
11#[derive(thiserror::Error, Debug)]
12pub enum Error {
13    /// Transient SMTP error, 4xx reply code
14    ///
15    /// [RFC 5321, section 4.2.1](https://tools.ietf.org/html/rfc5321#section-4.2.1)
16    #[error("transient: {}", if .0.message.is_empty() { "undetailed error during SMTP transaction".to_string() } else { .0.message.join("; ") })]
17    Transient(Response),
18    /// Permanent SMTP error, 5xx reply code
19    ///
20    /// [RFC 5321, section 4.2.1](https://tools.ietf.org/html/rfc5321#section-4.2.1)
21    #[error("permanent: {}", if .0.message.is_empty() { "undetailed error during SMTP transaction".to_string() } else { .0.message.join("; ") })]
22    Permanent(Response),
23    /// Error parsing a response
24    #[error("{0}")]
25    ResponseParsing(&'static str),
26    /// Error parsing a base64 string in response
27    #[error("challenge parsing: {0}")]
28    ChallengeParsing(#[from] DecodeError),
29    /// Error parsing UTF8in response
30    #[error("utf8: {0}")]
31    Utf8Parsing(#[from] FromUtf8Error),
32    /// Internal client error
33    #[error("client: {0}")]
34    Client(&'static str),
35    /// DNS resolution error
36    #[error("could not resolve hostname")]
37    Resolution,
38    /// IO error
39    #[error("io: {0}")]
40    Io(#[from] io::Error),
41    /// Parsing error
42    #[error("parsing: {0:?}")]
43    Parsing(nom::error::ErrorKind),
44    #[cfg(feature = "runtime-tokio")]
45    /// Timeout error
46    #[error("timeout: {0}")]
47    Timeout(#[from] tokio::time::error::Elapsed),
48    #[cfg(feature = "runtime-async-std")]
49    /// Timeout error
50    #[error("timeout: {0}")]
51    Timeout(#[from] async_std::future::TimeoutError),
52    /// Failure to parse email address.
53    #[error("address parse error: {0}")]
54    AddrParseError(#[from] AddrParseError),
55}
56
57impl From<nom::Err<nom::error::Error<&str>>> for Error {
58    fn from(err: nom::Err<nom::error::Error<&str>>) -> Error {
59        Parsing(match err {
60            nom::Err::Incomplete(_) => nom::error::ErrorKind::Complete,
61            nom::Err::Failure(e) => e.code,
62            nom::Err::Error(e) => e.code,
63        })
64    }
65}
66
67impl From<Response> for Error {
68    fn from(response: Response) -> Error {
69        match response.code.severity {
70            Severity::TransientNegativeCompletion => Transient(response),
71            Severity::PermanentNegativeCompletion => Permanent(response),
72            _ => Client("Unknown error code"),
73        }
74    }
75}
76
77impl From<&'static str> for Error {
78    fn from(string: &'static str) -> Error {
79        Client(string)
80    }
81}
82
83/// SMTP result type
84pub type SmtpResult = Result<Response, Error>;
85
86#[cfg(test)]
87mod test {
88    use super::*;
89    use crate::response::{Category, Code, Detail, Response, Severity};
90
91    #[test]
92    fn test_error_response_to_string() {
93        let err = Error::Permanent(Response::new(
94            Code::new(
95                Severity::PermanentNegativeCompletion,
96                Category::Information,
97                Detail::Zero,
98            ),
99            vec![
100                "gmx.net (mxgmx117) Nemesis ESMTP Service not available".to_string(),
101                "No SMTP service".to_string(),
102                "IP address is block listed.".to_string(),
103                "For explanation visit https://www.gmx.net/mail/senderguidelines?c=bl".to_string(),
104            ],
105        ));
106        assert_eq!(format!("{}", err), "permanent: gmx.net (mxgmx117) Nemesis ESMTP Service not available; No SMTP service; IP address is block listed.; For explanation visit https://www.gmx.net/mail/senderguidelines?c=bl".to_string());
107    }
108}