Skip to content
Clone
eeff292
80 lines 2.3 KB Rust
use crate::errors::{SpaceError, SpaceResult};
const MIN_EMAIL_LEN: usize = 3;
const MAX_EMAIL_LEN: usize = 254;
pub fn validate_email(raw: &str) -> SpaceResult<String> {
let email = raw.trim().to_lowercase();
if email.len() < MIN_EMAIL_LEN || email.len() > MAX_EMAIL_LEN {
return Err(SpaceError::InvalidEmail);
}
if email.chars().any(char::is_whitespace) {
return Err(SpaceError::InvalidEmail);
}
let mut parts = email.split('@');
let (Some(local), Some(domain), None) = (parts.next(), parts.next(), parts.next()) else {
return Err(SpaceError::InvalidEmail);
};
if !valid_local_part(local) || !valid_domain(domain) {
return Err(SpaceError::InvalidEmail);
}
Ok(email)
}
fn valid_local_part(local: &str) -> bool {
const MAX_LOCAL_LEN: usize = 64;
if local.is_empty() || local.len() > MAX_LOCAL_LEN {
return false;
}
if local.starts_with('.') || local.ends_with('.') || local.contains("..") {
return false;
}
local.chars().all(|c| {
c.is_ascii_alphanumeric()
|| matches!(
c,
'.' | '!'
| '#'
| '$'
| '%'
| '&'
| '\''
| '*'
| '+'
| '-'
| '/'
| '='
| '?'
| '^'
| '_'
| '`'
| '{'
| '|'
| '}'
| '~'
)
})
}
fn valid_domain(domain: &str) -> bool {
const MAX_LABEL_LEN: usize = 63;
let labels: Vec<&str> = domain.split('.').collect();
if labels.len() < 2 {
return false;
}
for label in &labels {
if label.is_empty() || label.len() > MAX_LABEL_LEN {
return false;
}
if label.starts_with('-') || label.ends_with('-') {
return false;
}
if !label.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') {
return false;
}
}
let Some(tld) = labels.last() else {
return false;
};
tld.len() >= 2 && tld.chars().all(|c| c.is_ascii_alphabetic())
}