use ::alloc::{borrow::Cow, boxed::Box, vec, vec::Vec};
use ::core::{
any::Any,
error::Error,
fmt::{Debug, Display, Formatter, Result as FmtResult},
marker::PhantomData,
panic::Location,
};
#[cfg(feature = "colors")]
use ::yansi::Paint;
use crate::features::{AnyDebugSendSync, ErrorSendSync};
#[derive(Debug)]
pub(crate) struct HumanInfo {
pub(crate) message: Cow<'static, str>,
pub(crate) location: &'static Location<'static>,
}
#[derive(Debug)]
pub(crate) struct MachineInfo {
pub(crate) attachment: Box<dyn AnyDebugSendSync>,
}
#[derive(Debug)]
pub(crate) enum Info {
Human(HumanInfo),
Machine(MachineInfo),
}
const _: () = {
assert!(size_of::<Info>() == size_of::<HumanInfo>());
};
#[doc(hidden)]
#[derive(Debug)]
pub struct ProvideContext;
#[derive(Default)]
pub struct NeuErr<M = ()>(NeuErrImpl, PhantomData<M>);
#[derive(Default)]
pub struct NeuErrImpl {
infos: Vec<Info>,
source: Option<Box<dyn ErrorSendSync>>,
}
impl<M> Debug for NeuErr<M> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Debug::fmt(&self.0, f)
}
}
impl<M> Display for NeuErr<M> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Display::fmt(&self.0, f)
}
}
impl Debug for NeuErrImpl {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
if f.alternate() {
f.debug_struct("NeuErr")
.field("infos", &self.infos)
.field("source", &self.source)
.finish()
} else {
Display::fmt(self, f)
}
}
}
impl Display for NeuErrImpl {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let mut human = self.contexts().peekable();
if human.peek().is_none() {
#[cfg(feature = "colors")]
let unknown = "Unknown error".red();
#[cfg(not(feature = "colors"))]
let unknown = "Unknown error";
write!(f, "{unknown}")?;
}
while let Some(context) = human.next() {
#[cfg(feature = "colors")]
let message = context.message.as_ref().red();
#[cfg(not(feature = "colors"))]
let message = context.message.as_ref();
#[cfg(feature = "colors")]
let location = context.location.rgb(0x90, 0x90, 0x90);
#[cfg(not(feature = "colors"))]
let location = context.location;
if f.alternate() {
write!(f, "{message} (at {location})")?;
if human.peek().is_some() {
write!(f, "; ")?;
}
} else {
writeln!(f, "{message}")?;
write!(f, "|- at {location}")?;
if human.peek().is_some() {
writeln!(f)?;
writeln!(f, "|")?;
}
}
}
#[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
let mut source = self.source.as_deref().map(|e| e as &(dyn Error + 'static));
while let Some(err) = source {
#[cfg(feature = "colors")]
let error = err.red();
#[cfg(not(feature = "colors"))]
let error = err;
if f.alternate() {
write!(f, "; caused by: {error}")?;
} else {
writeln!(f)?;
writeln!(f, "|")?;
write!(f, "|- caused by: {error}")?;
}
source = err.source();
}
Ok(())
}
}
impl NeuErr<ProvideContext> {
#[track_caller]
#[must_use]
pub fn new<C>(context: C) -> Self
where
C: Into<Cow<'static, str>>,
{
let infos =
vec![Info::Human(HumanInfo { message: context.into(), location: Location::caller() })];
Self(NeuErrImpl { infos, ..Default::default() }, PhantomData)
}
#[track_caller]
#[must_use]
pub fn new_with_source<C, E>(context: C, source: E) -> Self
where
C: Into<Cow<'static, str>>,
E: ErrorSendSync + 'static,
{
let infos =
vec![Info::Human(HumanInfo { message: context.into(), location: Location::caller() })];
Self(NeuErrImpl { infos, source: Some(Box::new(source)) }, PhantomData)
}
}
impl NeuErr {
#[must_use]
pub fn from_source<E>(source: E) -> Self
where
E: ErrorSendSync + 'static,
{
Self(NeuErrImpl { source: Some(Box::new(source)), ..Default::default() }, PhantomData)
}
}
impl<M> NeuErr<M> {
#[track_caller]
#[must_use]
pub fn context<C>(self, context: C) -> NeuErr<ProvideContext>
where
C: Into<Cow<'static, str>>,
{
NeuErr(self.0.context(context), PhantomData)
}
#[must_use]
pub fn attach<C>(self, context: C) -> Self
where
C: AnyDebugSendSync + 'static,
{
Self(self.0.attach(context), PhantomData)
}
#[must_use]
pub fn attach_override<C>(self, context: C) -> Self
where
C: AnyDebugSendSync + 'static,
{
Self(self.0.attach_override(context), PhantomData)
}
#[cfg_attr(not(test), expect(unused, reason = "For consistency"))]
pub(crate) fn contexts(&self) -> impl Iterator<Item = &'_ HumanInfo> {
self.0.contexts()
}
pub fn attachments<C>(&self) -> impl Iterator<Item = &'_ C>
where
C: AnyDebugSendSync + 'static,
{
self.0.attachments()
}
#[must_use]
pub fn attachment<C>(&self) -> Option<&C>
where
C: AnyDebugSendSync + 'static,
{
self.0.attachment()
}
#[must_use]
pub fn source(&self) -> Option<&(dyn ErrorSendSync + 'static)> {
self.0.source.as_deref()
}
#[must_use]
#[inline]
pub fn into_error(self) -> NeuErrImpl {
self.0
}
#[must_use]
#[inline]
pub fn remove_marker(self) -> NeuErr {
NeuErr(self.0, PhantomData)
}
}
impl NeuErrImpl {
#[must_use]
#[inline]
pub const fn wrap(self) -> NeuErr {
NeuErr(self, PhantomData)
}
#[track_caller]
#[must_use]
pub fn context<C>(mut self, context: C) -> Self
where
C: Into<Cow<'static, str>>,
{
let context = HumanInfo { message: context.into(), location: Location::caller() };
self.infos.push(Info::Human(context));
self
}
#[must_use]
pub fn attach<C>(mut self, context: C) -> Self
where
C: AnyDebugSendSync + 'static,
{
let context = MachineInfo { attachment: Box::new(context) };
self.infos.push(Info::Machine(context));
self
}
#[must_use]
pub fn attach_override<C>(mut self, mut context: C) -> Self
where
C: AnyDebugSendSync + 'static,
{
let mut inserted = false;
#[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
self.infos.retain_mut(|info| match info {
Info::Machine(ctx) => {
if let Some(content) =
(ctx.attachment.as_mut() as &mut (dyn Any + 'static)).downcast_mut::<C>()
{
if !inserted {
core::mem::swap(content, &mut context);
inserted = true;
true } else {
false }
} else {
true }
}
_ => true,
});
if !inserted {
self.infos.push(Info::Machine(MachineInfo { attachment: Box::new(context) }));
}
self
}
pub(crate) fn infos(&self) -> impl Iterator<Item = &'_ Info> {
self.infos.iter().rev()
}
pub(crate) fn contexts(&self) -> impl Iterator<Item = &'_ HumanInfo> {
self.infos().filter_map(|info| match info {
Info::Human(info) => Some(info),
_ => None,
})
}
pub fn attachments<C>(&self) -> impl Iterator<Item = &'_ C>
where
C: AnyDebugSendSync + 'static,
{
#[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
self.infos()
.filter_map(|info| match info {
Info::Machine(info) => Some(info),
_ => None,
}) .map(|ctx| ctx.attachment.as_ref() as &(dyn Any + 'static))
.filter_map(|ctx| ctx.downcast_ref())
}
#[must_use]
pub fn attachment<C>(&self) -> Option<&C>
where
C: AnyDebugSendSync + 'static,
{
self.attachments().next()
}
}
impl<M> From<NeuErr<M>> for NeuErrImpl {
#[inline]
fn from(err: NeuErr<M>) -> Self {
err.0
}
}
impl From<NeuErr<ProvideContext>> for NeuErr {
#[inline]
fn from(err: NeuErr<ProvideContext>) -> Self {
NeuErr(err.0, PhantomData)
}
}
#[diagnostic::do_not_recommend]
impl<E> From<E> for NeuErr
where
E: ErrorSendSync + 'static,
{
fn from(err: E) -> Self {
Self::from_source(err)
}
}
impl Error for NeuErrImpl {
#[inline]
fn source(&self) -> Option<&(dyn Error + 'static)> {
#[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
self.source.as_deref().map(|e| e as &(dyn Error + 'static))
}
}
impl<M> AsRef<dyn Error> for NeuErr<M> {
#[inline]
fn as_ref(&self) -> &(dyn Error + 'static) {
&self.0
}
}
#[cfg(feature = "send")]
impl<M> AsRef<dyn Error + Send> for NeuErr<M> {
#[inline]
fn as_ref(&self) -> &(dyn Error + Send + 'static) {
&self.0
}
}
#[cfg(all(feature = "send", feature = "sync"))]
impl<M> AsRef<dyn Error + Send + Sync> for NeuErr<M> {
#[inline]
fn as_ref(&self) -> &(dyn Error + Send + Sync + 'static) {
&self.0
}
}
impl<M> From<NeuErr<M>> for Box<dyn Error> {
#[inline]
fn from(this: NeuErr<M>) -> Self {
Box::new(this.into_error())
}
}
#[cfg(feature = "send")]
impl<M> From<NeuErr<M>> for Box<dyn Error + Send> {
#[inline]
fn from(this: NeuErr<M>) -> Self {
Box::new(this.into_error())
}
}
#[cfg(all(feature = "send", feature = "sync"))]
impl<M> From<NeuErr<M>> for Box<dyn Error + Send + Sync> {
#[inline]
fn from(this: NeuErr<M>) -> Self {
Box::new(this.into_error())
}
}
#[cfg(feature = "std")]
impl<M> std::process::Termination for NeuErr<M> {
fn report(self) -> std::process::ExitCode {
std::process::Termination::report(self.0)
}
}
#[cfg(feature = "std")]
impl std::process::Termination for NeuErrImpl {
fn report(self) -> std::process::ExitCode {
self.attachment::<std::process::ExitCode>()
.copied()
.unwrap_or(std::process::ExitCode::FAILURE)
}
}