use crate::error::{OrmError, OrmResult};
use std::collections::HashMap;
#[derive(Debug, Default, Clone)]
pub struct ValidationErrors {
errors: HashMap<String, Vec<String>>,
}
impl ValidationErrors {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, field: impl Into<String>, message: impl Into<String>) {
self.errors
.entry(field.into())
.or_default()
.push(message.into());
}
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &Vec<String>)> {
self.errors.iter()
}
pub fn merge(&mut self, other: ValidationErrors) {
for (field, msgs) in other.errors {
self.errors.entry(field).or_default().extend(msgs);
}
}
pub fn into_orm_error(self) -> OrmError {
let msg = self
.errors
.iter()
.map(|(f, msgs)| format!("{}: {}", f, msgs.join(", ")))
.collect::<Vec<_>>()
.join("; ");
OrmError::Validation(msg)
}
}
pub struct ValidationContext<'a> {
errors: &'a mut ValidationErrors,
}
impl<'a> ValidationContext<'a> {
pub fn new(errors: &'a mut ValidationErrors) -> Self {
Self { errors }
}
pub fn add_error(&mut self, field: impl Into<String>, message: impl Into<String>) {
self.errors.add(field, message);
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
}
pub trait Validate {
fn validate_attrs(&self) -> ValidationErrors;
fn validate_custom(&self, _ctx: &mut ValidationContext) {}
fn validate(&self) -> OrmResult<()> {
let mut errors = self.validate_attrs();
{
let mut ctx = ValidationContext::new(&mut errors);
self.validate_custom(&mut ctx);
}
if errors.is_empty() {
Ok(())
} else {
Err(errors.into_orm_error())
}
}
}
static EMAIL_RE: once_cell::sync::Lazy<regex::Regex> =
once_cell::sync::Lazy::new(|| regex::Regex::new(r"^[^\s@]+@[^\s@]+\.[^\s@]+$").unwrap());
static URL_RE: once_cell::sync::Lazy<regex::Regex> =
once_cell::sync::Lazy::new(|| regex::Regex::new(r"^https?://[^\s/$.?#].[^\s]*$").unwrap());
pub fn validate_email(value: &str) -> bool {
EMAIL_RE.is_match(value)
}
pub fn validate_url(value: &str) -> bool {
URL_RE.is_match(value)
}
pub fn validate_min_length(value: &str, min: usize) -> bool {
value.len() >= min
}
pub fn validate_max_length(value: &str, max: usize) -> bool {
value.len() <= max
}
pub fn validate_regex(value: &str, pattern: &str) -> bool {
regex::Regex::new(pattern)
.map(|re| re.is_match(value))
.unwrap_or(false)
}