use time::{Date, OffsetDateTime}; use crate::errors::AuthError; pub fn validate_email(raw: &str) -> Result { let email = raw.trim().to_lowercase(); if email.len() < 3 || email.len() > 254 { return Err(AuthError::BadRequest("invalid email address".to_string())); } if email.chars().any(|c| c.is_whitespace()) { return Err(AuthError::BadRequest("invalid email address".to_string())); } let Some(at) = email.find('@') else { return Err(AuthError::BadRequest("invalid email address".to_string())); }; if at == 0 || at == email.len() - 1 || !email[at + 1..].contains('.') { return Err(AuthError::BadRequest("invalid email address".to_string())); } Ok(email) } pub fn validate_username(name: &str) -> Result { if name.is_empty() || name.len() > 64 { return Err(AuthError::BadRequest( "username must be between 1 and 64 characters".to_string(), )); } if !name .chars() .all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '.' | '-')) { return Err(AuthError::BadRequest( "username may only contain letters, digits, underscores, dots and dashes".to_string(), )); } Ok(name.to_string()) } pub fn derive_username(email: &str, explicit: Option<&str>) -> Result { match explicit { Some(name) => validate_username(name), None => { let local_part = email.split('@').next().unwrap_or_default(); let base = local_part.split('+').next().unwrap_or_default(); validate_username(base) } } } pub fn validate_age(date_of_birth: Option, min_age: u8) -> Result<(), AuthError> { let Some(dob) = date_of_birth else { return Err(AuthError::BadRequest( "date of birth is required".to_string(), )); }; let today = OffsetDateTime::now_utc().date(); let mut age = today.year() - dob.year(); if (today.month(), today.day()) < (dob.month(), dob.day()) { age -= 1; } if age < min_age as i32 { return Err(AuthError::BadRequest(format!( "you must be at least {min_age} years old" ))); } Ok(()) }