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::{write_location, ChainedError, Exn};
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 {
fn walk_sources(error: &dyn Error, location: &'static Location<'static>) -> Vec<Frame> {
if let Some(source) = error.source() {
let children = vec![Frame {
error: Box::new(SourceError::new(source)),
location,
children: walk_sources(source, location),
}];
children
} else {
vec![]
}
}
let location = Location::caller();
let children = walk_sources(&error, location);
let frame = Frame {
error: Box::new(error),
location,
children,
};
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) -> crate::ChainedError {
self.into()
}
pub fn frame(&self) -> &Frame {
&self.frame
}
pub fn iter(&self) -> impl Iterator<Item = &Frame> {
self.frame().iter_frames()
}
pub fn downcast_any_ref<T: Error + 'static>(&self) -> Option<&T> {
self.iter().find_map(|e| e.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)]
enum ErrorMode {
Display,
Debug,
}
#[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 {
match err_mode {
ErrorMode::Display => fmt::Display::fmt(frame.error(), f),
ErrorMode::Debug => {
write!(f, "{:?}", frame.error())
}
}?;
if !f.alternate() {
write_location(f, frame.location)?;
}
let children = frame.children();
let children_len = children.len();
for (cidx, child) in children.iter().enumerate() {
write!(f, "\n{prefix}|")?;
write!(f, "\n{prefix}└─ ")?;
let child_child_len = child.children().len();
let may_linearize_chain = matches!(tree_mode, TreeMode::Linearize) && children_len == 1 && child_child_len == 1;
if may_linearize_chain {
write_frame_recursive(f, child, prefix, err_mode, tree_mode)?;
} else if cidx < children_len - 1 {
write_frame_recursive(f, child, &format!("{prefix}| "), err_mode, tree_mode)?;
} else {
write_frame_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 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 {
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) {
&*self.error
}
pub fn location(&self) -> &'static Location<'static> {
self.location
}
pub fn children(&self) -> &[Frame] {
&self.children
}
}
impl Frame {
pub fn probable_cause(&self) -> Option<&Frame> {
fn walk<'a>(frame: &'a Frame, depth: usize) -> (usize, usize, &'a Frame) {
if frame.children.is_empty() {
return (1, depth, frame);
}
let mut total_leafs = 0;
let mut best: Option<(usize, usize, &'a Frame)> = None;
for child in &frame.children {
let (leafs, d, f) = walk(child, depth + 1);
total_leafs += leafs;
match best {
None => best = Some((leafs, d, f)),
Some((bl, bd, _)) => {
if leafs > bl || (leafs == bl && d > bd) {
best = Some((leafs, d, f));
}
}
}
}
let self_candidate = (total_leafs, depth, frame);
match best {
None => self_candidate,
Some(best_child) => {
if total_leafs > best_child.0 || (total_leafs == best_child.0 && depth > best_child.1) {
self_candidate
} else {
best_child
}
}
}
}
if self.children().iter().all(|c| c.children().is_empty()) {
if let Some(last) = self.children().last() {
return Some(last);
}
}
let res = walk(self, 0).2;
if std::ptr::addr_eq(res, self) {
None
} else {
Some(res)
}
}
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
}
}
#[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 {
frame: Box::new(frame),
phantom: Default::default(),
}
}
}
pub struct Untyped(Box<dyn Error + Send + Sync + 'static>);
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 {}
pub struct Something;
impl fmt::Display for Something {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Something went wrong")
}
}
impl fmt::Debug for Something {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self, f)
}
}
impl Error for Something {}
struct SourceError {
display: String,
alt_display: String,
debug: String,
alt_debug: String,
}
impl fmt::Debug for SourceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let dbg = if f.alternate() { &self.alt_debug } else { &self.debug };
f.write_str(dbg)
}
}
impl fmt::Display for SourceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ds = if f.alternate() {
&self.alt_display
} else {
&self.display
};
f.write_str(ds)
}
}
impl Error for SourceError {}
impl SourceError {
fn new(err: &dyn Error) -> Self {
SourceError {
display: err.to_string(),
alt_display: format!("{err:#}"),
debug: format!("{err:?}"),
alt_debug: format!("{err:#?}"),
}
}
}
impl<E> From<Exn<E>> for ChainedError
where
E: std::error::Error + Send + Sync + 'static,
{
fn from(mut err: Exn<E>) -> Self {
let stack: VecDeque<_> = err.frame.children.drain(..).collect();
let location = err.frame.location;
ChainedError {
err: err.into_box(),
location,
source: recurse_source_frames(stack),
}
}
}
fn recurse_source_frames(mut stack: VecDeque<Frame>) -> Option<Box<ChainedError>> {
let frame = stack.pop_front()?;
stack.extend(frame.children);
Box::new(ChainedError {
err: frame.error,
location: frame.location,
source: recurse_source_frames(stack),
})
.into()
}