Skip to main content

par_validator/
lib.rs

1//! Parallel validation helpers for **string-like** fields (CPU / Rayon) and **fixed-point
2//! numerics** (GPU / wgpu).
3//!
4//! ## String rules
5//! Use [`RuleBuilder`] with key paths from [`rust_key_paths`] (typically via `key-paths-derive`).
6//! Mandatory rules run **sequentially** and short-circuit on the first failure; remaining rules run
7//! in parallel with Rayon inside [`RuleBuilder::apply`].
8//!
9//! ## Numeric rules
10//! See [`gpu_numeric`] for [`GpuNumericEngine`](gpu_numeric::GpuNumericEngine), which batches
11//! [`NumericRule`](gpu_numeric::NumericRule) rows in a single compute dispatch. Values cross the
12//! CPU/GPU boundary as **i32 × 100** (no `f32`/`f64` in the shader).
13//!
14//! ## Examples
15//! - `cargo run --example hybrid_gpu` — small hybrid demo  
16//! - `cargo run --example fintech_rayon_nested` — large nested batch, CPU only  
17//! - `cargo run --example fintech_gpu_batch` — large numeric batch, GPU only  
18//! - `cargo run --example fintech_hybrid_batch` — both layers on a trade batch  
19
20#![forbid(unsafe_code)]
21
22use std::fmt::Debug;
23
24use rayon::prelude::*;
25use rust_key_paths::{AccessorTrait, KpType};
26
27pub mod errors;
28pub mod gpu_numeric;
29
30pub use errors::RuleBuilderError;
31
32/// Fluent wrapper around a [`KpType`] (key path) and a set of validation functions.
33///
34/// `R` is the **root** type you pass to [`RuleBuilder::with_root`]; `V` is the **value** type at
35/// the end of the path (often `String`). `E` is your error payload (e.g. `String`).
36///
37/// Rule functions must be `fn` pointers (not closures that capture state) so they can be stored
38/// in a [`Vec`] and shared across Rayon threads.
39pub struct RuleBuilder<'a, R, V, E: PartialEq + Eq + Send + Sync> {
40    root: Option<&'a R>,
41    kp: KpType<'a, R, V>,
42    mandatory_rules: Vec<fn(Option<&'a V>) -> RuleBuilderError<E>>,
43    rules: Vec<fn(Option<&'a V>) -> RuleBuilderError<E>>,
44}
45
46impl<'a, R, V, E> RuleBuilder<'a, R, V, E>
47where
48    E: Debug + Clone + 'static + PartialEq + Eq + Send + Sync,
49    R: Sync,
50    V: Sync,
51{
52    /// Starts a builder for the given statically dispatched key path (`#[derive(Kp)]` fields).
53    pub fn new(kp: KpType<'a, R, V>) -> Self {
54        Self {
55            root: None,
56            kp,
57            rules: vec![],
58            mandatory_rules: vec![]
59        }
60    }
61
62    /// Supply the struct instance `root` that the key path reads from.
63    pub fn with_root(mut self, root: &'a R) -> Self {
64        self.root = Some(root);
65        self
66    }
67
68    /// Append a rule executed **in parallel** with other non-mandatory rules when you call
69    /// [`apply`](Self::apply).
70    pub fn rule(mut self, f: fn(Option<&'a V>) -> RuleBuilderError<E>) -> Self {
71        self.rules.push(f);
72        self
73    }
74
75    /// Rule that runs **before** parallel rules, in order. On the first non-[`Success`](RuleBuilderError::Success),
76    /// [`apply`](Self::apply) returns immediately with that single outcome.
77    pub fn mandatory_rule(mut self, f: fn(Option<&'a V>) -> RuleBuilderError<E>) -> Self {
78        self.mandatory_rules.push(f);
79        self
80    }
81
82    /// Deprecated typo; use [`Self::mandatory_rule`].
83    #[deprecated(note = "use mandatory_rule")]
84    pub fn madatory_rule(self, f: fn(Option<&'a V>) -> RuleBuilderError<E>) -> Self {
85        self.mandatory_rule(f)
86    }
87
88    /// Resolves `Option<&V>` via the key path, runs mandatory rules, then runs remaining rules on
89    /// a Rayon thread pool.
90    pub fn apply(&self) -> Vec<RuleBuilderError<E>> {
91        let val = self.kp.get_optional(self.root);
92        for rule in self.mandatory_rules.iter() {
93            let result = rule(val);
94            if RuleBuilderError::Success != result {
95                return vec![result];
96            }
97        }
98        self.rules.par_iter().map(|f| f(val)).collect()
99    }
100}
101
102