Skip to content

Commit b65901e

Browse files
committed
Add Error::chain method to return iterator over nested errors
1 parent 91fe02d commit b65901e

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

src/error.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,14 @@ impl Error {
365365
}
366366
}
367367

368+
/// An iterator over the chain of nested errors wrapped by this Error.
369+
pub fn chain(&self) -> impl Iterator<Item = &(dyn StdError + 'static)> {
370+
Chain {
371+
root: self,
372+
current: None,
373+
}
374+
}
375+
368376
pub(crate) fn bad_self_argument(to: &str, cause: Error) -> Self {
369377
Error::BadArgument {
370378
to: Some(to.to_string()),
@@ -487,3 +495,44 @@ impl serde::de::Error for Error {
487495
Self::DeserializeError(msg.to_string())
488496
}
489497
}
498+
499+
struct Chain<'a> {
500+
root: &'a Error,
501+
current: Option<&'a (dyn StdError + 'static)>,
502+
}
503+
504+
impl<'a> Iterator for Chain<'a> {
505+
type Item = &'a (dyn StdError + 'static);
506+
507+
fn next(&mut self) -> Option<Self::Item> {
508+
loop {
509+
let error: Option<&dyn StdError> = match self.current {
510+
None => {
511+
self.current = Some(self.root);
512+
self.current
513+
}
514+
Some(current) => match current.downcast_ref::<Error>()? {
515+
Error::BadArgument { cause, .. }
516+
| Error::CallbackError { cause, .. }
517+
| Error::WithContext { cause, .. } => {
518+
self.current = Some(&**cause);
519+
self.current
520+
}
521+
Error::ExternalError(err) => {
522+
self.current = Some(&**err);
523+
self.current
524+
}
525+
_ => None,
526+
},
527+
};
528+
529+
// Skip `ExternalError` as it only wraps the underlying error
530+
// without meaningful context
531+
if let Some(Error::ExternalError(_)) = error?.downcast_ref::<Error>() {
532+
continue;
533+
}
534+
535+
return self.current;
536+
}
537+
}
538+
}

tests/error.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,29 @@ fn test_error_context() -> Result<()> {
4646

4747
Ok(())
4848
}
49+
50+
#[test]
51+
fn test_error_chain() -> Result<()> {
52+
let lua = Lua::new();
53+
54+
// Check that `Error::ExternalError` creates a chain with a single element
55+
let io_err = io::Error::new(io::ErrorKind::Other, "other");
56+
assert_eq!(Error::external(io_err).chain().count(), 1);
57+
58+
let func = lua.create_function(|_, ()| {
59+
let err = Error::external(io::Error::new(io::ErrorKind::Other, "other")).context("io error");
60+
Err::<(), _>(err)
61+
})?;
62+
let err = func.call::<()>(()).err().unwrap();
63+
assert_eq!(err.chain().count(), 3);
64+
for (i, err) in err.chain().enumerate() {
65+
match i {
66+
0 => assert!(matches!(err.downcast_ref(), Some(Error::CallbackError { .. }))),
67+
1 => assert!(matches!(err.downcast_ref(), Some(Error::WithContext { .. }))),
68+
2 => assert!(matches!(err.downcast_ref(), Some(io::Error { .. }))),
69+
_ => unreachable!(),
70+
}
71+
}
72+
73+
Ok(())
74+
}

0 commit comments

Comments
 (0)