modo/validate/validator.rs
1use std::collections::HashMap;
2
3use super::error::ValidationError;
4use super::rules::FieldValidator;
5
6/// A builder that collects validation errors across multiple fields.
7///
8/// Call [`Validator::new`] (or [`Default::default`]) to start, chain
9/// [`field`](Validator::field) calls for each input to validate, then call
10/// [`check`](Validator::check) to obtain the result. Errors from all fields
11/// are gathered before returning — no short-circuit.
12///
13/// # Example
14///
15/// ```rust,no_run
16/// use modo::validate::Validator;
17///
18/// let result = Validator::new()
19/// .field("name", &"Alice".to_string(), |f| f.required().min_length(1))
20/// .field("age", &25i32, |f| f.range(18..=120))
21/// .check();
22/// ```
23pub struct Validator {
24 errors: HashMap<String, Vec<String>>,
25}
26
27impl Validator {
28 /// Create a new empty validator.
29 pub fn new() -> Self {
30 Self {
31 errors: HashMap::new(),
32 }
33 }
34
35 /// Validate a single field. The closure receives a `FieldValidator` and should
36 /// chain rule methods on it. Any rule failures are collected as errors for this field.
37 ///
38 /// Works with any value type — string rules are available for `T: AsRef<str>`,
39 /// numeric rules for `T: PartialOrd + Display`. Errors from this call do not
40 /// short-circuit; subsequent `field` calls still execute and accumulate.
41 pub fn field<T>(
42 mut self,
43 name: &str,
44 value: &T,
45 f: impl FnOnce(FieldValidator<'_, T>) -> FieldValidator<'_, T>,
46 ) -> Self {
47 let mut field_errors = Vec::new();
48 let fv = FieldValidator::new(value, &mut field_errors);
49 f(fv);
50 if !field_errors.is_empty() {
51 self.errors
52 .entry(name.to_string())
53 .or_default()
54 .extend(field_errors);
55 }
56 self
57 }
58
59 /// Finalize validation.
60 ///
61 /// # Errors
62 ///
63 /// Returns [`ValidationError`] when one or more fields have errors.
64 pub fn check(self) -> Result<(), ValidationError> {
65 if self.errors.is_empty() {
66 Ok(())
67 } else {
68 Err(ValidationError::new(self.errors))
69 }
70 }
71}
72
73impl Default for Validator {
74 fn default() -> Self {
75 Self::new()
76 }
77}