use std::collections::VecDeque;
use std::error::Error;
use std::fmt;
use std::marker::PhantomData;
use std::ops::Deref;
use std::panic::Location;
use crate::concrete::chain::ErrorHandle;
use crate::{Metadata, types::ChainedError, write_location};
pub struct Exn<E: std::error::Error + Send + Sync + 'static = Untyped> {
frame: Box<Frame>,
phantom: PhantomData<E>,
}
#[track_caller]
#[expect(
clippy::unnecessary_box_returns,
reason = "erasure retains the existing frame allocation"
)]
pub(super) fn into_frame<E: Error + Send + Sync + 'static>(error: E) -> Box<Frame> {
#[cfg(any(feature = "tree-error", not(feature = "auto-chain-error")))]
{
crate::ErrorExt::raise(error).into_frame()
}
#[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
{
Exn::new(error).frame
}
}
impl<E: Error + Send + Sync + 'static> From<E> for Exn<E> {
#[track_caller]
fn from(error: E) -> Self {
Exn::new(error)
}
}
impl<E: Error + Send + Sync + 'static> Exn<E> {
#[track_caller]
pub fn new(error: E) -> Self {
let frame = Frame {
source: FrameSource {
error: Box::new(error),
location: Location::caller(),
children: Vec::new(),
},
};
Self {
frame: Box::new(frame),
phantom: PhantomData,
}
}
#[track_caller]
pub(super) fn with_cause(cause: impl Error + Send + Sync + 'static, error: E) -> Self {
let cause = into_frame(cause);
let mut exn = Exn::new(error);
exn.frame.source.children.push(*cause);
exn
}
#[track_caller]
pub fn raise_all<T, I>(children: I, err: E) -> Self
where
T: Error + Send + Sync + 'static,
I: IntoIterator,
I::Item: Into<Exn<T>>,
{
let mut new_exn = Exn::new(err);
for exn in children {
let exn = exn.into();
new_exn.frame.source.children.push(*exn.frame);
}
new_exn
}
#[track_caller]
pub fn raise<T: Error + Send + Sync + 'static>(self, err: T) -> Exn<T> {
let mut new_exn = Exn::new(err);
new_exn.frame.source.children.push(*self.frame);
new_exn
}
#[track_caller]
pub fn chain<T: Error + Send + Sync + 'static>(mut self, err: impl Into<Exn<T>>) -> Exn<E> {
let err = err.into();
self.frame.source.children.push(*err.frame);
self
}
#[track_caller]
pub fn chain_all<T, I>(mut self, errors: I) -> Exn<E>
where
T: Error + Send + Sync + 'static,
I: IntoIterator,
I::Item: Into<Exn<T>>,
{
for err in errors {
let err = err.into();
self.frame.source.children.push(*err.frame);
}
self
}
pub fn drain_children(&mut self) -> impl Iterator<Item = Exn> + '_ {
self.frame.source.children.drain(..).map(Exn::from)
}
pub fn erased(self) -> Exn {
Exn::from_boxed_frame(self.frame)
}
pub fn error(&self) -> &E {
self.frame
.source
.error
.downcast_ref()
.expect("the owned frame always matches the compile-time error type")
}
pub fn into_box(self) -> Box<E> {
match self.frame.source.error.downcast() {
Ok(err) => err,
Err(_) => unreachable!("The type in the frame is always the type of this instance"),
}
}
pub fn into_inner(self) -> E {
*self.into_box()
}
pub fn into_error(self) -> crate::Error {
self.into()
}
pub fn into_chain(self) -> ChainedError {
self.into()
}
pub fn frame(&self) -> &Frame {
&self.frame
}
pub fn iter(&self) -> impl Iterator<Item = &Frame> {
self.frame().iter_frames()
}
pub fn iter_errors(&self) -> impl Iterator<Item = &(dyn Error + 'static)> + '_ {
self.frame.iter_errors_with_locations().map(|source| source.error())
}
pub fn metadata(&self) -> impl Iterator<Item = &Metadata> + '_ {
self.iter_errors()
.filter_map(|error| error.downcast_ref::<crate::Message>())
.map(|error| &error.values)
.filter(|values| !values.is_empty())
}
pub fn metadata_merged(&self) -> Metadata {
let mut merged = Metadata::new();
for values in self.metadata() {
merged.extend(values.iter().map(|(key, value)| (key.clone(), value.clone())));
}
merged
}
pub fn probable_cause(&self) -> &(dyn Error + 'static) {
self.frame.probable_cause().unwrap_or_else(|| self.frame.error())
}
pub fn downcast_any_ref<T: Error + 'static>(&self) -> Option<&T> {
self.iter_errors().find_map(|error| error.downcast_ref())
}
}
impl<E> Deref for Exn<E>
where
E: Error + Send + Sync + 'static,
{
type Target = E;
fn deref(&self) -> &Self::Target {
self.error()
}
}
impl<E: Error + Send + Sync + 'static> fmt::Debug for Exn<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_frame_recursive(f, self.frame(), ErrorMode::Display)
}
}
impl fmt::Debug for Frame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_frame_recursive(f, self, ErrorMode::Display)
}
}
#[derive(Copy, Clone)]
pub(crate) enum ErrorMode {
Display,
Debug,
}
impl ErrorMode {
pub(crate) fn fmt(self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(io) = error.downcast_ref::<std::io::Error>()
&& io.get_ref().is_some()
{
return write!(f, "I/O error ({:?})", io.kind());
}
match self {
ErrorMode::Display => write!(f, "{error}"),
ErrorMode::Debug => write!(f, "{error:?}"),
}
}
}
fn write_frame_recursive(f: &mut fmt::Formatter<'_>, frame: &Frame, err_mode: ErrorMode) -> fmt::Result {
if crate::error::is_transparent_marker(frame.error()) {
let children = ErrorNode::Frame(frame).children();
if !children.is_empty() {
for (index, child) in children.into_iter().enumerate() {
if index != 0 {
writeln!(f)?;
}
write_error_node_recursive(f, child, &mut Vec::new(), &mut 0, err_mode, true)?;
}
return Ok(());
}
}
write_error_node_recursive(f, ErrorNode::Frame(frame), &mut Vec::new(), &mut 0, err_mode, true)
}
fn write_error_node_recursive(
f: &mut fmt::Formatter<'_>,
node: ErrorNode<'_>,
siblings_follow: &mut Vec<bool>,
number: &mut usize,
err_mode: ErrorMode,
linearize: bool,
) -> fmt::Result {
let children = node.children();
let is_chain = children.len() == 1
|| (!children.is_empty()
&& children
.iter()
.all(|child| matches!(child, ErrorNode::FlatSource { .. })));
let continues_chain = linearize && is_chain;
if !siblings_follow.is_empty() {
f.write_str("\n ")?;
if let Some((has_sibling, ancestors)) = siblings_follow[1..].split_last() {
for has_sibling in ancestors {
f.write_str(if *has_sibling { "│ " } else { " " })?;
}
f.write_str(if *has_sibling || continues_chain {
"├─"
} else {
"└─"
})?;
}
write!(f, "{number}: ")?;
}
err_mode.fmt(node.root_error(), f)?;
if !f.alternate() {
write_location(f, node.location())?;
}
if children.is_empty() {
return Ok(());
}
if siblings_follow.is_empty() {
f.write_str("\n\nCaused by:")?;
}
if continues_chain {
let has_sibling = siblings_follow.last().copied().unwrap_or(false);
let child_count = children.len();
for (index, child) in children.into_iter().enumerate() {
if siblings_follow.is_empty() {
siblings_follow.push(false);
} else {
*number += 1;
}
if let Some(last) = siblings_follow.last_mut() {
*last = has_sibling || index + 1 < child_count;
}
write_error_node_recursive(f, child, siblings_follow, number, err_mode, true)?;
}
if let Some(last) = siblings_follow.last_mut() {
*last = has_sibling;
}
return Ok(());
}
let child_count = children.len();
for (mut number, child) in children.into_iter().enumerate() {
siblings_follow.push(number + 1 < child_count);
write_error_node_recursive(f, child, siblings_follow, &mut number, err_mode, is_chain)?;
siblings_follow.pop();
}
Ok(())
}
impl<E: Error + Send + Sync + 'static> fmt::Display for Exn<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.frame, f)
}
}
impl<E: Error + Send + Sync + 'static> PartialEq<str> for Exn<E> {
fn eq(&self, other: &str) -> bool {
crate::root_error_eq(self.frame().error(), other)
}
}
impl<E: Error + Send + Sync + 'static> PartialEq<&str> for Exn<E> {
fn eq(&self, other: &&str) -> bool {
<Self as PartialEq<str>>::eq(self, other)
}
}
impl<E: Error + Send + Sync + 'static> PartialEq<String> for Exn<E> {
fn eq(&self, other: &String) -> bool {
<Self as PartialEq<str>>::eq(self, other)
}
}
impl fmt::Display for Frame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write_frame_recursive(f, self, ErrorMode::Debug)
} else {
if crate::error::is_transparent_marker(self.error())
&& let Some(diagnostic) = self.iter_errors_with_locations().next()
{
return fmt::Display::fmt(diagnostic.error(), f);
}
fmt::Display::fmt(self.error(), f)
}
}
}
pub struct Frame {
source: FrameSource,
}
pub(crate) struct FrameSource {
error: Box<dyn Error + Send + Sync + 'static>,
location: &'static Location<'static>,
children: Vec<Frame>,
}
impl fmt::Display for FrameSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.error())
}
}
impl fmt::Debug for FrameSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.error(), f)
}
}
impl Error for FrameSource {
fn source(&self) -> Option<&(dyn Error + 'static)> {
crate::error::native_source(self.error()).or_else(|| {
self.children.first().map(|frame| {
if frame.children().is_empty() {
frame.error() as &(dyn Error + 'static)
} else {
&frame.source as &(dyn Error + 'static)
}
})
})
}
}
impl FrameSource {
pub(crate) fn error(&self) -> &(dyn Error + Send + Sync + 'static) {
let mut error = &*self.error;
loop {
if let Some(erased) = error.downcast_ref::<Untyped>() {
error = &*erased.0;
} else if let Some(shared) = error.downcast_ref::<ErrorHandle>() {
error = shared.owned_error();
} else {
return error;
}
}
}
pub(crate) fn location(&self) -> &'static Location<'static> {
self.location
}
pub(crate) fn children(&self) -> &[Frame] {
&self.children
}
}
impl Frame {
pub fn error(&self) -> &(dyn Error + Send + Sync + 'static) {
self.source.error()
}
pub fn location(&self) -> &'static Location<'static> {
self.source.location()
}
pub fn children(&self) -> &[Frame] {
self.source.children()
}
pub(crate) fn source_frame(&self) -> &FrameSource {
&self.source
}
}
#[derive(Clone, Copy)]
pub(crate) enum ErrorNode<'a> {
Frame(&'a Frame),
Source {
error: &'a (dyn Error + 'static),
location: &'static Location<'static>,
},
FlatSource {
error: &'a (dyn Error + 'static),
location: &'static Location<'static>,
},
}
impl<'a> ErrorNode<'a> {
pub(crate) fn error(self) -> &'a (dyn Error + 'static) {
match self {
ErrorNode::Frame(frame) => frame.error(),
ErrorNode::Source { error, .. } | ErrorNode::FlatSource { error, .. } => error,
}
}
fn root_error(self) -> &'a (dyn Error + 'static) {
let mut error = self.error();
while let Some(nested) = error.downcast_ref::<crate::Error>() {
error = nested.error();
}
error
}
pub(crate) fn location(self) -> &'static Location<'static> {
match self {
ErrorNode::Frame(frame) => frame.location(),
ErrorNode::Source { location, .. } | ErrorNode::FlatSource { location, .. } => location,
}
}
pub(crate) fn children(self) -> Vec<ErrorNode<'a>> {
if matches!(self, ErrorNode::FlatSource { .. }) {
return Vec::new();
}
let error = self.error();
let location = self.location();
let mut children = Vec::new();
if let Some(nested) = error.downcast_ref::<crate::Error>() {
let root_error = self.root_error();
let mut skipped_root = false;
for source in nested
.iter_errors_with_locations()
.filter(|source| !source.error().is::<crate::Error>())
{
if !skipped_root && std::ptr::eq(source.error(), root_error) {
skipped_root = true;
continue;
}
children.push(ErrorNode::FlatSource {
error: source.error(),
location: source.location().unwrap_or(location),
});
}
} else if let Some(error) = crate::error::native_source(error) {
children.push(ErrorNode::Source { error, location });
}
if let ErrorNode::Frame(frame) = self {
children.extend(frame.children().iter().map(ErrorNode::Frame));
}
let mut diagnostics = Vec::new();
for child in children {
if crate::error::is_transparent_marker(child.error()) {
diagnostics.extend(child.children());
} else {
diagnostics.push(child);
}
}
diagnostics
}
}
impl Frame {
pub fn probable_cause(&self) -> Option<&(dyn Error + 'static)> {
self.probable_cause_inner()
}
pub fn iter_frames(&self) -> impl Iterator<Item = &Frame> + '_ {
let mut queue = std::collections::VecDeque::new();
queue.push_back(self);
BreadthFirstFrames { queue }
}
}
pub struct BreadthFirstFrames<'a> {
queue: std::collections::VecDeque<&'a Frame>,
}
impl<'a> Iterator for BreadthFirstFrames<'a> {
type Item = &'a Frame;
fn next(&mut self) -> Option<Self::Item> {
let frame = self.queue.pop_front()?;
for child in frame.children() {
self.queue.push_back(child);
}
Some(frame)
}
}
impl<E> From<Exn<E>> for Box<Frame>
where
E: Error + Send + Sync + 'static,
{
fn from(err: Exn<E>) -> Self {
err.frame
}
}
impl<E> From<Exn<E>> for Box<dyn Error + Send + Sync + 'static>
where
E: Error + Send + Sync + 'static,
{
fn from(err: Exn<E>) -> Self {
Box::new(err.into_error())
}
}
#[cfg(feature = "anyhow")]
impl<E> From<Exn<E>> for anyhow::Error
where
E: Error + Send + Sync + 'static,
{
fn from(err: Exn<E>) -> Self {
anyhow::Error::from(err.into_chain())
}
}
impl<E> From<Exn<E>> for Frame
where
E: Error + Send + Sync + 'static,
{
fn from(err: Exn<E>) -> Self {
*err.frame
}
}
impl From<Frame> for Exn {
fn from(frame: Frame) -> Self {
Exn::from_boxed_frame(Box::new(frame))
}
}
impl Exn {
pub(crate) fn from_boxed_frame(mut frame: Box<Frame>) -> Self {
if !frame.source.error.is::<Untyped>() {
frame.source.error = Box::new(Untyped(frame.source.error));
}
Exn {
frame,
phantom: Default::default(),
}
}
}
#[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
impl Exn {
pub(crate) fn from_chain(chain: ChainedError) -> Self {
let mut frames = Vec::new();
let mut next = Some(chain);
while let Some(node) = next {
next = node.source.map(|source| *source);
let frame = (!node.err.is_native_source()).then(|| Frame {
source: FrameSource {
error: node.err.into_owned_error(),
location: node.location,
children: Vec::new(),
},
});
frames.push((frame, node.logical_parent));
}
while let Some((frame, parent)) = frames.pop() {
let Some(mut frame) = frame else { continue };
frame.source.children.reverse();
match parent {
Some(parent) => frames[parent]
.0
.as_mut()
.expect("an explicit frame has an explicit parent")
.source
.children
.push(frame),
None => return frame.into(),
}
}
unreachable!("an error chain always contains its root frame")
}
}
pub struct Untyped(Box<dyn Error + Send + Sync + 'static>);
impl Untyped {
pub(crate) fn from_boxed(error: Box<dyn Error + Send + Sync + 'static>) -> Self {
Untyped(error)
}
}
impl fmt::Display for Untyped {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl fmt::Debug for Untyped {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl Error for Untyped {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.0.source()
}
}
impl<E> From<Exn<E>> for ChainedError
where
E: std::error::Error + Send + Sync + 'static,
{
fn from(err: Exn<E>) -> Self {
let flattened = flatten_error_nodes(*err.frame);
let mut source = None;
for node in flattened.into_iter().rev() {
source = Some(Box::new(ChainedError {
err: node.error,
location: node.location,
logical_parent: node.logical_parent,
source,
}));
}
*source.expect("an Exn always contains its root error")
}
}
struct OwnedErrorNode {
error: ErrorHandle,
location: &'static Location<'static>,
logical_parent: Option<usize>,
}
fn flatten_error_nodes(root: Frame) -> Vec<OwnedErrorNode> {
enum Pending {
Frame {
frame: Frame,
logical_parent: Option<usize>,
},
Source {
error: ErrorHandle,
location: &'static Location<'static>,
logical_parent: usize,
},
}
let mut queue = VecDeque::from([Pending::Frame {
frame: root,
logical_parent: None,
}]);
let mut out = Vec::new();
while let Some(node) = queue.pop_front() {
let node_index = out.len();
match node {
Pending::Frame {
frame:
Frame {
source:
FrameSource {
error,
location,
children,
},
},
logical_parent,
} => {
let error = ErrorHandle::new(unerase(error));
if let Some(source) = error.source() {
queue.push_back(Pending::Source {
error: source,
location,
logical_parent: node_index,
});
}
queue.extend(children.into_iter().map(|frame| Pending::Frame {
frame,
logical_parent: Some(node_index),
}));
out.push(OwnedErrorNode {
error,
location,
logical_parent,
});
}
Pending::Source {
error,
location,
logical_parent,
} => {
if let Some(source) = error.source() {
queue.push_back(Pending::Source {
error: source,
location,
logical_parent: node_index,
});
}
out.push(OwnedErrorNode {
error,
location,
logical_parent: Some(logical_parent),
});
}
}
}
out
}
fn unerase(mut error: Box<dyn Error + Send + Sync + 'static>) -> Box<dyn Error + Send + Sync + 'static> {
loop {
match error.downcast::<Untyped>() {
Ok(untyped) => error = untyped.0,
Err(typed) => return typed,
}
}
}