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;
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);
}
if let Some((domain, tld)) = s.rsplit_once('.') {
@ -55,7 +55,7 @@ impl FromStr for Host {
Ok(Host {
subdomain: Some(subdomain.to_string()),
domain: domain.to_string(),
tld: tld.to_string()
tld: tld.to_string(),
})
}
} else {
@ -85,18 +85,12 @@ mod tests {
#[test]
fn parse_empty_tld() {
assert_eq!(
"example.".parse::<Host>(),
Err(ParseHostError::EmptyTld)
);
assert_eq!("example.".parse::<Host>(), Err(ParseHostError::EmptyTld));
}
#[test]
fn parse_empty_domain() {
assert_eq!(
".com".parse::<Host>(),
Err(ParseHostError::EmptyDomain)
);
assert_eq!(".com".parse::<Host>(), Err(ParseHostError::EmptyDomain));
assert_eq!(
"example..com".parse::<Host>(),
Err(ParseHostError::EmptyDomain)
@ -127,19 +121,13 @@ mod tests {
#[test]
fn parse_host_with_subdomain() {
let host: Host = "mail.example.com".parse().unwrap();
assert_eq!(
host.subdomain,
Some(String::from("mail"))
);
assert_eq!(host.subdomain, Some(String::from("mail")));
}
#[test]
fn parse_complex_subdomain() {
let host: Host = "mail.sender.example.com".parse().unwrap();
assert_eq!(
host.subdomain,
Some(String::from("mail.sender"))
);
assert_eq!(host.subdomain, Some(String::from("mail.sender")));
}
#[test]
@ -147,7 +135,7 @@ mod tests {
let host = Host {
subdomain: None,
domain: String::from("example"),
tld: String::from("com")
tld: String::from("com"),
};
let host_str = host.to_string();
assert_eq!(host_str.as_str(), "example.com");
@ -158,7 +146,7 @@ mod tests {
let host = Host {
subdomain: Some(String::from("mail")),
domain: String::from("example"),
tld: String::from("com")
tld: String::from("com"),
};
let host_str = host.to_string();
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 request;
pub mod response;
pub mod status;

View file

@ -10,7 +10,11 @@ pub struct Request {
impl fmt::Display for Request {
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 {
match self {
Self::ParseHostError(e) => write!(f, "{e}"),
_ => write!(f, "{self:?}")
_ => write!(f, "{self:?}"),
}
}
}
@ -52,23 +56,28 @@ impl FromStr for Request {
type Err = ParseRequestError;
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(' ') {
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() {
return Err(ParseRequestError::EmptyMessage);
}
if let Some((user, host)) = user.rsplit_once('@') {
if host.is_empty() {
return Err(ParseRequestError::EmptyHost);
} else if user.is_empty() {
} else if user == "misfin://" {
return Err(ParseRequestError::EmptyUser);
}
let host = host.parse()?;
let user = user.to_string();
Ok(Request { user, host, message })
let Some(user) = user.strip_prefix("misfin://").map(ToString::to_string) else {
return Err(ParseRequestError::Malformed);
};
Ok(Request {
user,
host,
message,
})
} else {
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)]
pub struct Response {
@ -48,8 +48,8 @@ impl From<ParseIntError> for ParseResponseError {
}
}
impl From<StatusError> for ParseResponseError {
fn from(_value: StatusError) -> Self {
impl From<ParseStatusError> for ParseResponseError {
fn from(_value: ParseStatusError) -> Self {
Self::StatusError
}
}
@ -97,9 +97,8 @@ mod tests {
fn parse_badint() {
let response = "twenty message deliverred\r\n".parse::<Response>();
match response {
Err(ParseResponseError::ParseInt(_)) => {},
Err(ParseResponseError::ParseInt(_)) => {}
_ => panic!(),
}
}
}

View file

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