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>,
}
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 {
error: Box::new(error),
location: Location::caller(),
children: Vec::new(),
};
Self {
frame: Box::new(frame),
phantom: PhantomData,
}
}
#[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.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.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.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.children.push(*err.frame);
}
self
}
pub fn drain_children(&mut self) -> impl Iterator<Item = Exn> + '_ {
self.frame.children.drain(..).map(Exn::from)
}
pub fn erased(self) -> Exn {
let untyped_frame = {
let Frame {
error,
location,
children,
} = *self.frame;
let error = Untyped(error);
Frame {
error: Box::new(error),
location,
children,
}
};
Exn {
frame: Box::new(untyped_frame),
phantom: Default::default(),
}
}
pub fn error(&self) -> &E {
self.frame
.error
.downcast_ref()
.expect("the owned frame always matches the compile-time error type")
}
pub fn into_box(self) -> Box<E> {
match self.frame.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 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, TreeMode::Linearize)
}
}
impl fmt::Debug for Frame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_frame_recursive(f, self, "", ErrorMode::Display, TreeMode::Linearize)
}
}
#[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:?}"),
}
}
}
#[derive(Copy, Clone)]
enum TreeMode {
Linearize,
Verbatim,
}
fn write_frame_recursive(
f: &mut fmt::Formatter<'_>,
frame: &Frame,
prefix: &str,
err_mode: ErrorMode,
tree_mode: TreeMode,
) -> 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, prefix, err_mode, tree_mode)?;
}
return Ok(());
}
}
write_error_node_recursive(f, ErrorNode::Frame(frame), prefix, err_mode, tree_mode)
}
fn write_error_node_recursive(
f: &mut fmt::Formatter<'_>,
node: ErrorNode<'_>,
prefix: &str,
err_mode: ErrorMode,
tree_mode: TreeMode,
) -> fmt::Result {
let mut root_error = node.error();
while let Some(error) = root_error.downcast_ref::<crate::Error>() {
root_error = error.error();
}
err_mode.fmt(root_error, f)?;
if !f.alternate() {
write_location(f, node.location())?;
}
if let Some(err) = node.error().downcast_ref::<crate::Error>() {
let mut skipped_root = false;
for source in err
.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;
}
write!(f, "\n{prefix}|\n{prefix}└─ ")?;
err_mode.fmt(source.error(), f)?;
if !f.alternate() {
write_location(f, source.location().unwrap_or_else(|| node.location()))?;
}
}
}
let children = node.children();
let children_len = children.len();
for (child_index, child) in children.into_iter().enumerate() {
write!(f, "\n{prefix}|")?;
write!(f, "\n{prefix}└─ ")?;
let child_child_len = if child
.error()
.downcast_ref::<crate::Error>()
.is_some_and(|err| err.iter_errors().filter(|source| !source.is::<crate::Error>()).count() > 1)
{
1
} else {
child.children().len()
};
let may_linearize_chain = matches!(tree_mode, TreeMode::Linearize) && children_len == 1 && child_child_len == 1;
if may_linearize_chain {
write_error_node_recursive(f, child, prefix, err_mode, tree_mode)?;
} else if child_index < children_len - 1 {
write_error_node_recursive(f, child, &format!("{prefix}| "), err_mode, tree_mode)?;
} else {
write_error_node_recursive(f, child, &format!("{prefix} "), err_mode, tree_mode)?;
}
}
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, TreeMode::Verbatim)
} 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 {
error: Box<dyn Error + Send + Sync + 'static>,
location: &'static Location<'static>,
children: Vec<Frame>,
}
impl Frame {
pub fn error(&self) -> &(dyn Error + Send + Sync + 'static) {
let mut error = &*self.error;
while let Some(erased) = error.downcast_ref::<Untyped>() {
error = &*erased.0;
}
error
}
pub fn location(&self) -> &'static Location<'static> {
self.location
}
pub fn children(&self) -> &[Frame] {
&self.children
}
}
#[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,
}
}
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>() {
if crate::error::is_transparent_marker(error) {
children.extend(
nested
.iter_errors_with_locations()
.filter(|source| !source.error().is::<crate::Error>())
.map(|source| 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(mut frame: Frame) -> Self {
if !frame.error.is::<Untyped>() {
frame.error = Box::new(Untyped(frame.error));
}
Exn {
frame: Box::new(frame),
phantom: Default::default(),
}
}
}
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 {
error,
location,
children,
},
logical_parent,
} => {
let error = ErrorHandle::new(unerase(error));
if !error.error().is::<crate::Error>()
&& 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 !error.error().is::<crate::Error>()
&& 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,
}
}
}