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`.
40    pub fn field<T>(
41        mut self,
42        name: &str,
43        value: &T,
44        f: impl FnOnce(FieldValidator<'_, T>) -> FieldValidator<'_, T>,
45    ) -> Self {
46        let mut field_errors = Vec::new();
47        let fv = FieldValidator::new(value, &mut field_errors);
48        f(fv);
49        if !field_errors.is_empty() {
50            self.errors
51                .entry(name.to_string())
52                .or_default()
53                .extend(field_errors);
54        }
55        self
56    }
57
58    /// Finalize validation.
59    ///
60    /// # Errors
61    ///
62    /// Returns [`ValidationError`] when one or more fields have errors.
63    pub fn check(self) -> Result<(), ValidationError> {
64        if self.errors.is_empty() {
65            Ok(())
66        } else {
67            Err(ValidationError::new(self.errors))
68        }
69    }
70}
71
72impl Default for Validator {
73    fn default() -> Self {
74        Self::new()
75    }
76}