From b65901e444fdfc01dd67612229ca489db4efe193 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Tue, 24 Sep 2024 22:48:19 +0100 Subject: [PATCH] Add `Error::chain` method to return iterator over nested errors --- src/error.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/error.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/error.rs b/src/error.rs index 59a80b2..452bf1a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -365,6 +365,14 @@ impl Error { } } + /// An iterator over the chain of nested errors wrapped by this Error. + pub fn chain(&self) -> impl Iterator { + Chain { + root: self, + current: None, + } + } + pub(crate) fn bad_self_argument(to: &str, cause: Error) -> Self { Error::BadArgument { to: Some(to.to_string()), @@ -487,3 +495,44 @@ impl serde::de::Error for Error { Self::DeserializeError(msg.to_string()) } } + +struct Chain<'a> { + root: &'a Error, + current: Option<&'a (dyn StdError + 'static)>, +} + +impl<'a> Iterator for Chain<'a> { + type Item = &'a (dyn StdError + 'static); + + fn next(&mut self) -> Option { + loop { + let error: Option<&dyn StdError> = match self.current { + None => { + self.current = Some(self.root); + self.current + } + Some(current) => match current.downcast_ref::()? { + Error::BadArgument { cause, .. } + | Error::CallbackError { cause, .. } + | Error::WithContext { cause, .. } => { + self.current = Some(&**cause); + self.current + } + Error::ExternalError(err) => { + self.current = Some(&**err); + self.current + } + _ => None, + }, + }; + + // Skip `ExternalError` as it only wraps the underlying error + // without meaningful context + if let Some(Error::ExternalError(_)) = error?.downcast_ref::() { + continue; + } + + return self.current; + } + } +} diff --git a/tests/error.rs b/tests/error.rs index 922b83c..ed1dcc2 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -46,3 +46,29 @@ fn test_error_context() -> Result<()> { Ok(()) } + +#[test] +fn test_error_chain() -> Result<()> { + let lua = Lua::new(); + + // Check that `Error::ExternalError` creates a chain with a single element + let io_err = io::Error::new(io::ErrorKind::Other, "other"); + assert_eq!(Error::external(io_err).chain().count(), 1); + + let func = lua.create_function(|_, ()| { + let err = Error::external(io::Error::new(io::ErrorKind::Other, "other")).context("io error"); + Err::<(), _>(err) + })?; + let err = func.call::<()>(()).err().unwrap(); + assert_eq!(err.chain().count(), 3); + for (i, err) in err.chain().enumerate() { + match i { + 0 => assert!(matches!(err.downcast_ref(), Some(Error::CallbackError { .. }))), + 1 => assert!(matches!(err.downcast_ref(), Some(Error::WithContext { .. }))), + 2 => assert!(matches!(err.downcast_ref(), Some(io::Error { .. }))), + _ => unreachable!(), + } + } + + Ok(()) +}