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}