error_rail/async_ext/
validation.rs

1//! Async validation utilities.
2//!
3//! Provides functions for running multiple async validations and collecting
4//! all errors, mirroring the sync `Validation` type's accumulating behavior.
5
6use core::future::Future;
7
8use crate::types::alloc_type::Vec;
9use crate::Validation;
10
11/// Runs multiple async validations sequentially and collects all errors.
12///
13/// Unlike `Result` which short-circuits on the first error, this function
14/// executes all validations and accumulates any errors that occur.
15///
16/// # Note
17///
18/// Validations are executed **sequentially** (not in parallel) to maintain
19/// runtime neutrality. For parallel execution, use a runtime-specific
20/// combinator like `futures::join_all` or `tokio::join!`.
21///
22/// # Example
23///
24/// ```rust,ignore
25/// use error_rail::prelude_async::*;
26///
27/// async fn validate_user(user: &User) -> Validation<ValidationError, ()> {
28///     validate_all_async([
29///         validate_email(&user.email),
30///         validate_username(&user.username),
31///         check_user_exists(&user.id),
32///     ])
33///     .await
34///     .map(|_| ())
35/// }
36/// ```
37pub async fn validate_all_async<T, E, Fut, I>(validations: I) -> Validation<E, Vec<T>>
38where
39    I: IntoIterator<Item = Fut>,
40    Fut: Future<Output = Validation<E, T>>,
41{
42    let mut results = Vec::new();
43
44    for fut in validations {
45        results.push(fut.await);
46    }
47
48    collect_validation_results(results)
49}
50
51/// Runs async validations sequentially, where each validation depends on
52/// the previous result.
53///
54/// Stops at the first invalid result and returns accumulated errors.
55///
56/// # Example
57///
58/// ```rust,ignore
59/// use error_rail::prelude_async::*;
60///
61/// async fn validate_order(order_id: u64) -> Validation<OrderError, Order> {
62///     validate_seq_async(
63///         order_id,
64///         [
65///             |id| async move { fetch_order(id).await },
66///             |order| async move { validate_inventory(&order).await },
67///             |order| async move { validate_payment(&order).await },
68///         ],
69///     )
70///     .await
71/// }
72/// ```
73pub async fn validate_seq_async<T, E, F, Fut>(
74    initial: T,
75    validators: impl IntoIterator<Item = F>,
76) -> Validation<E, T>
77where
78    F: FnOnce(T) -> Fut,
79    Fut: Future<Output = Validation<E, T>>,
80{
81    let mut current = initial;
82
83    for validator in validators {
84        match validator(current).await {
85            Validation::Valid(v) => current = v,
86            invalid => return invalid,
87        }
88    }
89
90    Validation::Valid(current)
91}
92
93/// Collects validation results into a single `Validation`.
94///
95/// This helper function aggregates multiple validation results, accumulating
96/// all errors rather than short-circuiting on the first failure.
97///
98/// # Behavior
99///
100/// - If **all** results are `Valid`, returns `Valid(Vec<T>)` containing all values
101///   in their original order.
102/// - If **any** result is `Invalid`, returns `Invalid` with all accumulated errors
103///   from all invalid results combined.
104///
105/// # Arguments
106///
107/// * `results` - A vector of `Validation<E, T>` results to collect
108///
109/// # Returns
110///
111/// A single `Validation<E, Vec<T>>` that either contains all success values
112/// or all accumulated errors.
113fn collect_validation_results<T, E>(results: Vec<Validation<E, T>>) -> Validation<E, Vec<T>> {
114    let mut errors: Vec<E> = Vec::new();
115    let mut values = Vec::with_capacity(results.len());
116
117    for result in results {
118        match result {
119            Validation::Valid(v) => values.push(v),
120            Validation::Invalid(errs) => errors.extend(errs),
121        }
122    }
123
124    if errors.is_empty() {
125        Validation::Valid(values)
126    } else {
127        Validation::invalid_many(errors)
128    }
129}