Add tests for Request; Fix some errors revealed in testing; Fix some

clippy lints;
This commit is contained in:
Nathan Fisher 2023-05-22 15:45:34 -04:00
parent f87299ad34
commit c68b539af5
5 changed files with 134 additions and 50 deletions

View file

@ -38,7 +38,7 @@ impl FromStr for Host {
type Err = ParseHostError; type Err = ParseHostError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.contains(|x: char| x.is_whitespace()) { if s.contains(char::is_whitespace) {
return Err(ParseHostError::IllegalWhitespace); return Err(ParseHostError::IllegalWhitespace);
} }
if let Some((domain, tld)) = s.rsplit_once('.') { if let Some((domain, tld)) = s.rsplit_once('.') {
@ -55,7 +55,7 @@ impl FromStr for Host {
Ok(Host { Ok(Host {
subdomain: Some(subdomain.to_string()), subdomain: Some(subdomain.to_string()),
domain: domain.to_string(), domain: domain.to_string(),
tld: tld.to_string() tld: tld.to_string(),
}) })
} }
} else { } else {
@ -85,18 +85,12 @@ mod tests {
#[test] #[test]
fn parse_empty_tld() { fn parse_empty_tld() {
assert_eq!( assert_eq!("example.".parse::<Host>(), Err(ParseHostError::EmptyTld));
"example.".parse::<Host>(),
Err(ParseHostError::EmptyTld)
);
} }
#[test] #[test]
fn parse_empty_domain() { fn parse_empty_domain() {
assert_eq!( assert_eq!(".com".parse::<Host>(), Err(ParseHostError::EmptyDomain));
".com".parse::<Host>(),
Err(ParseHostError::EmptyDomain)
);
assert_eq!( assert_eq!(
"example..com".parse::<Host>(), "example..com".parse::<Host>(),
Err(ParseHostError::EmptyDomain) Err(ParseHostError::EmptyDomain)
@ -127,19 +121,13 @@ mod tests {
#[test] #[test]
fn parse_host_with_subdomain() { fn parse_host_with_subdomain() {
let host: Host = "mail.example.com".parse().unwrap(); let host: Host = "mail.example.com".parse().unwrap();
assert_eq!( assert_eq!(host.subdomain, Some(String::from("mail")));
host.subdomain,
Some(String::from("mail"))
);
} }
#[test] #[test]
fn parse_complex_subdomain() { fn parse_complex_subdomain() {
let host: Host = "mail.sender.example.com".parse().unwrap(); let host: Host = "mail.sender.example.com".parse().unwrap();
assert_eq!( assert_eq!(host.subdomain, Some(String::from("mail.sender")));
host.subdomain,
Some(String::from("mail.sender"))
);
} }
#[test] #[test]
@ -147,7 +135,7 @@ mod tests {
let host = Host { let host = Host {
subdomain: None, subdomain: None,
domain: String::from("example"), domain: String::from("example"),
tld: String::from("com") tld: String::from("com"),
}; };
let host_str = host.to_string(); let host_str = host.to_string();
assert_eq!(host_str.as_str(), "example.com"); assert_eq!(host_str.as_str(), "example.com");
@ -158,7 +146,7 @@ mod tests {
let host = Host { let host = Host {
subdomain: Some(String::from("mail")), subdomain: Some(String::from("mail")),
domain: String::from("example"), domain: String::from("example"),
tld: String::from("com") tld: String::from("com"),
}; };
let host_str = host.to_string(); let host_str = host.to_string();
assert_eq!(host_str.as_str(), "mail.example.com"); assert_eq!(host_str.as_str(), "mail.example.com");

View file

@ -1,5 +1,5 @@
#![warn(clippy::all, clippy::pedantic)]
pub mod host; pub mod host;
pub mod request; pub mod request;
pub mod response; pub mod response;
pub mod status; pub mod status;

View file

@ -10,7 +10,11 @@ pub struct Request {
impl fmt::Display for Request { impl fmt::Display for Request {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "misfin://{}@{} {}\r\n", self.user, self.host, self.message) write!(
f,
"misfin://{}@{} {}\r\n",
self.user, self.host, self.message
)
} }
} }
@ -28,7 +32,7 @@ impl fmt::Display for ParseRequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::ParseHostError(e) => write!(f, "{e}"), Self::ParseHostError(e) => write!(f, "{e}"),
_ => write!(f, "{self:?}") _ => write!(f, "{self:?}"),
} }
} }
} }
@ -52,23 +56,28 @@ impl FromStr for Request {
type Err = ParseRequestError; type Err = ParseRequestError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.ends_with("\r\n") {
return Err(ParseRequestError::Malformed);
}
if let Some((user, message)) = s.split_once(' ') { if let Some((user, message)) = s.split_once(' ') {
let message = message.trim_end().to_string(); let Some(message) = message.strip_suffix("\r\n").map(ToString::to_string) else {
return Err(ParseRequestError::Malformed);
};
if message.is_empty() { if message.is_empty() {
return Err(ParseRequestError::EmptyMessage); return Err(ParseRequestError::EmptyMessage);
} }
if let Some((user, host)) = user.rsplit_once('@') { if let Some((user, host)) = user.rsplit_once('@') {
if host.is_empty() { if host.is_empty() {
return Err(ParseRequestError::EmptyHost); return Err(ParseRequestError::EmptyHost);
} else if user.is_empty() { } else if user == "misfin://" {
return Err(ParseRequestError::EmptyUser); return Err(ParseRequestError::EmptyUser);
} }
let host = host.parse()?; let host = host.parse()?;
let user = user.to_string(); let Some(user) = user.strip_prefix("misfin://").map(ToString::to_string) else {
Ok(Request { user, host, message }) return Err(ParseRequestError::Malformed);
};
Ok(Request {
user,
host,
message,
})
} else { } else {
Err(ParseRequestError::MissingSeparator) Err(ParseRequestError::MissingSeparator)
} }
@ -77,3 +86,91 @@ impl FromStr for Request {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
const REQ_STR: &'static str = "misfin://john@misfin.example.com Anyone seen Jane?\r\n";
fn req() -> Request {
Request {
user: "john".to_string(),
host: Host {
subdomain: Some("misfin".into()),
domain: "example".into(),
tld: "com".into(),
},
message: "Anyone seen Jane?".to_string(),
}
}
#[test]
fn to_string() {
assert_eq!(req().to_string().as_str(), REQ_STR,);
}
#[test]
fn parse() {
assert_eq!(REQ_STR.parse::<Request>().unwrap(), req());
}
#[test]
fn parse_missing_sep() {
let req = "misfin://john@example.comHelloWorld!\r\n".parse::<Request>();
assert_eq!(
req,
Err(ParseRequestError::MissingSeparator)
);
}
#[test]
fn parse_malformed() {
let req = "misfin://john@example.com Hello World!\n".parse::<Request>();
assert_eq!(
req,
Err(ParseRequestError::Malformed)
);
let req = "mail://john@example.com Hello World!\r\n".parse::<Request>();
assert_eq!(
req,
Err(ParseRequestError::Malformed)
);
}
#[test]
fn parse_empty_user() {
let req = "misfin://@example.com Hello World!\r\n".parse::<Request>();
assert_eq!(
req,
Err(ParseRequestError::EmptyUser)
);
}
#[test]
fn parse_empty_host() {
let req = "misfin://john@ Hello World!\r\n".parse::<Request>();
assert_eq!(
req,
Err(ParseRequestError::EmptyHost)
);
}
#[test]
fn parse_empty_msg() {
let req = "misfin://john@example.com \r\n".parse::<Request>();
assert_eq!(
req,
Err(ParseRequestError::EmptyMessage)
);
}
#[test]
fn parse_host_illegal_whitespace() {
let req = "misfin://john@example\tfairy.com Hello World!\r\n".parse::<Request>();
assert_eq!(
req,
Err(ParseRequestError::ParseHostError(ParseHostError::IllegalWhitespace))
);
}
}

View file

@ -1,6 +1,6 @@
use std::{fmt, str::FromStr, num::ParseIntError}; use std::{fmt, num::ParseIntError, str::FromStr};
use crate::status::{Status, StatusError}; use crate::status::{Status, ParseStatusError};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Response { pub struct Response {
@ -48,8 +48,8 @@ impl From<ParseIntError> for ParseResponseError {
} }
} }
impl From<StatusError> for ParseResponseError { impl From<ParseStatusError> for ParseResponseError {
fn from(_value: StatusError) -> Self { fn from(_value: ParseStatusError) -> Self {
Self::StatusError Self::StatusError
} }
} }
@ -97,9 +97,8 @@ mod tests {
fn parse_badint() { fn parse_badint() {
let response = "twenty message deliverred\r\n".parse::<Response>(); let response = "twenty message deliverred\r\n".parse::<Response>();
match response { match response {
Err(ParseResponseError::ParseInt(_)) => {}, Err(ParseResponseError::ParseInt(_)) => {}
_ => panic!(), _ => panic!(),
} }
} }
} }

View file

@ -1,15 +1,15 @@
use std::fmt; use std::fmt;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct StatusError; pub struct ParseStatusError;
impl fmt::Display for StatusError { impl fmt::Display for ParseStatusError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}") write!(f, "{self:?}")
} }
} }
impl std::error::Error for StatusError {} impl std::error::Error for ParseStatusError {}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[repr(u8)] #[repr(u8)]
@ -46,7 +46,7 @@ impl From<Status> for u8 {
} }
impl TryFrom<u8> for Status { impl TryFrom<u8> for Status {
type Error = StatusError; type Error = ParseStatusError;
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
match value / 10 { match value / 10 {
@ -56,7 +56,7 @@ impl TryFrom<u8> for Status {
4 => Ok(Self::TemporaryFailure((value % 40).try_into()?)), 4 => Ok(Self::TemporaryFailure((value % 40).try_into()?)),
5 => Ok(Self::PermanentFailure((value % 50).try_into()?)), 5 => Ok(Self::PermanentFailure((value % 50).try_into()?)),
6 => Ok(Self::AuthenticationFailure((value % 60).try_into()?)), 6 => Ok(Self::AuthenticationFailure((value % 60).try_into()?)),
_ => Err(StatusError), _ => Err(ParseStatusError),
} }
} }
} }
@ -73,14 +73,14 @@ pub enum Redirect {
} }
impl TryFrom<u8> for Redirect { impl TryFrom<u8> for Redirect {
type Error = StatusError; type Error = ParseStatusError;
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
match value { match value {
0 => Ok(Self::Temporary), 0 => Ok(Self::Temporary),
1 => Ok(Self::Permanent), 1 => Ok(Self::Permanent),
n if n < 10 => Ok(Self::Other), n if n < 10 => Ok(Self::Other),
_ => Err(StatusError), _ => Err(ParseStatusError),
} }
} }
} }
@ -105,7 +105,7 @@ pub enum TemporaryFailure {
} }
impl TryFrom<u8> for TemporaryFailure { impl TryFrom<u8> for TemporaryFailure {
type Error = StatusError; type Error = ParseStatusError;
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
match value { match value {
@ -116,7 +116,7 @@ impl TryFrom<u8> for TemporaryFailure {
4 => Ok(Self::RateLimit), 4 => Ok(Self::RateLimit),
5 => Ok(Self::MailboxFull), 5 => Ok(Self::MailboxFull),
n if n < 10 => Ok(Self::Other), n if n < 10 => Ok(Self::Other),
_ => Err(StatusError), _ => Err(ParseStatusError),
} }
} }
} }
@ -132,7 +132,7 @@ pub enum PermanentFailure {
} }
impl TryFrom<u8> for PermanentFailure { impl TryFrom<u8> for PermanentFailure {
type Error = StatusError; type Error = ParseStatusError;
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
match value { match value {
@ -142,7 +142,7 @@ impl TryFrom<u8> for PermanentFailure {
3 => Ok(Self::DomainNotServiced), 3 => Ok(Self::DomainNotServiced),
9 => Ok(Self::BadRequest), 9 => Ok(Self::BadRequest),
n if n < 10 => Ok(Self::Other), n if n < 10 => Ok(Self::Other),
_ => Err(StatusError), _ => Err(ParseStatusError),
} }
} }
} }
@ -158,7 +158,7 @@ pub enum AuthenticationFailure {
} }
impl TryFrom<u8> for AuthenticationFailure { impl TryFrom<u8> for AuthenticationFailure {
type Error = StatusError; type Error = ParseStatusError;
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
match value { match value {
@ -168,7 +168,7 @@ impl TryFrom<u8> for AuthenticationFailure {
3 => Ok(Self::IdentityMismatch), 3 => Ok(Self::IdentityMismatch),
4 => Ok(Self::ProofRequired), 4 => Ok(Self::ProofRequired),
n if n < 10 => Ok(Self::Other), n if n < 10 => Ok(Self::Other),
_ => Err(StatusError), _ => Err(ParseStatusError),
} }
} }
} }