use crate::Error;
use exn::Frame;
use smallvec::SmallVec;
struct FrameIter<'a> {
stack: SmallVec<[&'a Frame; 16]>,
}
impl<'a> FrameIter<'a> {
fn new(root: &'a Frame) -> Self {
let mut stack = SmallVec::new();
stack.push(root);
Self { stack }
}
}
impl<'a> Iterator for FrameIter<'a> {
type Item = &'a Frame;
fn next(&mut self) -> Option<Self::Item> {
let frame = self.stack.pop()?;
for child in frame.children() {
self.stack.push(child);
}
Some(frame)
}
}
pub fn count_frames(exn: &exn::Exn<Error>) -> usize {
FrameIter::new(exn.as_frame()).count()
}
pub fn count_errors(exn: &exn::Exn<Error>) -> usize {
count_frames(exn)
}
pub fn has_retryable(exn: &exn::Exn<Error>) -> bool {
FrameIter::new(exn.as_frame()).any(|frame| {
frame
.as_any()
.downcast_ref::<Error>()
.is_some_and(|e| e.is_retryable())
})
}
pub fn has_permanent(exn: &exn::Exn<Error>) -> bool {
FrameIter::new(exn.as_frame()).any(|frame| {
frame
.as_any()
.downcast_ref::<Error>()
.is_some_and(|e| e.is_permanent())
})
}
pub fn is_all_retryable(exn: &exn::Exn<Error>) -> bool {
FrameIter::new(exn.as_frame()).all(|frame| {
frame
.as_any()
.downcast_ref::<Error>()
.is_none_or(|e| e.is_retryable())
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ErrorKind;
use exn::{ResultExt, bail};
#[test]
fn test_count_frames() {
fn inner() -> crate::Result<()> {
bail!(Error::permanent(ErrorKind::NotFound, "inner"));
}
fn outer() -> crate::Result<()> {
inner().or_raise(|| Error::temporary(ErrorKind::Unexpected, "outer"))?;
Ok(())
}
let result = outer();
if let Err(exn) = result {
assert_eq!(count_frames(&exn), 2);
}
}
#[test]
fn test_count_errors() {
fn inner() -> crate::Result<()> {
bail!(Error::permanent(ErrorKind::NotFound, "inner"));
}
fn outer() -> crate::Result<()> {
inner().or_raise(|| Error::temporary(ErrorKind::Unexpected, "outer"))?;
Ok(())
}
let result = outer();
if let Err(exn) = result {
assert_eq!(count_errors(&exn), 2);
}
}
#[test]
fn test_has_retryable() {
fn inner() -> crate::Result<()> {
bail!(Error::temporary(ErrorKind::Timeout, "timeout"));
}
fn outer() -> crate::Result<()> {
inner().or_raise(|| Error::permanent(ErrorKind::Unexpected, "outer"))?;
Ok(())
}
let result = outer();
if let Err(exn) = result {
assert!(has_retryable(&exn));
}
}
#[test]
fn test_has_retryable_only_permanent() {
fn inner() -> crate::Result<()> {
bail!(Error::permanent(ErrorKind::NotFound, "not found"));
}
fn outer() -> crate::Result<()> {
inner().or_raise(|| Error::permanent(ErrorKind::Unexpected, "outer"))?;
Ok(())
}
let result = outer();
if let Err(exn) = result {
assert!(!has_retryable(&exn));
}
}
#[test]
fn test_has_permanent() {
fn inner() -> crate::Result<()> {
bail!(Error::permanent(ErrorKind::NotFound, "not found"));
}
fn outer() -> crate::Result<()> {
inner().or_raise(|| Error::temporary(ErrorKind::Timeout, "outer"))?;
Ok(())
}
let result = outer();
if let Err(exn) = result {
assert!(has_permanent(&exn));
}
}
#[test]
fn test_is_all_retryable() {
fn inner() -> crate::Result<()> {
bail!(Error::temporary(ErrorKind::Timeout, "timeout"));
}
fn outer() -> crate::Result<()> {
inner().or_raise(|| Error::temporary(ErrorKind::Unexpected, "outer"))?;
Ok(())
}
let result = outer();
if let Err(exn) = result {
assert!(is_all_retryable(&exn));
}
}
#[test]
fn test_deep_recursion_safety() {
let mut result: crate::Result<()> =
Err(Error::permanent(ErrorKind::NotFound, "base").raise());
for i in 0..1000 {
result =
result.or_raise(|| Error::temporary(ErrorKind::Unexpected, format!("wrap {i}")));
}
if let Err(exn) = result {
assert_eq!(count_frames(&exn), 1001);
assert!(has_retryable(&exn));
assert!(has_permanent(&exn));
} else {
panic!("expected error");
}
}
}