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}