chore(transport): Use nom 5.0 with functions (#360)

This commit is contained in:
Alexis Mousset
2019-09-18 23:12:36 +00:00
committed by GitHub
parent bf2adcabed
commit 5bc1cba2eb
5 changed files with 98 additions and 99 deletions

View File

@@ -21,7 +21,7 @@ is-it-maintained-open-issues = { repository = "lettre/lettre" }
[dependencies]
log = "^0.4"
nom = { version = "^4.0", optional = true }
nom = { version = "^5.0", optional = true }
bufstream = { version = "^0.1", optional = true }
native-tls = { version = "^0.2", optional = true }
base64 = { version = "^0.10", optional = true }

View File

@@ -7,7 +7,6 @@ use crate::smtp::error::{Error, SmtpResult};
use crate::smtp::response::Response;
use bufstream::BufStream;
use log::debug;
use nom::ErrorKind as NomErrorKind;
use std::fmt::{Debug, Display};
use std::io::{self, BufRead, BufReader, Read, Write};
use std::net::ToSocketAddrs;
@@ -250,7 +249,9 @@ impl<S: Connector + Write + Read + Timeout + Debug> InnerClient<S> {
let mut response = raw_response.parse::<Response>();
while response.is_err() {
if response.as_ref().err().unwrap() != &NomErrorKind::Complete {
if let Error::Parsing(nom::error::ErrorKind::Complete) =
response.as_ref().err().unwrap()
{
break;
}
// TODO read more than one line

View File

@@ -37,7 +37,7 @@ pub enum Error {
/// TLS error
Tls(native_tls::Error),
/// Parsing error
Parsing(nom::ErrorKind),
Parsing(nom::error::ErrorKind),
}
impl Display for Error {
@@ -94,9 +94,13 @@ impl From<native_tls::Error> for Error {
}
}
impl From<nom::ErrorKind> for Error {
fn from(err: nom::ErrorKind) -> Error {
Parsing(err)
impl From<nom::Err<(&str, nom::error::ErrorKind)>> for Error {
fn from(err: nom::Err<(&str, nom::error::ErrorKind)>) -> Error {
Parsing(match err {
nom::Err::Incomplete(_) => nom::error::ErrorKind::Complete,
nom::Err::Failure((_, k)) => k,
nom::Err::Error((_, k)) => k,
})
}
}

View File

@@ -1,11 +1,18 @@
//! SMTP response, containing a mandatory return code and an optional text
//! message
use nom::*;
use nom::{crlf, ErrorKind as NomErrorKind};
use crate::smtp::Error;
use nom::{
branch::alt,
bytes::complete::{tag, take_until},
combinator::{complete, map},
multi::many0,
sequence::{preceded, tuple},
IResult,
};
use std::fmt::{Display, Formatter, Result};
use std::result;
use std::str::{from_utf8, FromStr};
use std::str::FromStr;
use std::string::ToString;
/// First digit indicates severity
@@ -142,13 +149,10 @@ pub struct Response {
}
impl FromStr for Response {
type Err = NomErrorKind;
type Err = Error;
fn from_str(s: &str) -> result::Result<Response, NomErrorKind> {
match parse_response(s.as_bytes()) {
Ok((_, res)) => Ok(res),
Err(e) => Err(e.into_error_kind()),
}
fn from_str(s: &str) -> result::Result<Response, Error> {
parse_response(s).map(|(_, r)| r).map_err(|e| e.into())
}
}
@@ -186,97 +190,88 @@ impl Response {
// Parsers (originally from tokio-smtp)
named!(
parse_code<Code>,
map!(
tuple!(parse_severity, parse_category, parse_detail),
|(severity, category, detail)| Code {
fn parse_code(i: &str) -> IResult<&str, Code> {
let (i, severity) = parse_severity(i)?;
let (i, category) = parse_category(i)?;
let (i, detail) = parse_detail(i)?;
Ok((
i,
Code {
severity,
category,
detail,
}
)
);
},
))
}
named!(
parse_severity<Severity>,
alt!(
tag!("2") => { |_| Severity::PositiveCompletion } |
tag!("3") => { |_| Severity::PositiveIntermediate } |
tag!("4") => { |_| Severity::TransientNegativeCompletion } |
tag!("5") => { |_| Severity::PermanentNegativeCompletion }
)
);
fn parse_severity(i: &str) -> IResult<&str, Severity> {
alt((
map(tag("2"), |_| Severity::PositiveCompletion),
map(tag("3"), |_| Severity::PositiveIntermediate),
map(tag("4"), |_| Severity::TransientNegativeCompletion),
map(tag("5"), |_| Severity::PermanentNegativeCompletion),
))(i)
}
named!(
parse_category<Category>,
alt!(
tag!("0") => { |_| Category::Syntax } |
tag!("1") => { |_| Category::Information } |
tag!("2") => { |_| Category::Connections } |
tag!("3") => { |_| Category::Unspecified3 } |
tag!("4") => { |_| Category::Unspecified4 } |
tag!("5") => { |_| Category::MailSystem }
)
);
fn parse_category(i: &str) -> IResult<&str, Category> {
alt((
map(tag("0"), |_| Category::Syntax),
map(tag("1"), |_| Category::Information),
map(tag("2"), |_| Category::Connections),
map(tag("3"), |_| Category::Unspecified3),
map(tag("4"), |_| Category::Unspecified4),
map(tag("5"), |_| Category::MailSystem),
))(i)
}
named!(
parse_detail<Detail>,
alt!(
tag!("0") => { |_| Detail::Zero } |
tag!("1") => { |_| Detail::One } |
tag!("2") => { |_| Detail::Two } |
tag!("3") => { |_| Detail::Three } |
tag!("4") => { |_| Detail::Four} |
tag!("5") => { |_| Detail::Five } |
tag!("6") => { |_| Detail::Six} |
tag!("7") => { |_| Detail::Seven } |
tag!("8") => { |_| Detail::Eight } |
tag!("9") => { |_| Detail::Nine }
)
);
fn parse_detail(i: &str) -> IResult<&str, Detail> {
alt((
map(tag("0"), |_| Detail::Zero),
map(tag("1"), |_| Detail::One),
map(tag("2"), |_| Detail::Two),
map(tag("3"), |_| Detail::Three),
map(tag("4"), |_| Detail::Four),
map(tag("5"), |_| Detail::Five),
map(tag("6"), |_| Detail::Six),
map(tag("7"), |_| Detail::Seven),
map(tag("8"), |_| Detail::Eight),
map(tag("9"), |_| Detail::Nine),
))(i)
}
named!(
parse_response<Response>,
map_res!(
tuple!(
// Parse any number of continuation lines.
many0!(tuple!(
parse_code,
preceded!(char!('-'), take_until_and_consume!(b"\r\n".as_ref()))
)),
// Parse the final line.
tuple!(
parse_code,
terminated!(
opt!(preceded!(char!(' '), take_until!(b"\r\n".as_ref()))),
crlf
)
)
),
|(lines, (last_code, last_line)): (Vec<_>, _)| {
// Check that all codes are equal.
if !lines.iter().all(|&(ref code, _)| *code == last_code) {
return Err(());
}
fn parse_response(i: &str) -> IResult<&str, Response> {
let (i, lines) = many0(tuple((
parse_code,
preceded(tag("-"), take_until("\r\n")),
tag("\r\n"),
)))(i)?;
let (i, (last_code, last_line)) =
tuple((parse_code, preceded(tag(" "), take_until("\r\n"))))(i)?;
let (i, _) = complete(tag("\r\n"))(i)?;
// Extract text from lines, and append last line.
let mut lines = lines.into_iter().map(|(_, text)| text).collect::<Vec<_>>();
if let Some(text) = last_line {
lines.push(text);
}
// Check that all codes are equal.
if !lines.iter().all(|&(ref code, _, _)| *code == last_code) {
return Err(nom::Err::Failure(("", nom::error::ErrorKind::Not)));
}
Ok(Response {
code: last_code,
message: lines
.into_iter()
.map(|line| from_utf8(line).map(ToString::to_string))
.collect::<result::Result<Vec<_>, _>>()
.map_err(|_| ())?,
})
}
)
);
// Extract text from lines, and append last line.
let mut lines: Vec<&str> = lines
.into_iter()
.map(|(_, text, _)| text)
.collect::<Vec<_>>();
lines.push(last_line);
Ok((
i,
Response {
code: last_code,
message: lines
.iter()
.map(ToString::to_string)
.collect::<Vec<String>>(),
},
))
}
#[cfg(test)]
mod test {

View File

@@ -610,7 +610,6 @@ mod test {
assert!(string_res.unwrap().starts_with("Subject: A Subject"));
}
#[test]
fn test_email_sendable() {
let email_builder = EmailBuilder::new();