use core::{
fmt::{self, Debug, Display},
ops::Deref,
};
use alloc::{boxed::Box, format, string::ToString};
pub type Location = &'static core::panic::Location<'static>;
#[cfg_attr(
any(feature = "rust-v1.81", feature = "std"),
doc = r##"
[`prelude`]: crate::prelude
[`prelude::Stashable`]: crate::prelude::Stashable
"##
)]
#[cfg_attr(
not(any(feature = "rust-v1.81", feature = "std")),
doc = r##"
[`prelude`]: crate::surrogate_error_trait::prelude
[`prelude::Stashable`]: crate::surrogate_error_trait::prelude::Stashable
"##
)]
#[derive(Debug)]
pub struct Error<I>(Box<ErrorData<I>>);
#[derive(Debug)]
pub enum ErrorData<I> {
Stashed(StashedErrors<I>),
Wrapped(WrappedError<I>),
AdHoc(AdHocError),
}
#[derive(Debug)]
pub struct StashedErrors<I> {
summary: Box<str>,
errors: Box<[I]>,
locations: Box<[Location]>,
}
#[derive(Debug)]
pub struct WrappedError<I> {
context: Option<Box<str>>,
inner: I,
location: Location,
}
#[derive(Debug)]
pub struct AdHocError {
message: Box<str>,
location: Location,
}
impl<I> From<ErrorData<I>> for Error<I> {
fn from(value: ErrorData<I>) -> Self {
Self(Box::new(value))
}
}
impl<I> Deref for Error<I> {
type Target = ErrorData<I>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<I> AsRef<ErrorData<I>> for Error<I> {
fn as_ref(&self) -> &ErrorData<I> {
self.deref()
}
}
impl<I> From<Error<I>> for ErrorData<I> {
fn from(value: Error<I>) -> Self {
*value.0
}
}
#[cfg(feature = "rust-v1.81")]
impl<I: Display + Debug> core::error::Error for Error<I> {}
#[cfg(feature = "rust-v1.81")]
impl<I: Display + Debug> core::error::Error for ErrorData<I> {}
#[cfg(feature = "rust-v1.81")]
impl<I: Display + Debug> core::error::Error for StashedErrors<I> {}
#[cfg(feature = "rust-v1.81")]
impl<I: Display + Debug> core::error::Error for WrappedError<I> {}
#[cfg(feature = "rust-v1.81")]
impl core::error::Error for AdHocError {}
#[cfg(all(not(feature = "rust-v1.81"), feature = "std"))]
impl<I: Display + Debug> std::error::Error for Error<I> {}
#[cfg(all(not(feature = "rust-v1.81"), feature = "std"))]
impl<I: Display + Debug> std::error::Error for ErrorData<I> {}
#[cfg(all(not(feature = "rust-v1.81"), feature = "std"))]
impl<I: Display + Debug> std::error::Error for StashedErrors<I> {}
#[cfg(all(not(feature = "rust-v1.81"), feature = "std"))]
impl<I: Display + Debug> std::error::Error for WrappedError<I> {}
#[cfg(all(not(feature = "rust-v1.81"), feature = "std"))]
impl std::error::Error for AdHocError {}
impl<I: Display> Display for Error<I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<I: Display> Display for ErrorData<I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let i: &dyn Display = match self {
Self::AdHoc(err) => err,
Self::Stashed(errs) => errs,
Self::Wrapped(inner) => inner,
};
if !f.alternate() {
write!(f, "{i}")
} else {
write!(f, "{i:#}")
}
}
}
impl<I: Display> Display for StashedErrors<I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let errors = self.errors.as_ref();
let locations = self.locations.as_ref();
let summary = &self.summary;
let is_pretty = f.alternate();
match (errors, locations, is_pretty) {
([], ..) => write!(f, "{summary}: 0 errors"),
(_, [], ..) => write!(f, "{summary}: 0 source locations"),
([e], _, false) => write!(f, "{summary}: {e}"),
(errs, _, false) => {
write!(f, "{summary} ({} errors)", errs.len())
}
(errs, locs, true) => {
write!(f, "{summary}")?;
display_list_of_children(f, errs, locs)
}
}
}
}
impl<I: Display> Display for WrappedError<I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let err = &self.inner;
let loc = self.location;
let is_pretty = f.alternate();
match (&self.context, is_pretty) {
(None, false) => write!(f, "{err}"),
(None, true) => {
write!(f, "{err:#}")?;
display_location(f, "", loc)
}
(Some(context), false) => {
write!(f, "{context}: {err}")
}
(Some(context), true) => {
write!(f, "{context}: {err:#}")?;
display_location(f, "", loc)
}
}
}
}
impl Display for AdHocError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let is_pretty = f.alternate(); if !is_pretty {
write!(f, "{}", self.message)
} else {
writeln!(f, "{}", self.message)?;
write!(f, "at {}", self.location)
}
}
}
impl<I> Error<I> {
#[track_caller]
pub fn from_message<M: Display>(msg: M) -> Self {
ErrorData::from_message(msg).into()
}
pub fn from_stash<M, E, L>(summary: M, errors: E, locations: L) -> Self
where
M: Display,
E: Into<Box<[I]>>,
L: Into<Box<[Location]>>,
{
ErrorData::from_stash(summary, errors, locations).into()
}
#[track_caller]
pub fn wrap<E>(err: E) -> Self
where
E: Into<I>,
{
ErrorData::wrap(err).into()
}
#[track_caller]
pub fn wrap_with<E, M>(err: E, msg: M) -> Self
where
E: Into<I>,
M: Display,
{
ErrorData::wrap_with(err, msg).into()
}
}
impl<I> ErrorData<I> {
#[track_caller]
pub fn from_message<M: Display>(msg: M) -> Self {
let err = AdHocError::from_message(msg.to_string());
Self::AdHoc(err)
}
pub fn from_stash<M, E, L>(summary: M, errors: E, locations: L) -> Self
where
M: Display,
E: Into<Box<[I]>>,
L: Into<Box<[Location]>>,
{
let err = StashedErrors::from(summary, errors, locations);
Self::Stashed(err)
}
#[track_caller]
pub fn wrap<E>(err: E) -> Self
where
E: Into<I>,
{
Self::Wrapped(WrappedError::wrap(err))
}
#[track_caller]
pub fn wrap_with<E, M>(err: E, msg: M) -> Self
where
E: Into<I>,
M: Display,
{
Self::Wrapped(WrappedError::wrap_with(err, msg))
}
#[deprecated(since = "0.6.0", note = "renamed to `children`")]
pub fn childs(&self) -> &[I] {
self.children()
}
pub fn children(&self) -> &[I] {
match self {
Self::AdHoc(_) => &[],
Self::Wrapped(err) => core::slice::from_ref(err.inner()),
Self::Stashed(errs) => errs.errors(),
}
}
}
impl<I> StashedErrors<I> {
pub fn from<M, E, L>(summary: M, errors: E, locations: L) -> Self
where
M: Display,
E: Into<Box<[I]>>,
L: Into<Box<[Location]>>,
{
Self {
summary: summary.to_string().into_boxed_str(),
errors: errors.into(),
locations: locations.into(),
}
}
pub fn errors(&self) -> &[I] {
&self.errors
}
}
impl<I> WrappedError<I> {
#[track_caller]
pub fn wrap<E>(err: E) -> Self
where
E: Into<I>,
{
Self {
context: None,
inner: err.into(),
location: location(),
}
}
#[track_caller]
pub fn wrap_with<E, M>(err: E, msg: M) -> Self
where
E: Into<I>,
M: Display,
{
Self {
context: Some(msg.to_string().into_boxed_str()),
inner: err.into(),
location: location(),
}
}
pub fn inner(&self) -> &I {
&self.inner
}
}
impl AdHocError {
#[track_caller]
pub fn from_message<M: Display>(msg: M) -> Self {
Self {
message: msg.to_string().into_boxed_str(),
location: location(),
}
}
}
#[track_caller]
pub fn location() -> Location {
core::panic::Location::caller()
}
fn display_list_of_children<I: Display>(
f: &mut fmt::Formatter<'_>,
errs: &[I],
locs: &[Location],
) -> fmt::Result {
for (e, l) in errs.iter().zip(locs) {
display_multiline(f, &e)?;
display_location(f, " ", l)?;
}
Ok(())
}
fn display_multiline<I: Display>(
f: &mut fmt::Formatter<'_>,
err: &I,
) -> fmt::Result {
let mut prefix = "- ";
for line in format!("{err:#}").lines() {
writeln!(f)?;
write!(f, "{prefix}{line}")?;
prefix = " ";
}
Ok(())
}
fn display_location(
f: &mut fmt::Formatter<'_>,
indent: &str,
location: Location,
) -> fmt::Result {
writeln!(f)?;
write!(f, "{indent}at {location}")
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(any(feature = "rust-v1.81", feature = "std"))]
fn error_is_small_std() {
use crate::prelude::Stashable;
assert_small::<super::Error<Stashable>>();
}
#[test]
fn error_is_small_surrogate() {
use crate::surrogate_error_trait::prelude::Stashable;
assert_small::<super::Error<Stashable>>();
}
fn assert_small<T>() {
use core::mem::size_of;
assert_eq!(size_of::<T>(), size_of::<usize>());
}
}