Repository: shepmaster/snafu (rust)

Issue:
Implement `iter_chain` on ErrorCompat
Copy over `iter_chain` from Rust unstable into `ErrorCompat`

Related: https://github.com/shepmaster/snafu/pull/186#discussion_r333283269
Depended by: https://github.com/shepmaster/snafu/pull/186

Pull request description:
Add ErrorCompat::iter_chain
Resolves #187.

Patch:
diff --git a/src/error_chain.rs b/src/error_chain.rs
new file mode 100644
index 0000000..51616e8
--- /dev/null
+++ b/src/error_chain.rs
@@ -0,0 +1,28 @@
+/// Backported version of the [`Chain`](std::error::Chain) struct,
+/// to versions of Rust lacking it.
+///
+/// Can be created via [`ErrorCompat::iter_chain`][crate::ErrorCompat::iter_chain].
+pub struct ChainCompat<'a> {
+    inner: Option<&'a dyn std::error::Error>,
+}
+
+impl<'a> ChainCompat<'a> {
+    /// Creates a new error chain iterator.
+    pub fn new(error: &'a dyn std::error::Error) -> Self {
+        ChainCompat { inner: Some(error) }
+    }
+}
+
+impl<'a> Iterator for ChainCompat<'a> {
+    type Item = &'a dyn std::error::Error;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match self.inner {
+            None => None,
+            Some(e) => {
+                self.inner = e.source();
+                Some(e)
+            }
+        }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 86928cc..d052653 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -226,6 +226,11 @@ pub use std::backtrace::Backtrace;
 #[cfg(feature = "futures")]
 pub mod futures;
 
+#[cfg(feature = "std")]
+mod error_chain;
+#[cfg(feature = "std")]
+pub use crate::error_chain::*;
+
 doc_comment::doc_comment! {
     include_str!("Snafu.md"),
     pub use snafu_derive::Snafu;
@@ -883,6 +888,20 @@ pub trait ErrorCompat {
     fn backtrace(&self) -> Option<&Backtrace> {
         None
     }
+
+    /// Returns an iterator for traversing the chain of errors,
+    /// starting with the current error
+    /// and continuing with recursive calls to `Error::source`.
+    ///
+    /// To omit the current error and only traverse its sources,
+    /// use `skip(1)`.
+    #[cfg(feature = "std")]
+    fn iter_chain(&self) -> ChainCompat
+    where
+        Self: AsErrorSource,
+    {
+        ChainCompat::new(self.as_error_source())
+    }
 }
 
 impl<'a, E> ErrorCompat for &'a E


Test patch:
diff --git a/tests/error_chain.rs b/tests/error_chain.rs
new file mode 100644
index 0000000..30f7fab
--- /dev/null
+++ b/tests/error_chain.rs
@@ -0,0 +1,57 @@
+use snafu::{ChainCompat, Snafu};
+use std::fmt::Debug;
+
+#[derive(Debug, Clone, Snafu)]
+enum LeafError {
+    #[snafu(display("User ID {} is invalid", user_id))]
+    InvalidUser { user_id: i32 },
+    #[snafu(display("no user available"))]
+    MissingUser,
+}
+
+#[derive(Debug, Clone, Snafu)]
+enum MiddleError {
+    #[snafu(display("failed to check the user"))]
+    CheckUser { source: LeafError },
+}
+
+#[derive(Debug, Clone, Snafu)]
+enum Error {
+    #[snafu(display("access control failure"))]
+    AccessControl { source: MiddleError },
+}
+
+#[track_caller]
+fn assert_eq_debug(a: impl Debug, b: impl Debug) {
+    assert_eq!(format!("{:?}", a), format!("{:?}", b));
+}
+
+#[test]
+fn chain_compat_iterates() {
+    use snafu::IntoError;
+
+    let bottom_error = InvalidUserSnafu { user_id: 12 }.build();
+    let middle_error = CheckUserSnafu.into_error(bottom_error.clone());
+    let error = AccessControlSnafu.into_error(middle_error.clone());
+
+    let errors: Vec<_> = ChainCompat::new(&error).collect();
+
+    assert_eq_debug(&errors[0], &error);
+    assert_eq_debug(&errors[1], &middle_error);
+    assert_eq_debug(&errors[2], &bottom_error);
+}
+
+#[test]
+fn errorcompat_chain_iterates() {
+    use snafu::{ErrorCompat, IntoError};
+
+    let bottom_error = InvalidUserSnafu { user_id: 12 }.build();
+    let middle_error = CheckUserSnafu.into_error(bottom_error.clone());
+    let error = AccessControlSnafu.into_error(middle_error.clone());
+
+    let errors: Vec<_> = ErrorCompat::iter_chain(&error).collect();
+
+    assert_eq_debug(&errors[0], &error);
+    assert_eq_debug(&errors[1], &middle_error);
+    assert_eq_debug(&errors[2], &bottom_error);
+}