zino_core/validation/validator/
email.rs1use self::InvalidEmail::*;
2use super::Validator;
3use crate::LazyLock;
4use regex::Regex;
5use std::{fmt, net::IpAddr, str::FromStr};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct EmailValidator;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum InvalidEmail {
14 Empty,
16 MissingAt,
18 UserLengthExceeded,
20 InvalidUser,
22 DomainLengthExceeded,
24 InvalidDomain,
26}
27
28impl fmt::Display for InvalidEmail {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Empty => write!(f, "value is empty"),
32 MissingAt => write!(f, "value is missing `@`"),
33 UserLengthExceeded => {
34 write!(f, "user length exceeded maximum of 64 characters")
35 }
36 InvalidUser => write!(f, "user contains unexpected characters"),
37 DomainLengthExceeded => {
38 write!(f, "domain length exceeded maximum of 255 characters")
39 }
40 InvalidDomain => write!(f, "domain contains unexpected characters"),
41 }
42 }
43}
44
45impl std::error::Error for InvalidEmail {}
46
47impl Validator<str> for EmailValidator {
48 type Error = InvalidEmail;
49
50 fn validate(&self, data: &str) -> Result<(), Self::Error> {
51 if data.is_empty() {
52 return Err(Empty);
53 }
54
55 let (user, domain) = data.split_once('@').ok_or(MissingAt)?;
56 if user.len() > 64 {
57 return Err(UserLengthExceeded);
58 }
59 if !EMAIL_USER_PATTERN.is_match(user) {
60 return Err(InvalidUser);
61 }
62 if domain.len() > 255 {
63 return Err(DomainLengthExceeded);
64 }
65 if !EMAIL_DOMAIN_PATTERN.is_match(domain)
66 && domain
67 .strip_prefix('[')
68 .and_then(|s| s.strip_suffix(']'))
69 .and_then(|s| IpAddr::from_str(s).ok())
70 .is_none()
71 {
72 return Err(InvalidDomain);
73 }
74 Ok(())
75 }
76}
77
78static EMAIL_USER_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
80 Regex::new(r"(?i-u)^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+\z")
81 .expect("fail to create a regex for the email user")
82});
83
84static EMAIL_DOMAIN_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
86 Regex::new(
87 r"(?i-u)^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$",
88 )
89 .expect("fail to create a regex for the email domain")
90});