use core::error::Error as StdError;
#[derive(Clone, Debug)]
pub struct Chain<'a> {
next: Option<&'a (dyn StdError + 'static)>,
}
impl<'a> Chain<'a> {
#[inline]
fn new(head: &'a (dyn StdError + 'static)) -> Self {
Self { next: Some(head) }
}
}
impl<'a> Iterator for Chain<'a> {
type Item = &'a (dyn StdError + 'static);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let current = self.next?;
self.next = current.source();
Some(current)
}
}
impl core::iter::FusedIterator for Chain<'_> {}
pub trait ErrorChainExt {
fn chain(&self) -> Chain<'_>;
fn root_cause(&self) -> &(dyn StdError + 'static);
}
#[inline]
fn root_cause_of<'a>(head: &'a (dyn StdError + 'static)) -> &'a (dyn StdError + 'static) {
let mut cause = head;
while let Some(source) = cause.source() {
cause = source;
}
cause
}
impl<E: StdError + 'static> ErrorChainExt for E {
#[inline]
fn chain(&self) -> Chain<'_> {
Chain::new(self)
}
#[inline]
fn root_cause(&self) -> &(dyn StdError + 'static) {
root_cause_of(self)
}
}
impl ErrorChainExt for dyn StdError + 'static {
#[inline]
fn chain(&self) -> Chain<'_> {
Chain::new(self)
}
#[inline]
fn root_cause(&self) -> &(dyn StdError + 'static) {
root_cause_of(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt;
#[derive(Debug)]
struct Leaf;
impl fmt::Display for Leaf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("leaf")
}
}
impl StdError for Leaf {}
#[derive(Debug)]
struct Layer {
label: &'static str,
source: Box<dyn StdError + 'static>,
}
impl fmt::Display for Layer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.label)
}
}
impl StdError for Layer {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(&*self.source)
}
}
fn three_deep() -> Layer {
Layer {
label: "top",
source: Box::new(Layer {
label: "middle",
source: Box::new(Leaf),
}),
}
}
#[test]
fn chain_starts_at_self_then_walks_sources() {
let err = three_deep();
let labels: Vec<String> = err.chain().map(ToString::to_string).collect();
assert_eq!(labels, ["top", "middle", "leaf"]);
}
#[test]
fn chain_on_leaf_yields_only_self() {
let leaf = Leaf;
assert_eq!(leaf.chain().count(), 1);
assert_eq!(leaf.chain().next().unwrap().to_string(), "leaf");
}
#[test]
fn chain_over_dyn_error_from_source_starts_at_that_source() {
let err = three_deep();
let source = err.source().expect("has a source");
let labels: Vec<String> = source.chain().map(ToString::to_string).collect();
assert_eq!(labels, ["middle", "leaf"]);
}
#[test]
fn root_cause_is_the_deepest_link() {
let err = three_deep();
let root = err.root_cause();
assert_eq!(root.to_string(), "leaf");
let last = err.chain().last().expect("non-empty chain");
assert!(std::ptr::eq(
std::ptr::from_ref(root).cast::<()>(),
std::ptr::from_ref(last).cast::<()>(),
));
}
#[test]
fn root_cause_of_leaf_is_itself() {
let leaf = Leaf;
assert_eq!(leaf.root_cause().to_string(), "leaf");
}
}