Skip to main content

par_validator/
builder.rs

1//! Key-path–driven validation builder ([`Rule`]).
2//!
3//! Each rule is a **`fn` predicate** paired with an error value `E`. The predicate returns
4//! **`true` if the field is valid**; `false` means failure and the paired `E` is returned from
5//! [`Rule::apply`].
6//!
7//! For loading rule **metadata** (which field, which predicate, error code) from a CSV catalog, see
8//! the `rule_csv_catalog` example and `examples/data/validation_catalog.csv` in the crate source.
9//!
10//! # Minimal runnable example
11//!
12//! From the crate directory: `cargo run --example basics` — see `examples/basics.rs`.
13
14use std::fmt::Debug;
15
16use rayon::prelude::*;
17use rust_key_paths::{AccessorTrait, KpType};
18
19/// Binds a [`KpType`] (from `#[derive(Kp)]`) to a root value and a list of validation predicates.
20///
21/// Each rule is a **`bool` predicate** plus an **`E`** value returned when the predicate is
22/// **`false`**.
23///
24/// # Semantics
25///
26/// - **Mandatory** rules run in order. The first predicate that returns **`false`** stops the run;
27///   [`apply`](Self::apply) returns `vec![that rule’s E]`.
28/// - **Non-mandatory** rules run **in parallel** (Rayon). Each predicate that returns **`false`**
29///   contributes its `E`; passing rules produce no entry. The result may be **empty** if all pass.
30///
31/// # Examples
32///
33/// ```
34/// use key_paths_derive::Kp;
35/// use par_validator::builder::Rule;
36///
37/// #[derive(Kp)]
38/// struct Invoice {
39///     reference: String,
40/// }
41///
42/// fn non_blank(r: Option<&String>) -> bool {
43///     r.map(|s| !s.trim().is_empty()).unwrap_or(false)
44/// }
45///
46/// fn len_at_most_8(r: Option<&String>) -> bool {
47///     r.map(|s| s.len() <= 8).unwrap_or(false)
48/// }
49///
50/// let inv = Invoice {
51///     reference: "INV-01".into(),
52/// };
53///
54/// let failures: Vec<&'static str> = Rule::new(Invoice::reference())
55///     .with_root(&inv)
56///     .mandatory_rule(non_blank, "reference_blank")
57///     .rule(len_at_most_8, "reference_too_long")
58///     .apply();
59///
60/// assert!(failures.is_empty());
61///
62/// let bad = Invoice {
63///     reference: "TOO_LONG_REFERENCE".into(),
64/// };
65/// let failures = Rule::new(Invoice::reference())
66///     .with_root(&bad)
67///     .mandatory_rule(non_blank, "reference_blank")
68///     .rule(len_at_most_8, "reference_too_long")
69///     .apply();
70///
71/// assert_eq!(failures, vec!["reference_too_long"]);
72/// ```
73pub struct Rule<'a, R, V, E: PartialEq + Eq + Send + Sync> {
74    root:            Option<&'a R>,
75    kp:              KpType<'a, R, V>,
76    mandatory_rules: Vec<(fn(Option<&'a V>) -> bool, E)>,
77    rules:           Vec<(fn(Option<&'a V>) -> bool, E)>,
78}
79
80impl<'a, R, V, E> Rule<'a, R, V, E>
81where
82    E: Debug + Clone + 'static + PartialEq + Eq + Send + Sync,
83    R: Sync,
84    V: Sync,
85{
86    /// Starts a builder for the given statically dispatched key path (`#[derive(Kp)]` fields).
87    pub fn new(kp: KpType<'a, R, V>) -> Self {
88        Self {
89            root: None,
90            kp,
91            rules: vec![],
92            mandatory_rules: vec![],
93        }
94    }
95
96    /// Supply the struct instance `root` that the key path reads from.
97    pub fn with_root(mut self, root: &'a R) -> Self {
98        self.root = Some(root);
99        self
100    }
101
102    /// Append a rule executed **in parallel** with other non-mandatory rules when you call
103    /// [`apply`](Self::apply).
104    pub fn rule(mut self, f: fn(Option<&'a V>) -> bool, e: E) -> Self {
105        self.rules.push((f, e));
106        self
107    }
108
109    /// Rule that runs **before** parallel rules, in order. On the first predicate that returns
110    /// **`false`**, [`apply`](Self::apply) returns `vec![e]` for that rule’s error value `e`.
111    pub fn mandatory_rule(mut self, f: fn(Option<&'a V>) -> bool, e: E) -> Self {
112        self.mandatory_rules.push((f, e));
113        self
114    }
115
116    /// Resolves `Option<&V>` via the key path, runs mandatory rules, then runs remaining rules on
117    /// a Rayon thread pool.
118    pub fn apply(&self) -> Vec<E> {
119        let val = self.kp.get_optional(self.root);
120        for rule in self.mandatory_rules.iter() {
121            if !rule.0(val) {
122                return vec![rule.1.clone()];
123            }
124        }
125        self.rules
126            .par_iter()
127            .filter_map(|rule| if rule.0(val) { None } else { Some(rule.1.clone()) })
128            .collect()
129    }
130}