Improve SMTP response parsing
This commit is contained in:
@@ -15,7 +15,7 @@ use std::io::{BufRead, Read, Write};
|
||||
|
||||
use bufstream::BufStream;
|
||||
|
||||
use response::{Response, Severity, Category};
|
||||
use response::ResponseParser;
|
||||
use error::SmtpResult;
|
||||
use client::net::{Connector, SmtpStream};
|
||||
use client::authentication::{plain, cram_md5};
|
||||
@@ -43,6 +43,12 @@ fn escape_crlf(string: &str) -> String {
|
||||
string.replace(CRLF, "<CR><LF>")
|
||||
}
|
||||
|
||||
/// Returns the string removing all the CRLF
|
||||
#[inline]
|
||||
fn remove_crlf(string: &str) -> String {
|
||||
string.replace(CRLF, "")
|
||||
}
|
||||
|
||||
/// Structure that implements the SMTP client
|
||||
pub struct Client<S: Write + Read = SmtpStream> {
|
||||
/// TCP stream between client and server
|
||||
@@ -194,34 +200,18 @@ impl<S: Connector + Write + Read = SmtpStream> Client<S> {
|
||||
|
||||
/// Gets the SMTP response
|
||||
fn get_reply(&mut self) -> SmtpResult {
|
||||
|
||||
let mut parser = ResponseParser::new();
|
||||
|
||||
let mut line = String::new();
|
||||
try!(self.stream.as_mut().unwrap().read_line(&mut line));
|
||||
|
||||
// If the string is too short to be a response code
|
||||
if line.len() < 3 {
|
||||
return Err(From::from("Could not parse reply code, line too short"));
|
||||
while try!(parser.read_line(remove_crlf(line.as_ref()).as_ref())) {
|
||||
line.clear();
|
||||
try!(self.stream.as_mut().unwrap().read_line(&mut line));
|
||||
}
|
||||
|
||||
let (severity, category, detail) = match (line[0..1].parse::<Severity>(), line[1..2].parse::<Category>(), line[2..3].parse::<u8>()) {
|
||||
(Ok(severity), Ok(category), Ok(detail)) => (severity, category, detail),
|
||||
_ => return Err(From::from("Could not parse reply code")),
|
||||
};
|
||||
|
||||
let mut message = Vec::new();
|
||||
|
||||
// 3 chars for code + space + CRLF
|
||||
while line.len() > 6 {
|
||||
let end = line.len() - 2;
|
||||
message.push(line[4..end].to_string());
|
||||
if line.as_bytes()[3] == '-' as u8 {
|
||||
line.clear();
|
||||
try!(self.stream.as_mut().unwrap().read_line(&mut line));
|
||||
} else {
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
|
||||
let response = Response::new(severity, category, detail, message);
|
||||
let response = try!(parser.response());
|
||||
|
||||
match response.is_positive() {
|
||||
true => Ok(response),
|
||||
@@ -232,7 +222,7 @@ impl<S: Connector + Write + Read = SmtpStream> Client<S> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{escape_dot, escape_crlf};
|
||||
use super::{escape_dot, remove_crlf, escape_crlf};
|
||||
|
||||
#[test]
|
||||
fn test_escape_dot() {
|
||||
@@ -242,6 +232,16 @@ mod test {
|
||||
assert_eq!(escape_dot("test\r\n.\r\ntest"), "test\r\n..\r\ntest");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_crlf() {
|
||||
assert_eq!(remove_crlf("\r\n"), "");
|
||||
assert_eq!(remove_crlf("EHLO my_name\r\n"), "EHLO my_name");
|
||||
assert_eq!(
|
||||
remove_crlf("EHLO my_name\r\nSIZE 42\r\n"),
|
||||
"EHLO my_nameSIZE 42"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_crlf() {
|
||||
assert_eq!(escape_crlf("\r\n"), "<CR><LF>");
|
||||
|
||||
@@ -81,7 +81,7 @@ impl Extension {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Extension;
|
||||
use response::{Severity, Category, Response};
|
||||
use response::{Severity, Category, Response, Code};
|
||||
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
@@ -95,15 +95,19 @@ mod test {
|
||||
#[test]
|
||||
fn test_parse_esmtp_response() {
|
||||
assert_eq!(Extension::parse_esmtp_response(&Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"2".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"2".parse::<Category>().unwrap(),
|
||||
1,
|
||||
),
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
)), vec![Extension::EightBitMime]);
|
||||
assert_eq!(Extension::parse_esmtp_response(&Response::new(
|
||||
"4".parse::<Severity>().unwrap(),
|
||||
"3".parse::<Category>().unwrap(),
|
||||
3,
|
||||
Code::new(
|
||||
"4".parse::<Severity>().unwrap(),
|
||||
"3".parse::<Category>().unwrap(),
|
||||
3,
|
||||
),
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "AUTH PLAIN CRAM-MD5".to_string()]
|
||||
)), vec![Extension::EightBitMime, Extension::PlainAuthentication, Extension::CramMd5Authentication]);
|
||||
}
|
||||
|
||||
360
src/response.rs
360
src/response.rs
@@ -15,6 +15,7 @@ use std::result::Result as RResult;
|
||||
|
||||
use self::Severity::*;
|
||||
use self::Category::*;
|
||||
use error::{SmtpResult, SmtpError};
|
||||
|
||||
/// First digit indicates severity
|
||||
#[derive(PartialEq,Eq,Copy,Clone,Debug)]
|
||||
@@ -102,17 +103,113 @@ impl Display for Category {
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains an SMTP reply, with separed code and message
|
||||
///
|
||||
/// The text message is optional, only the code is mandatory
|
||||
/// Represents a 3 digit SMTP response code
|
||||
#[derive(PartialEq,Eq,Clone,Debug)]
|
||||
pub struct Response {
|
||||
pub struct Code {
|
||||
/// First digit of the response code
|
||||
severity: Severity,
|
||||
/// Second digit of the response code
|
||||
category: Category,
|
||||
/// Third digit
|
||||
detail: u8,
|
||||
}
|
||||
|
||||
impl FromStr for Code {
|
||||
type Err = SmtpError;
|
||||
|
||||
#[inline]
|
||||
fn from_str(s: &str) -> RResult<Code, SmtpError> {
|
||||
if s.len() == 3 {
|
||||
match (s[0..1].parse::<Severity>(), s[1..2].parse::<Category>(), s[2..3].parse::<u8>()) {
|
||||
(Ok(severity), Ok(category), Ok(detail)) => Ok(Code {severity: severity, category: category, detail: detail}),
|
||||
_ => return Err(From::from("Could not parse reply code")),
|
||||
}
|
||||
} else {
|
||||
Err(From::from("Could not parse reply code"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Code {
|
||||
/// Creates a new `Code` structure
|
||||
pub fn new(severity: Severity, category: Category, detail: u8) -> Code {
|
||||
Code {
|
||||
severity: severity,
|
||||
category: category,
|
||||
detail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the reply code
|
||||
pub fn code(&self) -> String {
|
||||
format!("{}{}{}", self.severity, self.category, self.detail)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an SMTP response
|
||||
#[derive(PartialEq,Eq,Clone,Debug)]
|
||||
pub struct ResponseParser {
|
||||
/// Response code
|
||||
code: Option<Code>,
|
||||
/// Server response string (optional)
|
||||
/// Handle multiline responses
|
||||
message: Vec<String>
|
||||
}
|
||||
|
||||
impl ResponseParser {
|
||||
/// Creates a new parser
|
||||
pub fn new() -> ResponseParser {
|
||||
ResponseParser {
|
||||
code: None,
|
||||
message: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a line and return a `bool` indicating if there are more lines to come
|
||||
pub fn read_line(&mut self, line: &str) -> RResult<bool, SmtpError> {
|
||||
|
||||
if line.len() < 3 {
|
||||
return Err(From::from("Could not parse reply code, line too short"));
|
||||
}
|
||||
|
||||
if self.code.is_none() {
|
||||
self.code = Some(try!(line[0..3].parse::<Code>()));
|
||||
} else {
|
||||
if self.code.as_ref().unwrap().code() != line[0..3] {
|
||||
println!("pouet");
|
||||
return Err(From::from("Could not parse reply code"));
|
||||
}
|
||||
}
|
||||
|
||||
if line.len() > 4 {
|
||||
self.message.push(line[4..].to_string());
|
||||
if line.as_bytes()[3] == '-' as u8 {
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a response from a `ResponseParser`
|
||||
pub fn response(self) -> SmtpResult {
|
||||
if self.code.is_some() {
|
||||
Ok(Response::new(self.code.unwrap(), self.message))
|
||||
} else {
|
||||
Err(From::from("Could not parse reply code"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains an SMTP reply, with separed code and message
|
||||
///
|
||||
/// The text message is optional, only the code is mandatory
|
||||
#[derive(PartialEq,Eq,Clone,Debug)]
|
||||
pub struct Response {
|
||||
/// Response code
|
||||
code: Code,
|
||||
/// Server response string (optional)
|
||||
/// Handle multiline responses
|
||||
message: Vec<String>
|
||||
@@ -120,18 +217,16 @@ pub struct Response {
|
||||
|
||||
impl Response {
|
||||
/// Creates a new `Response`
|
||||
pub fn new(severity: Severity, category: Category, detail: u8, message: Vec<String>) -> Response {
|
||||
pub fn new(code: Code, message: Vec<String>) -> Response {
|
||||
Response {
|
||||
severity: severity,
|
||||
category: category,
|
||||
detail: detail,
|
||||
message: message
|
||||
code: code,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tells if the response is positive
|
||||
pub fn is_positive(&self) -> bool {
|
||||
match self.severity {
|
||||
match self.code.severity {
|
||||
PositiveCompletion => true,
|
||||
PositiveIntermediate => true,
|
||||
_ => false,
|
||||
@@ -143,24 +238,30 @@ impl Response {
|
||||
self.message.clone()
|
||||
}
|
||||
|
||||
/// Gets the first line beginning with the given string
|
||||
/// TODO testing
|
||||
pub fn get_line_beginning_with(&self, start: &str) -> Option<String> {
|
||||
self.message.iter().find(|&x| (*x).starts_with(start)).map(|s| s.to_string())
|
||||
}
|
||||
|
||||
/// Returns the severity (i.e. 1st digit)
|
||||
pub fn severity(&self) -> Severity {
|
||||
self.severity
|
||||
self.code.severity
|
||||
}
|
||||
|
||||
/// Returns the category (i.e. 2nd digit)
|
||||
pub fn category(&self) -> Category {
|
||||
self.category
|
||||
self.code.category
|
||||
}
|
||||
|
||||
/// Returns the detail (i.e. 3rd digit)
|
||||
pub fn detail(&self) -> u8 {
|
||||
self.detail
|
||||
self.code.detail
|
||||
}
|
||||
|
||||
/// Returns the reply code
|
||||
fn code(&self) -> String {
|
||||
format!("{}{}{}", self.severity, self.category, self.detail)
|
||||
self.code.code()
|
||||
}
|
||||
|
||||
/// Tests code equality
|
||||
@@ -182,7 +283,7 @@ impl Response {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Severity, Category, Response};
|
||||
use super::{Severity, Category, Response, ResponseParser, Code};
|
||||
|
||||
#[test]
|
||||
fn test_severity_from_str() {
|
||||
@@ -208,44 +309,117 @@ mod test {
|
||||
assert_eq!(format!("{}", Category::Unspecified4), "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_new() {
|
||||
assert_eq!(
|
||||
Code::new(Severity::TransientNegativeCompletion, Category::Connections, 0),
|
||||
Code {
|
||||
severity: Severity::TransientNegativeCompletion,
|
||||
category: Category::Connections,
|
||||
detail: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_from_str() {
|
||||
assert_eq!(
|
||||
"421".parse::<Code>().unwrap(),
|
||||
Code {
|
||||
severity: Severity::TransientNegativeCompletion,
|
||||
category: Category::Connections,
|
||||
detail: 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_code() {
|
||||
let code = Code {
|
||||
severity: Severity::TransientNegativeCompletion,
|
||||
category: Category::Connections,
|
||||
detail: 1,
|
||||
};
|
||||
|
||||
assert_eq!(code.code(), "421");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_new() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail: 1,
|
||||
},
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
), Response {
|
||||
severity: Severity::PositiveCompletion,
|
||||
category: Category::Unspecified4,
|
||||
detail: 1,
|
||||
code: Code {
|
||||
severity: Severity::PositiveCompletion,
|
||||
category: Category::Unspecified4,
|
||||
detail: 1,
|
||||
},
|
||||
message: vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()],
|
||||
});
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec![]
|
||||
), Response {
|
||||
severity: Severity::PositiveCompletion,
|
||||
category: Category::Unspecified4,
|
||||
detail: 1,
|
||||
code: Code {
|
||||
severity: Severity::PositiveCompletion,
|
||||
category: Category::Unspecified4,
|
||||
detail: 1,
|
||||
},
|
||||
message: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_parser() {
|
||||
let mut parser = ResponseParser::new();
|
||||
|
||||
assert!(parser.read_line("250-me").unwrap());
|
||||
assert!(parser.read_line("250-8BITMIME").unwrap());
|
||||
assert!(parser.read_line("250-SIZE 42").unwrap());
|
||||
assert!(!parser.read_line("250 AUTH PLAIN CRAM-MD5").unwrap());
|
||||
|
||||
let response = parser.response().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
response,
|
||||
Response {
|
||||
code: Code {
|
||||
severity: Severity::PositiveCompletion,
|
||||
category: Category::MailSystem,
|
||||
detail: 0,
|
||||
},
|
||||
message: vec!["me".to_string(), "8BITMIME".to_string(),
|
||||
"SIZE 42".to_string(), "AUTH PLAIN CRAM-MD5".to_string()],
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_is_positive() {
|
||||
assert!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).is_positive());
|
||||
assert!(! Response::new(
|
||||
"4".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "5".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).is_positive());
|
||||
}
|
||||
@@ -253,16 +427,20 @@ mod test {
|
||||
#[test]
|
||||
fn test_response_message() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).message(), vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]);
|
||||
let empty_message: Vec<String> = vec![];
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec![]
|
||||
).message(), empty_message);
|
||||
}
|
||||
@@ -270,15 +448,19 @@ mod test {
|
||||
#[test]
|
||||
fn test_response_severity() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).severity(), Severity::PositiveCompletion);
|
||||
assert_eq!(Response::new(
|
||||
"5".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "5".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail: 1,
|
||||
},
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).severity(), Severity::PermanentNegativeCompletion);
|
||||
}
|
||||
@@ -286,9 +468,11 @@ mod test {
|
||||
#[test]
|
||||
fn test_response_category() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).category(), Category::Unspecified4);
|
||||
}
|
||||
@@ -296,9 +480,11 @@ mod test {
|
||||
#[test]
|
||||
fn test_response_detail() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).detail(), 1);
|
||||
}
|
||||
@@ -306,9 +492,11 @@ mod test {
|
||||
#[test]
|
||||
fn test_response_code() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).code(), "241");
|
||||
}
|
||||
@@ -316,15 +504,19 @@ mod test {
|
||||
#[test]
|
||||
fn test_response_has_code() {
|
||||
assert!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).has_code(241));
|
||||
assert!(! Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).has_code(251));
|
||||
}
|
||||
@@ -332,39 +524,51 @@ mod test {
|
||||
#[test]
|
||||
fn test_response_first_word() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).first_word(), Some("me".to_string()));
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec!["me mo".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).first_word(), Some("me".to_string()));
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec![]
|
||||
).first_word(), None);
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec![" ".to_string()]
|
||||
).first_word(), None);
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec![" ".to_string()]
|
||||
).first_word(), None);
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
Code {
|
||||
severity: "2".parse::<Severity>().unwrap(),
|
||||
category: "4".parse::<Category>().unwrap(),
|
||||
detail:1,
|
||||
},
|
||||
vec!["".to_string()]
|
||||
).first_word(), None);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user