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 iter = validations.into_iter();
43    let (lower, upper) = iter.size_hint();
44    let capacity = upper.unwrap_or(lower);
45
46    let mut values = Vec::with_capacity(capacity);
47    let mut errors: Vec<E> = Vec::new();
48
49    for fut in iter {
50        match fut.await {
51            Validation::Valid(v) => {
52                if errors.is_empty() {
53                    values.push(v);
54                }
55            },
56            Validation::Invalid(errs) => errors.extend(errs),
57        }
58    }
59
60    if errors.is_empty() {
61        Validation::Valid(values)
62    } else {
63        Validation::invalid_many(errors)
64    }
65}
66
67/// Runs async validations sequentially, where each validation depends on
68/// the previous result.
69///
70/// Stops at the first invalid result and returns accumulated errors.
71///
72/// # Example
73///
74/// ```rust,ignore
75/// use error_rail::prelude_async::*;
76///
77/// async fn validate_order(order_id: u64) -> Validation<OrderError, Order> {
78///     validate_seq_async(
79///         order_id,
80///         [
81///             |id| async move { fetch_order(id).await },
82///             |order| async move { validate_inventory(&order).await },
83///             |order| async move { validate_payment(&order).await },
84///         ],
85///     )
86///     .await
87/// }
88/// ```
89pub async fn validate_seq_async<T, E, F, Fut>(
90    initial: T,
91    validators: impl IntoIterator<Item = F>,
92) -> Validation<E, T>
93where
94    F: FnOnce(T) -> Fut,
95    Fut: Future<Output = Validation<E, T>>,
96{
97    let mut current = initial;
98
99    for validator in validators {
100        match validator(current).await {
101            Validation::Valid(v) => current = v,
102            invalid => return invalid,
103        }
104    }
105
106    Validation::Valid(current)
107}