error_iter/
lib.rs

1//! Iterators over `std::error::Error` sources on stable Rust.
2//!
3//! ```
4//! use error_iter::ErrorIter as _;
5//! use std::io::{Error as IoError, ErrorKind};
6//! use thiserror::Error;
7//!
8//! #[derive(Debug, Error)]
9//! enum Error {
10//!     #[error("I/O Error")]
11//!     Io(#[from] IoError),
12//!
13//!     #[error("Unknown error")]
14//!     Unknown,
15//! }
16//!
17//! fn do_something() {
18//!     let error = Error::from(IoError::new(ErrorKind::Other, "oh no!"));
19//!
20//!     eprintln!("Error: {}", error);
21//!     for source in error.sources().skip(1) {
22//!         eprintln!("  Caused by: {}", source);
23//!     }
24//! }
25//! ```
26
27#![deny(clippy::all)]
28#![deny(clippy::pedantic)]
29#![forbid(unsafe_code)]
30
31pub struct ErrorIterator<'a> {
32    inner: Option<&'a (dyn std::error::Error + 'static)>,
33}
34
35impl<'a> Iterator for ErrorIterator<'a> {
36    type Item = &'a (dyn std::error::Error + 'static);
37
38    fn next(&mut self) -> Option<Self::Item> {
39        if let Some(error) = self.inner.take() {
40            self.inner = error.source();
41            return Some(error);
42        }
43
44        None
45    }
46}
47
48/// Implement this trait on your error types for free iterators over their sources!
49///
50/// The default implementation provides iterators for any type that implements `std::error::Error`.
51pub trait ErrorIter: std::error::Error + Sized + 'static {
52    /// Create an iterator over the error and its recursive sources.
53    ///
54    /// ```
55    /// use error_iter::ErrorIter as _;
56    /// use thiserror::Error;
57    ///
58    /// #[derive(Debug, Error)]
59    /// enum Error {
60    ///     #[error("Nested error: {0}")]
61    ///     Nested(#[source] Box<Error>),
62    ///
63    ///     #[error("Leaf error")]
64    ///     Leaf,
65    /// }
66    ///
67    /// let error = Error::Nested(Box::new(Error::Leaf));
68    ///
69    /// let mut iter = error.sources();
70    ///
71    /// assert_eq!("Nested error: Leaf error".to_string(), iter.next().unwrap().to_string());
72    /// assert_eq!("Leaf error".to_string(), iter.next().unwrap().to_string());
73    /// assert!(iter.next().is_none());
74    /// assert!(iter.next().is_none());
75    /// ```
76    fn sources(&self) -> ErrorIterator {
77        ErrorIterator { inner: Some(self) }
78    }
79}
80
81impl<T> ErrorIter for T where T: std::error::Error + Sized + 'static {}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use thiserror::Error;
87
88    #[derive(Debug, Error)]
89    enum Error {
90        #[error("Nested error: {0}")]
91        Nested(#[source] Box<Error>),
92
93        #[error("Leaf error")]
94        Leaf,
95    }
96
97    #[test]
98    fn iter_sources_ok() {
99        let error = Error::Nested(Box::new(Error::Nested(Box::new(Error::Leaf))));
100
101        let mut iter = error.sources();
102
103        assert_eq!(
104            "Nested error: Nested error: Leaf error".to_string(),
105            iter.next().unwrap().to_string()
106        );
107        assert_eq!(
108            "Nested error: Leaf error".to_string(),
109            iter.next().unwrap().to_string()
110        );
111        assert_eq!("Leaf error".to_string(), iter.next().unwrap().to_string());
112        assert!(iter.next().is_none());
113        assert!(iter.next().is_none());
114    }
115}