use crate::Metadata;
macro_rules! classification_predicates {
() => {
pub fn is_retryable(&self) -> bool {
self.classify().is_retryable()
}
pub fn is_resource_exhausted(&self) -> bool {
self.classify().is_resource_exhausted()
}
pub fn can_retry(&self) -> bool {
self.classify().can_retry()
}
pub fn can_retry_lenient(&self) -> bool {
self.classify().can_retry_lenient()
}
pub fn is_corrupted(&self) -> bool {
self.classify().is_corrupted()
}
pub fn is_not_found(&self) -> bool {
self.classify().is_not_found()
}
pub fn is_validation(&self) -> bool {
self.classify().is_validation()
}
};
}
#[derive(Clone, Copy, Debug)]
pub struct DisplaySource<'a> {
error: &'a (dyn std::error::Error + 'static),
location: Option<&'static std::panic::Location<'static>>,
}
impl<'a> DisplaySource<'a> {
pub fn error(&self) -> &'a (dyn std::error::Error + 'static) {
self.error
}
pub fn location(&self) -> Option<&'static std::panic::Location<'static>> {
self.location
}
}
impl std::fmt::Display for DisplaySource<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.error, f)?;
if !f.alternate()
&& let Some(location) = self.location
{
crate::write_location(f, location)?;
}
Ok(())
}
}
impl crate::Error {
pub fn iter_errors(&self) -> impl Iterator<Item = &(dyn std::error::Error + 'static)> + '_ {
self.iter_errors_with_locations().map(|source| source.error)
}
pub fn iter_errors_with_locations(&self) -> impl Iterator<Item = DisplaySource<'_>> + '_ {
Errors::new(self.iter_root()).filter(|source| !is_transparent_marker(source.error))
}
pub fn downcast_any_ref<T: std::error::Error + 'static>(&self) -> Option<&T> {
self.iter_errors().find_map(|error| error.downcast_ref())
}
pub fn probable_cause(&self) -> &(dyn std::error::Error + 'static) {
self.iter_root().probable_cause().unwrap_or_else(|| self.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 classify(&self) -> Classifications<'_> {
classify(self)
}
classification_predicates!();
}
impl<E: std::error::Error + Send + Sync + 'static> crate::Exn<E> {
pub fn classify(&self) -> Classifications<'_> {
Classifications(Errors::new(Node::Frame(self.frame())))
}
classification_predicates!();
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Class {
Validation,
Corruption,
NotFound,
Retryable,
ResourceExhaustion(crate::ResourceExhaustionKind),
Io(std::io::ErrorKind),
Tagged(&'static str),
}
#[derive(Clone, Copy, Debug)]
pub struct Classification<'a> {
class: Class,
error: &'a (dyn std::error::Error + 'static),
}
pub fn classify<'a>(err: &'a (dyn std::error::Error + 'static)) -> Classifications<'a> {
Classifications(Errors::new(err.downcast_ref::<crate::Error>().map_or(
Node::Source {
error: err,
location: None,
},
crate::Error::iter_root,
)))
}
pub struct Classifications<'a>(Errors<'a>);
impl<'a> Iterator for Classifications<'a> {
type Item = Classification<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.0.find_map(|source| classify_one(source.error))
}
}
impl Classifications<'_> {
pub fn is_retryable(self) -> bool {
self.has(Class::Retryable)
}
pub fn can_retry(mut self) -> bool {
self.any(|classification| class_can_retry(classification.class()))
}
pub fn can_retry_lenient(mut self) -> bool {
self.any(classification_can_retry_lenient)
}
pub fn is_not_found(self) -> bool {
self.has(Class::NotFound)
}
pub fn is_validation(self) -> bool {
self.has(Class::Validation)
}
pub fn is_corrupted(self) -> bool {
self.has(Class::Corruption)
}
pub fn is_resource_exhausted(mut self) -> bool {
self.any(|classification| matches!(classification.class(), Class::ResourceExhaustion(_)))
}
pub fn has(mut self, class: Class) -> bool {
self.any(|classification| classification.class() == class)
}
}
impl<'a> Classification<'a> {
pub fn class(&self) -> Class {
self.class
}
pub fn error(&self) -> &'a (dyn std::error::Error + 'static) {
self.error
}
pub fn io_kind(&self) -> Option<std::io::ErrorKind> {
self.error.downcast_ref::<std::io::Error>().map(std::io::Error::kind)
}
}
fn classify_one<'a>(error: &'a (dyn std::error::Error + 'static)) -> Option<Classification<'a>> {
let class = if let Some(marker) = error.downcast_ref::<crate::ClassificationMarker>() {
marker.class()
} else if let Some(error) = error.downcast_ref::<crate::Message>() {
error.class?
} else if error.is::<std::collections::TryReserveError>() {
Class::ResourceExhaustion(crate::ResourceExhaustionKind::AllocationFailure)
} else {
let error = error.downcast_ref::<std::io::Error>()?;
match error.kind() {
std::io::ErrorKind::NotFound => Class::NotFound,
std::io::ErrorKind::OutOfMemory => {
Class::ResourceExhaustion(crate::ResourceExhaustionKind::AllocationFailure)
}
kind => Class::Io(kind),
}
};
Some(Classification { class, error })
}
fn class_can_retry(class: Class) -> bool {
matches!(
class,
Class::Retryable | Class::Io(std::io::ErrorKind::Interrupted | std::io::ErrorKind::TimedOut)
)
}
fn classification_can_retry_lenient(classification: Classification<'_>) -> bool {
class_can_retry(classification.class())
|| classification.io_kind().is_some_and(|kind| {
use std::io::ErrorKind::*;
matches!(
kind,
UnexpectedEof
| OutOfMemory
| BrokenPipe
| AddrInUse
| ConnectionAborted
| ConnectionReset
| ConnectionRefused
)
})
}
#[derive(Clone, Copy)]
enum Node<'a> {
Frame(&'a crate::exn::Frame),
Source {
error: &'a (dyn std::error::Error + 'static),
location: Option<&'static std::panic::Location<'static>>,
},
#[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
Chain {
node: &'a crate::types::ChainedError,
index: usize,
cursor: Option<usize>,
},
}
impl<'a> Node<'a> {
fn display(self) -> DisplaySource<'a> {
let (error, location) = match self {
Node::Frame(frame) => (
frame.error() as &(dyn std::error::Error + 'static),
Some(frame.location()),
),
Node::Source { error, location } => (error, location),
#[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
Node::Chain { node, .. } => (node.err.error(), node.err.has_frame_location().then_some(node.location)),
};
DisplaySource { error, location }
}
fn children(self) -> std::collections::VecDeque<Node<'a>> {
#[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
let root = match self {
Node::Chain { node, index, .. } => Node::Chain {
node,
index,
cursor: None,
},
root => root,
};
#[cfg(any(feature = "tree-error", not(feature = "auto-chain-error")))]
let root = self;
let mut traversal = Errors::new(root);
traversal.children(root);
traversal.pending
}
fn probable_cause(self) -> Option<&'a (dyn std::error::Error + 'static)> {
let mut node = self;
let mut cause = None;
loop {
let mut pending = node.children();
let mut only_child = None;
while let Some(child) = pending.pop_front() {
if is_transparent_marker(child.display().error) {
pending.extend(child.children());
} else if only_child.replace(child).is_some() {
return cause;
}
}
node = match only_child {
Some(child) => child,
None => return cause,
};
cause = Some(node.display().error);
}
}
}
struct Errors<'a> {
root: Option<Node<'a>>,
previous: Option<Node<'a>>,
pending: std::collections::VecDeque<Node<'a>>,
#[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
chains: Vec<(usize, Option<&'a crate::types::ChainedError>)>,
}
impl<'a> Errors<'a> {
fn new(root: Node<'a>) -> Self {
Errors {
root: Some(root),
previous: None,
pending: Default::default(),
#[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
chains: Vec::new(),
}
}
fn source(
&mut self,
error: &'a (dyn std::error::Error + 'static),
location: Option<&'static std::panic::Location<'static>>,
) {
if let Some(error) = error.downcast_ref::<crate::Error>() {
self.pending.push_back(error.iter_root());
} else if let Some(source) = native_source(error) {
self.pending.push_back(Node::Source {
error: source,
location: location.filter(|_| is_transparent_marker(error)),
});
}
}
fn children(&mut self, node: Node<'a>) {
match node {
Node::Frame(frame) => {
self.source(frame.error(), Some(frame.location()));
self.pending.extend(frame.children().iter().map(Node::Frame));
}
Node::Source { error, location } => self.source(error, location),
#[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
Node::Chain { node, index, cursor } => {
if let Some(error) = node.err.error().downcast_ref::<crate::Error>() {
self.pending.push_back(error.iter_root());
}
let cursor = match cursor {
Some(cursor) => cursor,
None if node.source.is_none() => return,
None => {
self.chains.push((index + 1, node.source.as_deref()));
self.chains.len() - 1
}
};
let (child_index, next) = &mut self.chains[cursor];
while let Some(child) = next.filter(|child| child.logical_parent.is_some_and(|parent| parent < index)) {
*child_index += 1;
*next = child.source.as_deref();
}
while let Some(child) = next.filter(|child| child.logical_parent == Some(index)) {
self.pending.push_back(Node::Chain {
node: child,
index: *child_index,
cursor: Some(cursor),
});
*child_index += 1;
*next = child.source.as_deref();
}
}
}
}
}
impl<'a> Iterator for Errors<'a> {
type Item = DisplaySource<'a>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(previous) = self.previous.take() {
self.children(previous);
}
let node = self.root.take().or_else(|| self.pending.pop_front())?;
self.previous = Some(node);
Some(node.display())
}
}
impl crate::exn::Frame {
pub(crate) fn probable_cause_inner(&self) -> Option<&(dyn std::error::Error + 'static)> {
Node::Frame(self).probable_cause()
}
pub(crate) fn iter_errors_with_locations(&self) -> impl Iterator<Item = DisplaySource<'_>> + '_ {
Errors::new(Node::Frame(self)).filter(|source| !is_transparent_marker(source.error))
}
}
#[cfg(any(feature = "tree-error", not(feature = "auto-chain-error")))]
mod _impl {
use crate::{Error, Exn};
use std::fmt::Formatter;
impl Error {
pub fn error(&self) -> &(dyn std::error::Error + 'static) {
self.inner.frame().error()
}
pub(super) fn iter_root(&self) -> super::Node<'_> {
super::Node::Frame(self.inner.frame())
}
}
pub(crate) enum Inner {
ExnAsError(Box<crate::exn::Frame>),
Exn(Box<crate::exn::Frame>),
}
impl Inner {
pub(crate) fn frame(&self) -> &crate::exn::Frame {
match self {
Inner::ExnAsError(f) | Inner::Exn(f) => f,
}
}
}
impl Error {
#[track_caller]
pub fn from_error(error: impl std::error::Error + Send + Sync + 'static) -> Self {
Error {
inner: Inner::ExnAsError(Exn::new(error).into()),
}
}
#[track_caller]
pub fn from_boxed(error: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
Self::from_error(crate::exn::Untyped::from_boxed(error))
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.inner {
Inner::ExnAsError(err) => std::fmt::Display::fmt(err.error(), f),
Inner::Exn(frame) => std::fmt::Display::fmt(frame, f),
}
}
}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.inner {
Inner::ExnAsError(err) => std::fmt::Debug::fmt(err.error(), f),
Inner::Exn(frame) => std::fmt::Debug::fmt(frame, f),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.inner {
Inner::ExnAsError(frame) | Inner::Exn(frame) => {
let error = frame.error();
(!error.is::<Error>())
.then(|| super::native_source(error))
.flatten()
.or_else(|| frame.children().first().map(|frame| frame.error() as _))
}
}
}
}
impl<E> From<Exn<E>> for Error
where
E: std::error::Error + Send + Sync + 'static,
{
fn from(err: Exn<E>) -> Self {
Error {
inner: Inner::Exn(err.into()),
}
}
}
}
#[cfg(any(feature = "tree-error", not(feature = "auto-chain-error")))]
pub(super) use _impl::Inner;
#[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
mod _impl {
use crate::{Error, Exn};
use std::fmt::Formatter;
impl Error {
pub fn error(&self) -> &(dyn std::error::Error + 'static) {
self.inner.err.error()
}
pub(super) fn iter_root(&self) -> super::Node<'_> {
super::Node::Chain {
node: &self.inner,
index: 0,
cursor: None,
}
}
}
impl Error {
#[track_caller]
pub fn from_error(error: impl std::error::Error + Send + Sync + 'static) -> Self {
Error {
inner: Exn::new(error).into_chain(),
}
}
#[track_caller]
pub fn from_boxed(error: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
Self::from_error(crate::exn::Untyped::from_boxed(error))
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if super::is_transparent_marker(self.error())
&& let Some(diagnostic) = self.iter_errors_with_locations().next()
{
return std::fmt::Display::fmt(&diagnostic, f);
}
std::fmt::Display::fmt(&self.inner, f)
}
}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if super::is_transparent_marker(self.error())
&& let Some(diagnostic) = self.iter_errors().next()
{
return std::fmt::Debug::fmt(diagnostic, f);
}
std::fmt::Debug::fmt(&self.inner, f)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.inner.source()
}
}
impl<E> From<Exn<E>> for Error
where
E: std::error::Error + Send + Sync + 'static,
{
fn from(err: Exn<E>) -> Self {
Error {
inner: err.into_chain(),
}
}
}
}
pub(crate) fn native_source<'a>(
err: &'a (dyn std::error::Error + 'static),
) -> Option<&'a (dyn std::error::Error + 'static)> {
match err.downcast_ref::<std::io::Error>() {
Some(err) => err.get_ref().map(|err| err as _),
None => err.source(),
}
}
pub(crate) fn is_transparent_marker(mut error: &(dyn std::error::Error + 'static)) -> bool {
while let Some(nested) = error.downcast_ref::<crate::Error>() {
error = nested.error();
}
error.is::<crate::ClassificationMarker>()
}