use super::lazy_patterns::EMAIL_REGEX;
use super::{ValidationError, ValidationResult, Validator};
pub struct EmailValidator {
message: Option<String>,
}
impl EmailValidator {
pub fn new() -> Self {
Self { message: None }
}
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
fn validate_with_length_check(&self, email: &str) -> bool {
if email.len() > 320 {
return false;
}
let parts: Vec<&str> = email.split('@').collect();
if parts.len() != 2 {
return false;
}
let local_part = parts[0];
let domain_part = parts[1];
if local_part.is_empty() || local_part.len() > 64 {
return false;
}
if domain_part.is_empty() || domain_part.len() > 255 {
return false;
}
if local_part.contains("..") {
return false;
}
for label in domain_part.split('.') {
if label.is_empty() || label.len() > 63 {
return false;
}
}
EMAIL_REGEX.is_match(email)
}
}
impl Default for EmailValidator {
fn default() -> Self {
Self::new()
}
}
impl Validator<String> for EmailValidator {
fn validate(&self, value: &String) -> ValidationResult<()> {
if self.validate_with_length_check(value) {
Ok(())
} else if let Some(ref msg) = self.message {
Err(ValidationError::Custom(msg.clone()))
} else {
Err(ValidationError::InvalidEmail(value.clone()))
}
}
}
impl Validator<str> for EmailValidator {
fn validate(&self, value: &str) -> ValidationResult<()> {
if self.validate_with_length_check(value) {
Ok(())
} else if let Some(ref msg) = self.message {
Err(ValidationError::Custom(msg.clone()))
} else {
Err(ValidationError::InvalidEmail(value.to_string()))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_emails() {
let validator = EmailValidator::new();
let valid_emails = vec![
"test@example.com",
"user.name@example.com",
"user+tag@example.co.uk",
"user_name@example.com",
"user%test@example.com",
"user-name@sub.example.com",
"a@example.com",
"test@sub.sub.example.com",
"123@example.com",
];
for email in valid_emails {
assert!(
validator.validate(email).is_ok(),
"Expected {} to be valid",
email
);
}
}
#[test]
fn test_invalid_emails() {
let validator = EmailValidator::new();
let invalid_emails = vec![
"invalid-email", "@example.com", "user@", "user..name@example.com", ".user@example.com", "user.@example.com", "user@-example.com", "user@example-.com", "user@example", "user@example.c", "user name@example.com", "user@exam ple.com", "user@@example.com", "user@.example.com", "user@example.com.", ];
for email in invalid_emails {
assert!(
validator.validate(email).is_err(),
"Expected {} to be invalid",
email
);
}
}
#[test]
fn test_length_constraints() {
let validator = EmailValidator::new();
let long_local = format!("{}@example.com", "a".repeat(65));
assert!(validator.validate(&long_local).is_err());
let long_domain = format!("user@{}.com", "a".repeat(252));
assert!(validator.validate(&long_domain).is_err());
let very_long_email = format!("{}@{}.com", "a".repeat(64), "b".repeat(252));
assert!(validator.validate(&very_long_email).is_err());
let long_label = format!("user@{}.example.com", "a".repeat(64));
assert!(validator.validate(&long_label).is_err());
let max_local = format!("{}@example.com", "a".repeat(64));
assert!(validator.validate(&max_local).is_ok());
}
#[test]
fn test_case_insensitivity() {
let validator = EmailValidator::new();
assert!(validator.validate("Test@Example.COM").is_ok());
assert!(validator.validate("USER@EXAMPLE.COM").is_ok());
}
#[test]
fn test_email_validator_with_numbers() {
let validator = EmailValidator::new();
assert!(validator.validate("123@example.com").is_ok());
assert!(validator.validate("user123@example.com").is_ok());
assert!(validator.validate("123user@example123.com").is_ok());
}
#[test]
fn test_email_validator_with_special_characters() {
let validator = EmailValidator::new();
assert!(validator.validate("user+tag@example.com").is_ok());
assert!(validator.validate("user_name@example.com").is_ok());
assert!(validator.validate("user-name@example.com").is_ok());
assert!(validator.validate("user.name@example.com").is_ok());
assert!(validator.validate("user%test@example.com").is_ok());
}
#[test]
fn test_email_validator_subdomains() {
let validator = EmailValidator::new();
assert!(validator.validate("user@mail.example.com").is_ok());
assert!(validator.validate("user@sub.mail.example.com").is_ok());
assert!(validator.validate("user@a.b.c.d.example.com").is_ok());
}
#[test]
fn test_email_validator_tld_variations() {
let validator = EmailValidator::new();
assert!(validator.validate("user@example.co").is_ok());
assert!(validator.validate("user@example.com").is_ok());
assert!(validator.validate("user@example.org").is_ok());
assert!(validator.validate("user@example.net").is_ok());
assert!(validator.validate("user@example.info").is_ok());
assert!(validator.validate("user@example.museum").is_ok());
}
#[test]
fn test_email_validator_edge_cases() {
let validator = EmailValidator::new();
assert!(validator.validate("a@b.co").is_ok());
assert!(validator.validate("user@123.com").is_ok());
assert!(validator.validate("user@example123.com").is_ok());
}
#[test]
fn test_email_validator_invalid_formats() {
let validator = EmailValidator::new();
assert!(validator.validate("user@domain@example.com").is_err());
assert!(validator.validate("@").is_err());
assert!(validator.validate("user@").is_err());
assert!(validator.validate("@domain.com").is_err());
assert!(validator.validate("user name@example.com").is_err());
assert!(validator.validate("user@exam ple.com").is_err());
assert!(validator.validate("user@example,com").is_err());
}
#[test]
fn test_email_validator_dot_rules() {
let validator = EmailValidator::new();
assert!(validator.validate("user..name@example.com").is_err());
assert!(validator.validate(".user@example.com").is_err());
assert!(validator.validate("user.@example.com").is_err());
assert!(validator.validate("user.name.test@example.com").is_ok());
}
#[test]
fn test_email_validator_hyphen_rules() {
let validator = EmailValidator::new();
assert!(validator.validate("user@my-domain.com").is_ok());
assert!(validator.validate("user@my-long-domain-name.com").is_ok());
assert!(validator.validate("user@-invalid.com").is_err());
assert!(validator.validate("user@invalid-.com").is_err());
assert!(validator.validate("user@invalid.-com").is_err());
assert!(validator.validate("user@invalid.com-").is_err());
}
#[test]
fn test_email_validator_returns_correct_error() {
let validator = EmailValidator::new();
let invalid_email = "invalid";
match validator.validate(invalid_email) {
Err(ValidationError::InvalidEmail(email)) => {
assert_eq!(email, invalid_email);
}
_ => panic!("Expected InvalidEmail error"),
}
}
#[test]
fn test_email_validator_with_string_type() {
let validator = EmailValidator::new();
let email = String::from("test@example.com");
assert!(validator.validate(&email).is_ok());
let invalid = String::from("invalid");
assert!(validator.validate(&invalid).is_err());
}
}