1use crate::error::{OrmError, OrmResult};
2use std::collections::HashMap;
3
4#[derive(Debug, Default, Clone)]
10pub struct ValidationErrors {
11 errors: HashMap<String, Vec<String>>,
12}
13
14impl ValidationErrors {
15 pub fn new() -> Self {
16 Self::default()
17 }
18
19 pub fn add(&mut self, field: impl Into<String>, message: impl Into<String>) {
20 self.errors
21 .entry(field.into())
22 .or_default()
23 .push(message.into());
24 }
25
26 pub fn is_empty(&self) -> bool {
27 self.errors.is_empty()
28 }
29
30 pub fn iter(&self) -> impl Iterator<Item = (&String, &Vec<String>)> {
31 self.errors.iter()
32 }
33
34 pub fn merge(&mut self, other: ValidationErrors) {
35 for (field, msgs) in other.errors {
36 self.errors.entry(field).or_default().extend(msgs);
37 }
38 }
39
40 pub fn into_orm_error(self) -> OrmError {
41 let msg = self
42 .errors
43 .iter()
44 .map(|(f, msgs)| format!("{}: {}", f, msgs.join(", ")))
45 .collect::<Vec<_>>()
46 .join("; ");
47 OrmError::Validation(msg)
48 }
49}
50
51pub struct ValidationContext<'a> {
57 errors: &'a mut ValidationErrors,
58}
59
60impl<'a> ValidationContext<'a> {
61 pub fn new(errors: &'a mut ValidationErrors) -> Self {
62 Self { errors }
63 }
64
65 pub fn add_error(&mut self, field: impl Into<String>, message: impl Into<String>) {
67 self.errors.add(field, message);
68 }
69
70 pub fn has_errors(&self) -> bool {
72 !self.errors.is_empty()
73 }
74}
75
76pub trait Validate {
82 fn validate_attrs(&self) -> ValidationErrors;
84
85 fn validate_custom(&self, _ctx: &mut ValidationContext) {}
87
88 fn validate(&self) -> OrmResult<()> {
90 let mut errors = self.validate_attrs();
91 {
92 let mut ctx = ValidationContext::new(&mut errors);
93 self.validate_custom(&mut ctx);
94 }
95 if errors.is_empty() {
96 Ok(())
97 } else {
98 Err(errors.into_orm_error())
99 }
100 }
101}
102
103static EMAIL_RE: once_cell::sync::Lazy<regex::Regex> =
108 once_cell::sync::Lazy::new(|| regex::Regex::new(r"^[^\s@]+@[^\s@]+\.[^\s@]+$").unwrap());
109
110static URL_RE: once_cell::sync::Lazy<regex::Regex> =
111 once_cell::sync::Lazy::new(|| regex::Regex::new(r"^https?://[^\s/$.?#].[^\s]*$").unwrap());
112
113pub fn validate_email(value: &str) -> bool {
114 EMAIL_RE.is_match(value)
115}
116
117pub fn validate_url(value: &str) -> bool {
118 URL_RE.is_match(value)
119}
120
121pub fn validate_min_length(value: &str, min: usize) -> bool {
122 value.len() >= min
123}
124
125pub fn validate_max_length(value: &str, max: usize) -> bool {
126 value.len() <= max
127}
128
129pub fn validate_regex(value: &str, pattern: &str) -> bool {
130 regex::Regex::new(pattern)
131 .map(|re| re.is_match(value))
132 .unwrap_or(false)
133}