Add Error::chain method to return iterator over nested errors

This commit is contained in:
Alex Orlenko
2024-09-24 22:48:19 +01:00
parent 91fe02da45
commit b65901e444
2 changed files with 75 additions and 0 deletions
+49
View File
@@ -365,6 +365,14 @@ impl Error {
}
}
/// An iterator over the chain of nested errors wrapped by this Error.
pub fn chain(&self) -> impl Iterator<Item = &(dyn StdError + 'static)> {
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<Self::Item> {
loop {
let error: Option<&dyn StdError> = match self.current {
None => {
self.current = Some(self.root);
self.current
}
Some(current) => match current.downcast_ref::<Error>()? {
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::<Error>() {
continue;
}
return self.current;
}
}
}
+26
View File
@@ -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(())
}