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