error_trees/
lib.rs

1//! This crate provides a convenient way of handling multiple
2//! errors.
3//!
4//! Instead of returning early with the first error in your app,
5//! it helps you store the errors that occur in a tree structure.
6//! It lets you label the errors, and flatten then into a list
7//! to present to the user.
8use itertools::Itertools;
9
10/// The error Tree structure.
11///
12/// - `L` is the Label type.
13/// - `E` is the inner Error type. It can be an error enum (from the thiserror package).
14#[derive(Debug)]
15pub enum ErrorTree<L, E> {
16    /// Stores your single error type.
17    Leaf(E),
18    /// Adds a label to the subtree.
19    Edge(L, Box<ErrorTree<L, E>>),
20    /// Groups multiple subtrees at the same level.
21    Vec(Vec<ErrorTree<L, E>>),
22}
23
24impl<L, E> ErrorTree<L, E> {
25    /**
26    Creates a `Leaf` tree from an `error`.
27
28    ```rust
29    # use itertools::*;
30    # use error_trees::*;
31    struct Error(String);
32    let error_tree = ErrorTree::<&'static str, _>::leaf(Error("error".into()));
33    ```
34    */
35    pub fn leaf(error: E) -> Self {
36        Self::Leaf(error)
37    }
38}
39
40/// The flattened error type
41#[derive(Debug)]
42pub struct FlatError<L, E> {
43    /// The path from the leaf to the root of the tree.
44    pub path: Vec<L>,
45    /// The error
46    pub error: E,
47}
48
49impl<L, E> ErrorTree<L, E>
50where
51    L: Clone,
52{
53    /**
54    Flattens the error tree in a `Vec` of `FlatError`s.
55
56    ```rust
57    # use itertools::*;
58    # use error_trees::*;
59    #[derive(Debug)]
60    struct Error(String);
61
62    let error_1 = ErrorTree::leaf(Error("error1".into())).with_label("label1");
63    let error_2 = ErrorTree::leaf(Error("error2".into())).with_label("label2");
64
65    let errors = vec![error_1, error_2];
66
67    let tree: ErrorTree<&'static str, Error> = errors.into();
68    let tree = tree.with_label("parent_label");
69
70    let flat_errors = tree.flatten_tree();
71
72    assert!(
73        matches!(
74            &flat_errors[..],
75            [
76                FlatError {
77                    path: path1,
78                    error: Error(error1),
79                },
80                FlatError {
81                    path: path2,
82                    error: Error(error2),
83                },
84            ]
85            if path1 == &vec!["label1", "parent_label"]
86            && path2 == &vec!["label2", "parent_label"]
87            && error1 == "error1"
88            && error2 == "error2"
89        ),
90        "unexpected: {:#?}",
91        flat_errors
92    );
93    ```
94    */
95    pub fn flatten_tree(self) -> Vec<FlatError<L, E>> {
96        match self {
97            ErrorTree::Leaf(error) => vec![FlatError {
98                path: Vec::new(),
99                error,
100            }],
101            ErrorTree::Edge(label, tree) => {
102                let mut flat_errors = tree.flatten_tree();
103                for flat in &mut flat_errors {
104                    flat.path.push(label.clone());
105                }
106                flat_errors
107            }
108            ErrorTree::Vec(errors) => errors
109                .into_iter()
110                .flat_map(|tree| tree.flatten_tree())
111                .collect_vec(),
112        }
113    }
114}
115
116/// Adds a label to the error tree.
117pub trait IntoErrorTree<L, E> {
118    /**
119    Adds a `label` to an error tree.
120    ```rust
121    # use error_trees::*;
122    struct Error(String);
123    let leaf = ErrorTree::leaf(Error("a regular error".into()));
124    let labeled_leaf = leaf.with_label("the label");
125    ```
126    */
127    fn with_label(self, label: L) -> ErrorTree<L, E>;
128}
129
130impl<L, E> IntoErrorTree<L, E> for E
131where
132    E: Into<ErrorTree<L, E>>,
133{
134    fn with_label(self, label: L) -> ErrorTree<L, E> {
135        ErrorTree::Edge(label, Box::new(self.into()))
136    }
137}
138
139impl<L, E> IntoErrorTree<L, E> for ErrorTree<L, E> {
140    fn with_label(self, label: L) -> ErrorTree<L, E> {
141        ErrorTree::Edge(label, Box::new(self))
142    }
143}
144
145impl<L, E> From<Vec<ErrorTree<L, E>>> for ErrorTree<L, E> {
146    fn from(subtrees: Vec<ErrorTree<L, E>>) -> Self {
147        ErrorTree::Vec(subtrees)
148    }
149}
150
151impl<L, E> From<Vec<E>> for ErrorTree<L, E>
152where
153    E: IntoErrorTree<L, E>,
154    ErrorTree<L, E>: From<E>,
155{
156    fn from(errors: Vec<E>) -> Self {
157        ErrorTree::Vec(errors.into_iter().map(|x| x.into()).collect_vec())
158    }
159}
160
161/// Convenience trait to convert tuple of `(success: T, errors: Vec<E>)` to a `result : Result<T, ErrorTree<L, E>>`
162pub trait IntoResult<T, E> {
163    /**
164    Turns `self` into a `Result`.
165
166    For tuples of `(success: T, errors: Vec<E>)`:
167    - It checks if `errors` is empty.
168        - If true, it will return `Ok(success)`.
169        - Otherwise it will return `Err(errors)`.
170
171    ```rust
172    # use itertools::*;
173    # use error_trees::*;
174    struct Error(String);
175    impl<L> From<Error> for ErrorTree<L, Error> {
176        fn from(e: Error) -> Self {
177            Self::leaf(e)
178        }
179    }
180
181    let result1: Result<(), _> = Err(Error("first".into())).label_error("one");
182    let result2: Result<(), _> = Err(Error("second".into())).label_error("two");
183
184    let final_result: Result<Vec<_>, ErrorTree<_, _>> = vec![result1, result2]
185        .into_iter()
186        .partition_result()
187        .into_result();
188    ```
189
190    For `errors: Vec<E>`:
191    - It checks if `errors` is empty.
192    - If true, it will return `Ok(())`.
193    - Otherwise, it will return `Err(errors)`.
194
195    Since the trait is implemented for tuples of `(success: T, errors: Vec<E>)`
196    and for `Vec<E>`, it works well with `partition_result` from the `itertools` crate!
197
198    ```rust
199    # use itertools::*;
200    # use error_trees::*;
201    struct Error(String);
202
203    let error1 = ErrorTree::leaf(Error("first".into())).with_label("one");
204    let error2 = ErrorTree::leaf(Error("second".into())).with_label("two");
205
206    let final_result: Result<_, ErrorTree<_, _>> = vec![error1, error2]
207        .into_result();
208    ```
209    */
210    fn into_result(self) -> Result<T, E>;
211}
212
213impl<T, IE, E> IntoResult<T, E> for (T, Vec<IE>)
214where
215    Vec<IE>: Into<E>,
216{
217    fn into_result(self) -> Result<T, E> {
218        let (oks, errs) = self;
219        if errs.is_empty() {
220            Ok(oks)
221        } else {
222            Err(errs.into())
223        }
224    }
225}
226
227impl<IE, E> IntoResult<(), E> for Vec<IE>
228where
229    Vec<IE>: Into<E>,
230{
231    fn into_result(self) -> Result<(), E> {
232        if self.is_empty() {
233            Ok(())
234        } else {
235            Err(self.into())
236        }
237    }
238}
239
240/// Convenience trait to label errors within a `Result`.
241pub trait LabelResult<T, L, E> {
242    /**
243    Maps a label to the `ErrorTree` within the result.
244
245    ```rust
246    # use itertools::*;
247    # use error_trees::*;
248    struct Error(String);
249    let result: Result<(), ErrorTree<&'static str, Error>> = Ok(());
250    let labeled_result = result.label_error("the label");
251    ```
252    */
253    fn label_error(self, label: L) -> Result<T, ErrorTree<L, E>>;
254}
255
256impl<T, L, E> LabelResult<T, L, E> for Result<T, E>
257where
258    ErrorTree<L, E>: From<E>,
259{
260    fn label_error(self, label: L) -> Result<T, ErrorTree<L, E>> {
261        self.map_err(|e| {
262            let tree: ErrorTree<L, E> = e.into();
263            tree.with_label(label)
264        })
265    }
266}
267
268impl<T, L, E> LabelResult<T, L, E> for Result<T, ErrorTree<L, E>> {
269    fn label_error(self, label: L) -> Result<T, ErrorTree<L, E>> {
270        self.map_err(|tree| tree.with_label(label))
271    }
272}
273
274pub trait FlattenResultErrors<T, L, E> {
275    fn flatten_results(self) -> Result<T, Vec<FlatError<L, E>>>;
276}
277
278impl<T, L, E> FlattenResultErrors<T, L, E> for Result<T, ErrorTree<L, E>>
279where
280    L: Clone,
281{
282    fn flatten_results(self) -> Result<T, Vec<FlatError<L, E>>> {
283        self.map_err(|tree| tree.flatten_tree())
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use itertools::Itertools;
290
291    use super::*;
292
293    fn faulty(error: &str) -> Result<(), Error> {
294        Err(Error(error.into()))
295    }
296
297    #[test]
298    fn can_build_tree_from_vec_of_results() {
299        let result_1 = faulty("error1").map_err(|e| e.with_label("label1"));
300        let result_2 = faulty("error2").map_err(|e| e.with_label("label2"));
301
302        let (_, errors): (Vec<_>, Vec<_>) = vec![result_1, result_2].into_iter().partition_result();
303
304        let tree: ErrorTree<&'static str, Error> = errors.into();
305        let tree = tree.with_label("parent_label");
306
307        let flat_errors = tree.flatten_tree();
308
309        assert!(
310            matches!(
311                &flat_errors[..],
312                [
313                    FlatError {
314                        path: path1,
315                        error: Error(error1),
316                    },
317                    FlatError {
318                        path: path2,
319                        error: Error(error2),
320                    },
321                ]
322                if path1 == &vec!["label1", "parent_label"]
323                && path2 == &vec!["label2", "parent_label"]
324                && error1 == "error1"
325                && error2 == "error2"
326            ),
327            "unexpected: {:#?}",
328            flat_errors
329        );
330    }
331
332    #[test]
333    fn can_call_into_result_from_vec_of_results() {
334        let result_1 = faulty("error1").map_err(|e| e.with_label("label1"));
335        let result_2 = faulty("error2").map_err(|e| e.with_label("label2"));
336
337        let result: Result<Vec<()>, ErrorTree<_, _>> = vec![result_1, result_2]
338            .into_iter()
339            .partition_result()
340            .into_result();
341
342        let flat_result = result.map_err(|e| e.flatten_tree());
343
344        let flat_errors = flat_result.unwrap_err();
345
346        assert!(
347            matches!(
348                &flat_errors[..],
349                [
350                    FlatError {
351                        path: path1,
352                        error: Error(error1),
353                    },
354                    FlatError {
355                        path: path2,
356                        error: Error(error2),
357                    },
358                ]
359                if path1 == &vec!["label1"]
360                && path2 == &vec!["label2"]
361                && error1 == "error1"
362                && error2 == "error2"
363            ),
364            "unexpected: {:#?}",
365            flat_errors
366        );
367    }
368
369    #[test]
370    fn can_call_into_result_from_vec_of_errors() {
371        let error1 = Error("error1".into()).with_label("label1");
372        let error2 = Error("error2".into()).with_label("label2");
373
374        let result: Result<_, ErrorTree<_, _>> = vec![error1, error2].into_result();
375
376        let flat_result = result.map_err(|e| e.flatten_tree());
377
378        let flat_errors = flat_result.unwrap_err();
379
380        assert!(
381            matches!(
382                &flat_errors[..],
383                [
384                    FlatError {
385                        path: path1,
386                        error: Error(error1),
387                    },
388                    FlatError {
389                        path: path2,
390                        error: Error(error2),
391                    },
392                ]
393                if path1 == &vec!["label1"]
394                && path2 == &vec!["label2"]
395                && error1 == "error1"
396                && error2 == "error2"
397            ),
398            "unexpected: {:#?}",
399            flat_errors
400        );
401    }
402
403    // For the README
404
405    // The error type
406    #[derive(Debug)]
407    struct Error(String);
408
409    impl<L> From<Error> for ErrorTree<L, Error> {
410        fn from(e: Error) -> Self {
411            Self::leaf(e)
412        }
413    }
414
415    // A function that returns an error
416    fn faulty_function() -> Result<(), Error> {
417        Err(Error("error".into()))
418    }
419
420    // A function that returns more than one error
421    fn parent_function() -> Result<Vec<()>, ErrorTree<&'static str, Error>> {
422        let result1 = faulty_function().label_error("first faulty");
423        let result2 = faulty_function().label_error("second faulty");
424
425        let result: Result<_, ErrorTree<_, _>> = vec![result1, result2]
426            .into_iter()
427            .partition_result::<Vec<_>, Vec<_>, _, _>()
428            .into_result();
429        result.label_error("parent function")
430    }
431
432    // your main function
433    #[test]
434    fn main_function() {
435        let result = parent_function();
436
437        let flat_results = result.flatten_results();
438        let flat_errors: Vec<FlatError<&str, Error>> = flat_results.unwrap_err();
439
440        assert!(
441            matches!(
442                &flat_errors[..],
443                [
444                    FlatError {
445                        path: path1,
446                        error: Error(_),
447                    },
448                    FlatError {
449                        path: path2,
450                        error: Error(_),
451                    },
452                ]
453                if path1 == &vec!["first faulty", "parent function"]
454                && path2 == &vec!["second faulty", "parent function"]
455            ),
456            "unexpected: {:#?}",
457            flat_errors
458        );
459    }
460}