Skip to main content

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}