use std::{collections::BTreeMap, convert::Infallible, sync::LazyLock};
use async_trait::async_trait;
pub use regex::Regex;
use crate::{
errors::{FieldError, FieldErrors, FieldUnit},
traits::FieldValidator,
};
pub struct LengthInChars {
pub min_length: u32,
pub max_length: u32,
}
#[async_trait]
impl FieldValidator<()> for LengthInChars {
type Error = Infallible;
async fn validate(
&self,
value: &str,
errors: &mut FieldErrors,
_context: &mut (),
) -> Result<(), Self::Error> {
let length = value.chars().count().min(u32::MAX as usize) as u32;
if length < self.min_length {
errors.push(FieldError::TooShort {
current: length,
min_length: self.min_length,
max_length: self.max_length,
unit: FieldUnit::Characters,
});
}
if length > self.max_length {
errors.push(FieldError::TooLong {
current: length,
min_length: self.min_length,
max_length: self.max_length,
unit: FieldUnit::Characters,
});
}
Ok(())
}
}
static EMAIL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$").expect("email regex")
});
pub struct Email;
#[async_trait]
impl FieldValidator<()> for Email {
type Error = Infallible;
async fn validate(
&self,
value: &str,
errors: &mut FieldErrors,
_context: &mut (),
) -> Result<(), Self::Error> {
if !EMAIL_REGEX.is_match_at(value, 0) {
errors.push(FieldError::Custom {
code: "invalid_email".to_owned(),
description: "Not a valid e-mail address".to_owned(),
values: BTreeMap::new(),
})
}
Ok(())
}
}
#[async_trait]
impl FieldValidator<()> for Regex {
type Error = Infallible;
async fn validate(
&self,
value: &str,
errors: &mut FieldErrors,
_context: &mut (),
) -> Result<(), Self::Error> {
if !self.is_match_at(value, 0) {
errors.push(FieldError::Custom {
code: "regex_unmatched".to_owned(),
description: "Field did not match the intended pattern".to_owned(),
values: BTreeMap::new(),
})
}
Ok(())
}
}