Skip to content

Commit 307580b

Browse files
committed
Add fail_all, fail_all_vec
1 parent 32f1457 commit 307580b

File tree

5 files changed

+236
-122
lines changed

5 files changed

+236
-122
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ and this project adheres to
1111

1212
### Added
1313

14+
- Functions `fail_all`, `fail_all_vec`
1415
- Macro `return_multiple_errors!`

README.md

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,41 @@ Propagate multiple errors instead of just the first one.
44

55
## Description
66

7-
Rust's built-in `?` operator causes an early return on the first encountered
8-
error. However,
7+
Rust's `?` operator and `Iterator::collect::<Result<_, _>>` return early on the
8+
first encountered error. However,
99
[sometimes](https://users.rust-lang.org/t/accumulating-multiple-errors-error-products/93730)
10-
we want to execute multiple independent fallible actions and then report all
11-
errors at once.
10+
we want to execute multiple independent actions and then report all errors at
11+
once. Or turn all results into errors if there is at least one error.
1212

13-
This crate covers this use case and aims to become an "umbrella" for more "mass
14-
error handling" fuctionality.
13+
This crate covers these use cases and aims to become an easily googlable "hub" for:
14+
15+
- more "mass error handling" fuctionality
16+
- knowledge about related functionality in other crates
17+
18+
Think of `multiple_errors` as
19+
[itertools](https://github.com/rust-itertools/itertools). It's also a
20+
lightweight "pure logic" crate with no dependencies, and should be suitable for
21+
`no_std` and old MSRVs. I haven't worked on this yet, please open an issue or a
22+
pull request if you need this.
1523

1624
## Example
1725

1826
```rust
19-
use multiple_errors::return_multiple_errors;
27+
use multiple_errors::{fail_all_vec, return_multiple_errors};
2028
use multiple_errors::testing_prelude::*;
29+
30+
// fail_all_vec:
31+
32+
let err = fail_all_vec(
33+
vec![Ok(A), Err(ErrA), Ok(A)],
34+
|res| res.err().map(HighLevelErr::from).unwrap_or(HighLevelErr::B(ErrB))
35+
);
36+
assert_eq!(err, Err(vec![ErrB.into(), ErrA.into(), ErrB.into()]));
37+
38+
let ok = fail_all_vec(vec![Ok(A), Ok(A)], |_: Result<_, ErrA>| ErrC);
39+
assert_eq!(ok, Ok(vec![A, A]));
40+
41+
// return_multiple_errors:
2142

2243
fn a_b_c() -> Result<(A, B, C), Vec<HighLevelErr>> {
2344
return_multiple_errors!(
@@ -36,14 +57,6 @@ fn a_b_c() -> Result<(A, B, C), Vec<HighLevelErr>> {
3657
}
3758
```
3859

39-
## Details
40-
41-
Currently, `multiple_errors` is a lightweight "pure logic" crate with no
42-
dependencies, similar to
43-
[itertools](https://github.com/rust-itertools/itertools). It should be (at least
44-
partially) suitable for `no_std` and old MSRVs. Please open an issue or pull
45-
request if you need these features.
46-
4760
## Similar crates
4861

4962
- [frunk::validated::Validated](https://docs.rs/frunk/0.4.2/frunk/validated/enum.Validated.html)
@@ -54,6 +67,15 @@ request if you need these features.
5467
It achieves similar goals to `return_multiple_errors!`, but in a more
5568
abstract, type-heavy and composable way.
5669

70+
- [itertools::Itertools::partition_result](https://docs.rs/itertools/0.12.1/itertools/trait.Itertools.html#method.partition_result)
71+
and more general
72+
[partition_map](https://docs.rs/itertools/0.12.1/itertools/trait.Itertools.html#method.partition_map)
73+
74+
> Partition a sequence of Results into one list of all the Ok elements and
75+
> another list of all the Err elements.
76+
77+
This is often useful, use `itertools` for this.
78+
5779
## License
5880

5981
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or

src/fail_all.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/// If at least one `Result` is an error, turn all of them into errors. Else, unwrap the `Result`s.
2+
///
3+
/// See [fail_all_vec] for an easier-to-understand version specialized for `Vec`.
4+
///
5+
/// [fail_all] is its lower-level implementation. You probably want to create
6+
/// a similar wrapper function instead of using [fail_all] directly.
7+
///
8+
/// ## Examples
9+
///
10+
/// ```rust
11+
/// # use multiple_errors::fail_all;
12+
/// # use multiple_errors::testing_prelude::*;
13+
/// #
14+
/// // Manually:
15+
///
16+
/// let Ok(ok) = fail_all([Ok(A), Ok(A)], |_: Result<_, ErrA>| ErrC) else {
17+
/// panic!();
18+
/// };
19+
/// assert_eq!(ok.collect::<Vec<_>>(), vec![A, A]);
20+
///
21+
/// // Or using a helper function for your container:
22+
///
23+
/// pub fn fail_all_3<T, E1, E2, F>(results: [Result<T, E1>; 3], f: F) -> Result<[T; 3], [E2; 3]>
24+
/// where
25+
/// F: FnMut(Result<T, E1>) -> E2,
26+
/// {
27+
/// fn collect_3<T>(mut iter: impl Iterator<Item = T>) -> [T; 3] {
28+
/// core::array::from_fn(|_| iter.next().expect("the iterator should have 3 elements"))
29+
/// }
30+
/// fail_all(results, f).map(collect_3).map_err(collect_3)
31+
/// }
32+
///
33+
/// let err = fail_all_3(
34+
/// [Ok(A), Err(ErrA), Ok(A)],
35+
/// |res| res.err().map(HighLevelErr::from).unwrap_or(HighLevelErr::B(ErrB))
36+
/// );
37+
/// assert_eq!(err, Err([ErrB.into(), ErrA.into(), ErrB.into()]));
38+
/// ```
39+
pub fn fail_all<I, T, E1, E2, F>(
40+
results: I,
41+
f: F,
42+
) -> Result<impl Iterator<Item = T>, impl Iterator<Item = E2>>
43+
where
44+
I: IntoIterator<Item = Result<T, E1>>,
45+
for<'a> &'a I: IntoIterator<Item = &'a Result<T, E1>>,
46+
F: FnMut(Result<T, E1>) -> E2,
47+
{
48+
if (&results).into_iter().any(Result::is_err) {
49+
return Err(results.into_iter().map(f));
50+
}
51+
Ok(results.into_iter().map(
52+
// This was going to be an `expect()`, but it requires an easily avoidable `E1: Debug`.
53+
|res| {
54+
res.ok()
55+
.unwrap_or_else(|| panic!("errors should be handled in the previous branch"))
56+
},
57+
))
58+
}
59+
60+
/// If at least one `Result` is an error, turn all of them into errors. Else, unwrap the `Result`s.
61+
///
62+
/// See [fail_all] for a more generic/low-level version that works with other
63+
/// input containers and doesn't `collect()`.
64+
///
65+
/// ## Examples
66+
///
67+
/// ```rust
68+
/// # use multiple_errors::fail_all_vec;
69+
/// # use multiple_errors::testing_prelude::*;
70+
/// #
71+
/// let err = fail_all_vec(
72+
/// vec![Ok(A), Err(ErrA), Ok(A)],
73+
/// |res| res.err().map(HighLevelErr::from).unwrap_or(HighLevelErr::B(ErrB))
74+
/// );
75+
/// assert_eq!(err, Err(vec![ErrB.into(), ErrA.into(), ErrB.into()]));
76+
///
77+
/// let ok = fail_all_vec(vec![Ok(A), Ok(A)], |_: Result<_, ErrA>| ErrC);
78+
/// assert_eq!(ok, Ok(vec![A, A]));
79+
/// ```
80+
pub fn fail_all_vec<T, E1, E2, F>(results: Vec<Result<T, E1>>, f: F) -> Result<Vec<T>, Vec<E2>>
81+
where
82+
F: FnMut(Result<T, E1>) -> E2,
83+
{
84+
fail_all(results, f)
85+
.map(Iterator::collect)
86+
.map_err(Iterator::collect)
87+
}

src/lib.rs

Lines changed: 4 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -3,111 +3,8 @@
33
#[doc(hidden)]
44
pub mod testing_prelude;
55

6-
/// Given a bunch of `Result`s, return a non-empty `Vec` of errors or unwrap
7-
/// all `Result`s and proceed.
8-
///
9-
/// ## Usage
10-
///
11-
/// The first statement should define a mutable `Vec` of errors, usually empty.
12-
///
13-
/// The statements that follow should define `Result` variables with the same
14-
/// (or convertible) error type.
15-
///
16-
/// Finally, the last statement should define a diverging branch with the
17-
/// following shape:
18-
///
19-
/// ```ignore
20-
/// if_there_are_errors {
21-
/// return /* the vec of errors or some other expression */;
22-
/// }
23-
/// ```
24-
///
25-
/// ## Examples
26-
///
27-
/// ```rust
28-
/// use multiple_errors::return_multiple_errors;
29-
/// use multiple_errors::testing_prelude::*;
30-
///
31-
/// fn a_b_c() -> Result<(A, B, C), Vec<HighLevelErr>> {
32-
/// return_multiple_errors!(
33-
/// let mut errors: Vec<HighLevelErr> = vec![];
34-
/// // Get some `Result`s:
35-
/// let a = action_a();
36-
/// let b = action_b();
37-
/// let c = action_c();
38-
/// if_there_are_errors {
39-
/// // Already converted and collected
40-
/// return Err(errors);
41-
/// }
42-
/// );
43-
/// // Already unwrapped
44-
/// Ok((a, b, c))
45-
/// }
46-
/// ```
47-
///
48-
/// ## Some nice details
49-
///
50-
/// - `Ok` types don't have to be the same
51-
/// - Errors don't have to be `Clone`
52-
#[macro_export]
53-
macro_rules! return_multiple_errors {
54-
(
55-
let mut $errors:ident: Vec<$E:ty> = $initial_errors:expr;
56-
$(
57-
let $var:ident = $expr:expr;
58-
)+
59-
if_there_are_errors {
60-
return $return_val:expr;
61-
}
62-
) => {
63-
$(
64-
let $var = $expr;
65-
)+
66-
let ( $( $var, )+ ) = match ( $( $var, )+ ) {
67-
( $( Ok($var), )+ ) => ( $( $var, )+ ),
68-
( $( $var, )+ ) => {
69-
let mut $errors: Vec<$E> = $initial_errors;
70-
$(
71-
if let Err(err) = $var {
72-
$errors.push(err.into());
73-
}
74-
)+
75-
return $return_val;
76-
}
77-
};
78-
};
79-
}
6+
mod fail_all;
7+
pub use fail_all::{fail_all, fail_all_vec};
808

81-
#[cfg(test)]
82-
mod return_multiple_errors {
83-
use super::*;
84-
use testing_prelude::*;
85-
86-
fn a_b(outcome_a: Outcome, outcome_b: Outcome) -> Result<(A, B), Vec<HighLevelErr>> {
87-
return_multiple_errors!(
88-
let mut errors: Vec<HighLevelErr> = vec![];
89-
let a = a(outcome_a);
90-
let b = b(outcome_b);
91-
if_there_are_errors {
92-
return Err(errors);
93-
}
94-
);
95-
Ok((a, b))
96-
}
97-
98-
#[test]
99-
fn both_errors() {
100-
assert_eq!(a_b(Fail, Fail), Err(vec![ErrA.into(), ErrB.into()]))
101-
}
102-
103-
#[test]
104-
fn either_error() {
105-
assert_eq!(a_b(Fail, Succeed), Err(vec![ErrA.into()]));
106-
assert_eq!(a_b(Succeed, Fail), Err(vec![ErrB.into()]))
107-
}
108-
109-
#[test]
110-
fn no_errors() {
111-
assert!(a_b(Succeed, Succeed).is_ok())
112-
}
113-
}
9+
// Implicitly does `#[macro_export]` of `return_multiple_errors!`
10+
mod return_multiple_errors;

src/return_multiple_errors.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/// Given a bunch of `Result`s, return a non-empty `Vec` of errors or unwrap
2+
/// all `Result`s and proceed.
3+
///
4+
/// ## Usage
5+
///
6+
/// The first statement should define a mutable `Vec` of errors, usually empty.
7+
///
8+
/// The statements that follow should define `Result` variables with the same
9+
/// (or convertible) error type.
10+
///
11+
/// Finally, the last statement should define a diverging branch with the
12+
/// following shape:
13+
///
14+
/// ```ignore
15+
/// if_there_are_errors {
16+
/// return /* the vec of errors or some other expression */;
17+
/// }
18+
/// ```
19+
///
20+
/// ## Examples
21+
///
22+
/// ```no_run
23+
/// # use multiple_errors::return_multiple_errors;
24+
/// # use multiple_errors::testing_prelude::*;
25+
/// #
26+
/// fn a_b_c() -> Result<(A, B, C), Vec<HighLevelErr>> {
27+
/// return_multiple_errors!(
28+
/// let mut errors: Vec<HighLevelErr> = vec![];
29+
/// // Get some `Result`s:
30+
/// let a = action_a();
31+
/// let b = action_b();
32+
/// let c = action_c();
33+
/// if_there_are_errors {
34+
/// // Already converted and collected
35+
/// return Err(errors);
36+
/// }
37+
/// );
38+
/// // Already unwrapped
39+
/// Ok((a, b, c))
40+
/// }
41+
/// ```
42+
///
43+
/// ## Some nice details
44+
///
45+
/// - `Ok` types don't have to be the same
46+
/// - Errors don't have to be `Clone`
47+
#[macro_export]
48+
macro_rules! return_multiple_errors {
49+
(
50+
let mut $errors:ident: Vec<$E:ty> = $initial_errors:expr;
51+
$(
52+
let $var:ident = $expr:expr;
53+
)+
54+
if_there_are_errors {
55+
return $return_val:expr;
56+
}
57+
) => {
58+
$(
59+
let $var = $expr;
60+
)+
61+
let ( $( $var, )+ ) = match ( $( $var, )+ ) {
62+
( $( Ok($var), )+ ) => ( $( $var, )+ ),
63+
( $( $var, )+ ) => {
64+
let mut $errors: Vec<$E> = $initial_errors;
65+
$(
66+
if let Err(err) = $var {
67+
$errors.push(err.into());
68+
}
69+
)+
70+
return $return_val;
71+
}
72+
};
73+
};
74+
}
75+
76+
#[cfg(test)]
77+
mod tests {
78+
use crate::testing_prelude::*;
79+
80+
fn a_b(outcome_a: Outcome, outcome_b: Outcome) -> Result<(A, B), Vec<HighLevelErr>> {
81+
return_multiple_errors!(
82+
let mut errors: Vec<HighLevelErr> = vec![];
83+
let a = a(outcome_a);
84+
let b = b(outcome_b);
85+
if_there_are_errors {
86+
return Err(errors);
87+
}
88+
);
89+
Ok((a, b))
90+
}
91+
92+
#[test]
93+
fn both_errors() {
94+
assert_eq!(a_b(Fail, Fail), Err(vec![ErrA.into(), ErrB.into()]))
95+
}
96+
97+
#[test]
98+
fn either_error() {
99+
assert_eq!(a_b(Fail, Succeed), Err(vec![ErrA.into()]));
100+
assert_eq!(a_b(Succeed, Fail), Err(vec![ErrB.into()]))
101+
}
102+
103+
#[test]
104+
fn no_errors() {
105+
assert!(a_b(Succeed, Succeed).is_ok())
106+
}
107+
}

0 commit comments

Comments
 (0)