use std::fmt::{self, Formatter};
use crate::{AnyError, Location, Meta, backtrace_enabled};
#[derive(Debug, Copy, Clone)]
pub enum SourceFormat {
OneLine,
MultiLine {
location: bool,
},
}
pub trait StackError: fmt::Display + fmt::Debug + Send + Sync {
fn as_std(&self) -> &(dyn std::error::Error + Send + Sync + 'static);
fn as_dyn(&self) -> &dyn StackError;
fn meta(&self) -> Option<&Meta>;
fn source(&self) -> Option<ErrorRef<'_>>;
fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
fn is_transparent(&self) -> bool;
fn as_ref(&self) -> ErrorRef<'_> {
ErrorRef::Stack(self.as_dyn())
}
fn stack(&self) -> Chain<'_> {
Chain::stack(self.as_ref())
}
fn sources(&self) -> Chain<'_> {
Chain::sources(self.as_ref())
}
fn report(&self) -> Report<'_> {
Report::new(self.as_dyn())
}
}
pub trait StackErrorExt: StackError + Sized + 'static {
#[track_caller]
fn into_any(self) -> AnyError {
AnyError::from_stack(self)
}
#[track_caller]
fn context(self, context: impl fmt::Display) -> AnyError {
self.into_any().context(context)
}
}
impl<T: StackError + Sized + 'static> StackErrorExt for T {}
#[derive(Copy, Clone, Debug)]
pub enum ErrorRef<'a> {
Std(&'a dyn std::error::Error, Option<&'a Meta>),
Stack(&'a dyn StackError),
}
impl<'a> ErrorRef<'a> {
pub fn std(err: &dyn std::error::Error) -> ErrorRef<'_> {
ErrorRef::Std(err, None)
}
pub(crate) fn std_with_meta(err: &'a dyn std::error::Error, meta: &'a Meta) -> ErrorRef<'a> {
ErrorRef::Std(err, Some(meta))
}
pub fn stack(err: &dyn StackError) -> ErrorRef<'_> {
ErrorRef::Stack(err)
}
pub fn is_transparent(&self) -> bool {
match self {
ErrorRef::Std(_, _) => false,
ErrorRef::Stack(error) => error.is_transparent(),
}
}
pub fn as_std(&self) -> &dyn std::error::Error {
match self {
ErrorRef::Std(error, _) => error,
ErrorRef::Stack(error) => error.as_std(),
}
}
pub fn source(self) -> Option<ErrorRef<'a>> {
match self {
Self::Std(error, _) => error.source().map(ErrorRef::std),
Self::Stack(error) => StackError::source(error),
}
}
pub fn meta(&self) -> Option<&Meta> {
match self {
ErrorRef::Std(_, meta) => *meta,
ErrorRef::Stack(error) => error.meta(),
}
}
pub(crate) fn fmt_location(&self, f: &mut Formatter) -> fmt::Result {
fmt_location(self.meta().and_then(|m| m.location()), f)
}
pub fn fmt_message(&self, f: &mut Formatter) -> fmt::Result {
match self {
ErrorRef::Std(error, _) => fmt::Display::fmt(error, f),
ErrorRef::Stack(error) => error.fmt_message(f),
}
}
}
impl<'a> fmt::Display for ErrorRef<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Std(error, _) => write!(f, "{error}"),
Self::Stack(error) => write!(f, "{error}"),
}
}
}
#[derive(Clone, Copy)]
pub struct Report<'a> {
inner: &'a dyn StackError,
location: bool,
sources: Option<SourceFormat>,
}
impl<'a> Report<'a> {
pub(crate) fn new(inner: &'a dyn StackError) -> Self {
let location = backtrace_enabled();
Self {
inner,
location,
sources: None,
}
}
pub fn full(mut self) -> Self {
let location = backtrace_enabled();
self.location = location;
self.sources = Some(SourceFormat::MultiLine { location });
self
}
pub fn location(mut self, value: bool) -> Self {
self.location = value;
self
}
pub fn sources(mut self, format: Option<SourceFormat>) -> Self {
self.sources = format;
self
}
}
impl<'a> fmt::Display for Report<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.sources {
None => {
let item = self
.inner
.stack()
.find(|item| !item.is_transparent())
.unwrap_or_else(|| self.inner.as_ref());
item.fmt_message(f)?;
}
Some(format) => {
let mut stack = self
.inner
.stack()
.filter(|s| self.location || !s.is_transparent())
.peekable();
let mut is_first = true;
while let Some(item) = stack.next() {
match format {
SourceFormat::OneLine => {
if !is_first {
write!(f, ": ")?;
}
item.fmt_message(f)?;
}
SourceFormat::MultiLine { location } => {
if !is_first {
write!(f, " ")?;
}
item.fmt_message(f)?;
if location {
item.fmt_location(f)?;
}
if stack.peek().is_some() {
writeln!(f)?;
if is_first {
writeln!(f, "Caused by:")?;
}
}
}
}
is_first = false;
}
}
}
Ok(())
}
}
fn fmt_location(location: Option<&Location>, f: &mut Formatter) -> fmt::Result {
if let Some(location) = location {
write!(f, " ({})", location)?;
}
Ok(())
}
pub struct Chain<'a> {
item: Option<ErrorRef<'a>>,
skip: bool,
}
impl<'a> Chain<'a> {
fn stack(item: ErrorRef<'a>) -> Self {
Self {
item: Some(item),
skip: false,
}
}
fn sources(item: ErrorRef<'a>) -> Self {
Self {
item: Some(item),
skip: true,
}
}
}
impl<'a> Iterator for Chain<'a> {
type Item = ErrorRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let item = self.item?;
self.item = item.source();
if self.skip {
self.skip = false;
} else {
return Some(item);
}
}
}
}
macro_rules! impl_stack_error_for_std_error {
($ty:ty) => {
impl StackError for $ty {
fn as_std(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
self
}
fn as_dyn(&self) -> &dyn StackError {
self
}
fn meta(&self) -> Option<&Meta> {
None
}
fn source(&self) -> Option<ErrorRef<'_>> {
std::error::Error::source(self).map(ErrorRef::std)
}
fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
fn is_transparent(&self) -> bool {
false
}
}
};
}
impl_stack_error_for_std_error!(std::io::Error);
impl_stack_error_for_std_error!(std::fmt::Error);
impl_stack_error_for_std_error!(std::str::Utf8Error);
impl_stack_error_for_std_error!(std::string::FromUtf8Error);
impl_stack_error_for_std_error!(std::net::AddrParseError);
impl_stack_error_for_std_error!(std::array::TryFromSliceError);
impl StackError for Box<dyn StackError> {
fn as_std(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
StackError::as_std(&**self)
}
fn as_dyn(&self) -> &dyn StackError {
StackError::as_dyn(&**self)
}
fn meta(&self) -> Option<&Meta> {
StackError::meta(&**self)
}
fn source(&self) -> Option<ErrorRef<'_>> {
StackError::source(&**self)
}
fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
StackError::fmt_message(&**self, f)
}
fn is_transparent(&self) -> bool {
StackError::is_transparent(&**self)
}
}
impl StackError for std::sync::Arc<dyn StackError> {
fn as_std(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
StackError::as_std(&**self)
}
fn as_dyn(&self) -> &dyn StackError {
StackError::as_dyn(&**self)
}
fn meta(&self) -> Option<&Meta> {
StackError::meta(&**self)
}
fn source(&self) -> Option<ErrorRef<'_>> {
StackError::source(&**self)
}
fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
StackError::fmt_message(&**self, f)
}
fn is_transparent(&self) -> bool {
StackError::is_transparent(&**self)
}
}