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}