1use ::alloc::{borrow::Cow, boxed::Box, vec, vec::Vec};
4use ::core::{
5 any::Any,
6 error::Error,
7 fmt::{Debug, Display, Formatter, Result as FmtResult},
8 marker::PhantomData,
9 panic::Location,
10};
11#[cfg(feature = "colors")]
12use ::yansi::Paint;
13
14use crate::features::{AnyDebugSendSync, ErrorSendSync};
15
16#[derive(Debug)]
19pub(crate) struct HumanInfo {
20 pub(crate) message: Cow<'static, str>,
22 pub(crate) location: &'static Location<'static>,
24}
25
26#[derive(Debug)]
29pub(crate) struct MachineInfo {
30 pub(crate) attachment: Box<dyn AnyDebugSendSync>,
32}
33
34#[derive(Debug)]
37pub(crate) enum Info {
38 Human(HumanInfo),
40 Machine(MachineInfo),
42}
43const _: () = {
45 assert!(size_of::<Info>() == size_of::<HumanInfo>());
46};
47
48#[doc(hidden)]
50#[derive(Debug)]
51pub struct ProvideContext;
52
53#[derive(Default)]
68pub struct NeuErr<M = ()>(NeuErrImpl, PhantomData<M>);
69
70#[derive(Default)]
72pub struct NeuErrImpl {
73 infos: Vec<Info>,
75 source: Option<Box<dyn ErrorSendSync>>,
77}
78
79impl<M> Debug for NeuErr<M> {
80 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
81 Debug::fmt(&self.0, f)
82 }
83}
84
85impl<M> Display for NeuErr<M> {
86 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
87 Display::fmt(&self.0, f)
88 }
89}
90
91impl Debug for NeuErrImpl {
92 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
93 if f.alternate() {
94 f.debug_struct("NeuErr")
95 .field("infos", &self.infos)
96 .field("source", &self.source)
97 .finish()
98 } else {
99 Display::fmt(self, f)
100 }
101 }
102}
103
104impl Display for NeuErrImpl {
105 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
106 let mut human = self.contexts().peekable();
107 if human.peek().is_none() {
108 #[cfg(feature = "colors")]
109 let unknown = "Unknown error".red();
110 #[cfg(not(feature = "colors"))]
111 let unknown = "Unknown error";
112
113 write!(f, "{unknown}")?;
114 }
115 while let Some(context) = human.next() {
116 #[cfg(feature = "colors")]
117 let message = context.message.as_ref().red();
118 #[cfg(not(feature = "colors"))]
119 let message = context.message.as_ref();
120
121 #[cfg(feature = "colors")]
122 let location = context.location.rgb(0x90, 0x90, 0x90);
123 #[cfg(not(feature = "colors"))]
124 let location = context.location;
125
126 if f.alternate() {
127 write!(f, "{message} (at {location})")?;
128 if human.peek().is_some() {
129 write!(f, "; ")?;
130 }
131 } else {
132 writeln!(f, "{message}")?;
133 write!(f, "|- at {location}")?;
134 if human.peek().is_some() {
135 writeln!(f)?;
136 writeln!(f, "|")?;
137 }
138 }
139 }
140
141 #[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
142 let mut source = self.source.as_deref().map(|e| e as &(dyn Error + 'static));
143 while let Some(err) = source {
144 #[cfg(feature = "colors")]
145 let error = err.red();
146 #[cfg(not(feature = "colors"))]
147 let error = err;
148
149 if f.alternate() {
150 write!(f, "; caused by: {error}")?;
151 } else {
152 writeln!(f)?;
153 writeln!(f, "|")?;
154 write!(f, "|- caused by: {error}")?;
155 }
156
157 source = err.source();
158 }
159
160 Ok(())
161 }
162}
163
164impl NeuErr<ProvideContext> {
165 #[track_caller]
167 #[must_use]
168 pub fn new<C>(context: C) -> Self
169 where
170 C: Into<Cow<'static, str>>,
171 {
172 let infos =
173 vec![Info::Human(HumanInfo { message: context.into(), location: Location::caller() })];
174 Self(NeuErrImpl { infos, ..Default::default() }, PhantomData)
175 }
176
177 #[track_caller]
179 #[must_use]
180 pub fn new_with_source<C, E>(context: C, source: E) -> Self
181 where
182 C: Into<Cow<'static, str>>,
183 E: ErrorSendSync + 'static,
184 {
185 let infos =
186 vec![Info::Human(HumanInfo { message: context.into(), location: Location::caller() })];
187 Self(NeuErrImpl { infos, source: Some(Box::new(source)) }, PhantomData)
188 }
189}
190
191impl NeuErr {
192 #[must_use]
194 pub fn from_source<E>(source: E) -> Self
195 where
196 E: ErrorSendSync + 'static,
197 {
198 Self(NeuErrImpl { source: Some(Box::new(source)), ..Default::default() }, PhantomData)
199 }
200}
201
202impl<M> NeuErr<M> {
203 #[track_caller]
205 #[must_use]
206 pub fn context<C>(self, context: C) -> NeuErr<ProvideContext>
207 where
208 C: Into<Cow<'static, str>>,
209 {
210 NeuErr(self.0.context(context), PhantomData)
211 }
212
213 #[must_use]
218 pub fn attach<C>(self, context: C) -> Self
219 where
220 C: AnyDebugSendSync + 'static,
221 {
222 Self(self.0.attach(context), PhantomData)
223 }
224
225 #[must_use]
230 pub fn attach_override<C>(self, context: C) -> Self
231 where
232 C: AnyDebugSendSync + 'static,
233 {
234 Self(self.0.attach_override(context), PhantomData)
235 }
236
237 #[cfg_attr(not(test), expect(unused, reason = "For consistency"))]
239 pub(crate) fn contexts(&self) -> impl Iterator<Item = &'_ HumanInfo> {
240 self.0.contexts()
241 }
242
243 pub fn attachments<C>(&self) -> impl Iterator<Item = &'_ C>
245 where
246 C: AnyDebugSendSync + 'static,
247 {
248 self.0.attachments()
249 }
250
251 #[must_use]
253 pub fn attachment<C>(&self) -> Option<&C>
254 where
255 C: AnyDebugSendSync + 'static,
256 {
257 self.0.attachment()
258 }
259
260 #[must_use]
262 pub fn source(&self) -> Option<&(dyn ErrorSendSync + 'static)> {
263 self.0.source.as_deref()
264 }
265
266 #[must_use]
270 #[inline]
271 pub fn into_error(self) -> NeuErrImpl {
272 self.0
273 }
274
275 #[must_use]
277 #[inline]
278 pub fn remove_marker(self) -> NeuErr {
279 NeuErr(self.0, PhantomData)
280 }
281}
282
283impl NeuErrImpl {
284 #[must_use]
286 #[inline]
287 pub const fn wrap(self) -> NeuErr {
288 NeuErr(self, PhantomData)
289 }
290
291 #[track_caller]
293 #[must_use]
294 pub fn context<C>(mut self, context: C) -> Self
295 where
296 C: Into<Cow<'static, str>>,
297 {
298 let context = HumanInfo { message: context.into(), location: Location::caller() };
299 self.infos.push(Info::Human(context));
300 self
301 }
302
303 #[must_use]
308 pub fn attach<C>(mut self, context: C) -> Self
309 where
310 C: AnyDebugSendSync + 'static,
311 {
312 let context = MachineInfo { attachment: Box::new(context) };
313 self.infos.push(Info::Machine(context));
314 self
315 }
316
317 #[must_use]
322 pub fn attach_override<C>(mut self, mut context: C) -> Self
323 where
324 C: AnyDebugSendSync + 'static,
325 {
326 let mut inserted = false;
327 #[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
328 self.infos.retain_mut(|info| match info {
329 Info::Machine(ctx) => {
330 if let Some(content) =
331 (ctx.attachment.as_mut() as &mut (dyn Any + 'static)).downcast_mut::<C>()
332 {
333 if !inserted {
334 core::mem::swap(content, &mut context);
335 inserted = true;
336 true } else {
338 false }
340 } else {
341 true }
343 }
344 _ => true,
345 });
346 if !inserted {
347 self.infos.push(Info::Machine(MachineInfo { attachment: Box::new(context) }));
349 }
350 self
351 }
352
353 pub(crate) fn infos(&self) -> impl Iterator<Item = &'_ Info> {
355 self.infos.iter().rev()
356 }
357
358 pub(crate) fn contexts(&self) -> impl Iterator<Item = &'_ HumanInfo> {
360 self.infos().filter_map(|info| match info {
361 Info::Human(info) => Some(info),
362 _ => None,
363 })
364 }
365
366 pub fn attachments<C>(&self) -> impl Iterator<Item = &'_ C>
368 where
369 C: AnyDebugSendSync + 'static,
370 {
371 #[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
372 self.infos()
373 .filter_map(|info| match info {
374 Info::Machine(info) => Some(info),
375 _ => None,
376 }) .map(|ctx| ctx.attachment.as_ref() as &(dyn Any + 'static))
378 .filter_map(|ctx| ctx.downcast_ref())
379 }
380
381 #[must_use]
383 pub fn attachment<C>(&self) -> Option<&C>
384 where
385 C: AnyDebugSendSync + 'static,
386 {
387 self.attachments().next()
388 }
389}
390
391impl<M> From<NeuErr<M>> for NeuErrImpl {
392 #[inline]
393 fn from(err: NeuErr<M>) -> Self {
394 err.0
395 }
396}
397
398impl From<NeuErr<ProvideContext>> for NeuErr {
399 #[inline]
400 fn from(err: NeuErr<ProvideContext>) -> Self {
401 NeuErr(err.0, PhantomData)
402 }
403}
404
405#[diagnostic::do_not_recommend]
406impl<E> From<E> for NeuErr
407where
408 E: ErrorSendSync + 'static,
409{
410 fn from(err: E) -> Self {
411 Self::from_source(err)
412 }
413}
414
415impl Error for NeuErrImpl {
416 #[inline]
417 fn source(&self) -> Option<&(dyn Error + 'static)> {
418 #[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
419 self.source.as_deref().map(|e| e as &(dyn Error + 'static))
420 }
421}
422
423impl<M> AsRef<dyn Error> for NeuErr<M> {
424 #[inline]
425 fn as_ref(&self) -> &(dyn Error + 'static) {
426 &self.0
427 }
428}
429
430#[cfg(feature = "send")]
431impl<M> AsRef<dyn Error + Send> for NeuErr<M> {
432 #[inline]
433 fn as_ref(&self) -> &(dyn Error + Send + 'static) {
434 &self.0
435 }
436}
437
438#[cfg(all(feature = "send", feature = "sync"))]
439impl<M> AsRef<dyn Error + Send + Sync> for NeuErr<M> {
440 #[inline]
441 fn as_ref(&self) -> &(dyn Error + Send + Sync + 'static) {
442 &self.0
443 }
444}
445
446impl<M> From<NeuErr<M>> for Box<dyn Error> {
447 #[inline]
448 fn from(this: NeuErr<M>) -> Self {
449 Box::new(this.into_error())
450 }
451}
452
453#[cfg(feature = "send")]
454impl<M> From<NeuErr<M>> for Box<dyn Error + Send> {
455 #[inline]
456 fn from(this: NeuErr<M>) -> Self {
457 Box::new(this.into_error())
458 }
459}
460
461#[cfg(all(feature = "send", feature = "sync"))]
462impl<M> From<NeuErr<M>> for Box<dyn Error + Send + Sync> {
463 #[inline]
464 fn from(this: NeuErr<M>) -> Self {
465 Box::new(this.into_error())
466 }
467}
468
469
470#[cfg(feature = "std")]
471impl<M> std::process::Termination for NeuErr<M> {
472 fn report(self) -> std::process::ExitCode {
473 std::process::Termination::report(self.0)
474 }
475}
476
477#[cfg(feature = "std")]
478impl std::process::Termination for NeuErrImpl {
479 fn report(self) -> std::process::ExitCode {
480 self.attachment::<std::process::ExitCode>()
481 .copied()
482 .unwrap_or(std::process::ExitCode::FAILURE)
483 }
484}