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}