use crate::error::{DomainError, DomainErrorKind};
use email_address::EmailAddress;
use stillwater::refined::{Predicate, Refined};
#[derive(Debug, Clone, Copy, Default)]
pub struct ValidEmail;
impl Predicate<String> for ValidEmail {
type Error = DomainError;
fn check(value: &String) -> Result<(), Self::Error> {
if value.is_empty() {
return Err(DomainError {
format_name: "email address",
value: value.clone(),
reason: DomainErrorKind::Empty,
example: "user@example.com",
});
}
if EmailAddress::is_valid(value) {
Ok(())
} else {
Err(DomainError {
format_name: "email address",
value: value.clone(),
reason: DomainErrorKind::InvalidFormat {
expected: "local@domain",
},
example: "user@example.com",
})
}
}
fn description() -> &'static str {
"RFC 5321 email address"
}
}
pub type Email = Refined<String, ValidEmail>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_simple_email() {
assert!(Email::new("user@example.com".to_string()).is_ok());
}
#[test]
fn valid_with_plus_tag() {
assert!(Email::new("user+tag@example.com".to_string()).is_ok());
}
#[test]
fn valid_with_subdomain() {
assert!(Email::new("user@mail.example.com".to_string()).is_ok());
}
#[test]
fn valid_quoted_local_part() {
assert!(Email::new("\"quoted\"@example.com".to_string()).is_ok());
}
#[test]
fn valid_localhost() {
assert!(Email::new("user@localhost".to_string()).is_ok());
}
#[test]
fn valid_ip_literal() {
assert!(Email::new("user@[192.168.1.1]".to_string()).is_ok());
}
#[test]
fn invalid_empty() {
let result = Email::new(String::new());
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err.reason, DomainErrorKind::Empty));
}
#[test]
fn invalid_missing_at() {
let result = Email::new("userexample.com".to_string());
assert!(result.is_err());
}
#[test]
fn invalid_missing_local() {
let result = Email::new("@example.com".to_string());
assert!(result.is_err());
}
#[test]
fn invalid_missing_domain() {
let result = Email::new("user@".to_string());
assert!(result.is_err());
}
#[test]
fn invalid_double_at() {
let result = Email::new("user@@example.com".to_string());
assert!(result.is_err());
}
#[test]
fn invalid_spaces() {
let result = Email::new("user @example.com".to_string());
assert!(result.is_err());
}
#[test]
fn error_includes_format_name() {
let result = Email::new("invalid".to_string());
let err = result.unwrap_err();
assert_eq!(err.format_name, "email address");
}
#[test]
fn error_includes_example() {
let result = Email::new("invalid".to_string());
let err = result.unwrap_err();
assert_eq!(err.example, "user@example.com");
}
#[test]
fn error_display_is_readable() {
let result = Email::new("invalid".to_string());
let err = result.unwrap_err();
let display = err.to_string();
assert!(display.contains("email address"));
assert!(display.contains("user@example.com"));
}
#[test]
fn description_returns_expected() {
assert_eq!(ValidEmail::description(), "RFC 5321 email address");
}
}