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
11use crate::features::{AnyDebugSendSync, ErrorSendSync};
12
13#[derive(Debug)]
16pub(crate) struct HumanInfo {
17 pub(crate) message: Cow<'static, str>,
19 pub(crate) location: &'static Location<'static>,
21}
22
23#[derive(Debug)]
26pub(crate) struct MachineInfo {
27 pub(crate) attachment: Box<dyn AnyDebugSendSync>,
29}
30
31#[derive(Debug)]
34pub(crate) enum Info {
35 Human(HumanInfo),
37 Machine(MachineInfo),
39}
40const _: () = {
42 assert!(size_of::<Info>() == size_of::<HumanInfo>());
43};
44
45#[derive(Default)]
60pub struct CtxError(CtxErrorImpl);
61
62#[derive(Default)]
64pub struct CtxErrorImpl {
65 infos: Vec<Info>,
67 source: Option<Box<dyn ErrorSendSync>>,
69}
70
71impl Debug for CtxError {
72 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
73 Debug::fmt(&self.0, f)
74 }
75}
76
77impl Display for CtxError {
78 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
79 Display::fmt(&self.0, f)
80 }
81}
82
83impl Debug for CtxErrorImpl {
84 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
85 if f.alternate() {
86 f.debug_struct("CtxError")
87 .field("infos", &self.infos)
88 .field("source", &self.source)
89 .finish()
90 } else {
91 Display::fmt(self, f)
92 }
93 }
94}
95
96impl Display for CtxErrorImpl {
97 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
98 let mut human = self.contexts().peekable();
99 if human.peek().is_none() {
100 write!(f, "Unknown error")?;
101 }
102 while let Some(context) = human.next() {
103 if f.alternate() {
104 write!(f, "{} (at {})", context.message, context.location)?;
105 if human.peek().is_some() {
106 write!(f, "; ")?;
107 }
108 } else {
109 writeln!(f, "{}", context.message)?;
110 write!(f, "|- at {}", context.location)?;
111 if human.peek().is_some() {
112 writeln!(f)?;
113 writeln!(f, "|")?;
114 }
115 }
116 }
117
118 #[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
119 let mut source = self.source.as_deref().map(|e| e as &(dyn Error + 'static));
120 while let Some(err) = source {
121 if f.alternate() {
122 write!(f, "; caused by: {err}")?;
123 } else {
124 writeln!(f)?;
125 writeln!(f, "|")?;
126 write!(f, "|- caused by: {err}")?;
127 }
128
129 source = err.source();
130 }
131
132 Ok(())
133 }
134}
135
136impl CtxError {
137 #[track_caller]
139 #[must_use]
140 #[inline]
141 pub fn new<C>(context: C) -> Self
142 where
143 C: Into<Cow<'static, str>>,
144 {
145 let infos =
146 vec![Info::Human(HumanInfo { message: context.into(), location: Location::caller() })];
147 Self(CtxErrorImpl { infos, ..Default::default() })
148 }
149
150 #[track_caller]
152 #[must_use]
153 #[inline]
154 pub fn new_with_source<C, E>(context: C, source: E) -> Self
155 where
156 C: Into<Cow<'static, str>>,
157 E: ErrorSendSync + 'static,
158 {
159 let infos =
160 vec![Info::Human(HumanInfo { message: context.into(), location: Location::caller() })];
161 Self(CtxErrorImpl { infos, source: Some(Box::new(source)) })
162 }
163
164 #[must_use]
166 #[inline]
167 pub fn from_source<E>(source: E) -> Self
168 where
169 E: ErrorSendSync + 'static,
170 {
171 Self(CtxErrorImpl { source: Some(Box::new(source)), ..Default::default() })
172 }
173
174 #[track_caller]
176 #[must_use]
177 #[inline]
178 pub fn context<C>(self, context: C) -> Self
179 where
180 C: Into<Cow<'static, str>>,
181 {
182 Self(self.0.context(context))
183 }
184
185 #[must_use]
190 #[inline]
191 pub fn attach<C>(self, context: C) -> Self
192 where
193 C: AnyDebugSendSync + 'static,
194 {
195 Self(self.0.attach(context))
196 }
197
198 #[must_use]
203 pub fn attach_override<C>(self, context: C) -> Self
204 where
205 C: AnyDebugSendSync + 'static,
206 {
207 Self(self.0.attach_override(context))
208 }
209
210 #[inline]
212 #[cfg_attr(not(test), expect(unused, reason = "For consistency"))]
213 pub(crate) fn contexts(&self) -> impl Iterator<Item = &'_ HumanInfo> {
214 self.0.contexts()
215 }
216
217 #[inline]
219 pub fn attachments<C>(&self) -> impl Iterator<Item = &'_ C>
220 where
221 C: AnyDebugSendSync + 'static,
222 {
223 self.0.attachments()
224 }
225
226 #[must_use]
228 #[inline]
229 pub fn attachment<C>(&self) -> Option<&C>
230 where
231 C: AnyDebugSendSync + 'static,
232 {
233 self.0.attachment()
234 }
235
236 #[must_use]
238 #[inline]
239 pub fn source(&self) -> Option<&(dyn ErrorSendSync + 'static)> {
240 self.0.source.as_deref()
241 }
242
243 #[must_use]
247 #[inline]
248 pub fn into_error(self) -> CtxErrorImpl {
249 self.0
250 }
251}
252
253impl CtxErrorImpl {
254 #[must_use]
256 #[inline]
257 pub const fn wrap(self) -> CtxError {
258 CtxError(self)
259 }
260
261 #[track_caller]
263 #[must_use]
264 #[inline]
265 pub fn context<C>(mut self, context: C) -> Self
266 where
267 C: Into<Cow<'static, str>>,
268 {
269 let context = HumanInfo { message: context.into(), location: Location::caller() };
270 self.infos.push(Info::Human(context));
271 self
272 }
273
274 #[must_use]
279 #[inline]
280 pub fn attach<C>(mut self, context: C) -> Self
281 where
282 C: AnyDebugSendSync + 'static,
283 {
284 let context = MachineInfo { attachment: Box::new(context) };
285 self.infos.push(Info::Machine(context));
286 self
287 }
288
289 #[must_use]
294 pub fn attach_override<C>(mut self, mut context: C) -> Self
295 where
296 C: AnyDebugSendSync + 'static,
297 {
298 let mut inserted = false;
299 #[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
300 self.infos.retain_mut(|info| match info {
301 Info::Machine(ctx) => {
302 if let Some(content) =
303 (ctx.attachment.as_mut() as &mut (dyn Any + 'static)).downcast_mut::<C>()
304 {
305 if !inserted {
306 core::mem::swap(content, &mut context);
307 inserted = true;
308 true } else {
310 false }
312 } else {
313 true }
315 }
316 _ => true,
317 });
318 if !inserted {
319 self.infos.push(Info::Machine(MachineInfo { attachment: Box::new(context) }));
321 }
322 self
323 }
324
325 #[inline]
327 pub(crate) fn infos(&self) -> impl Iterator<Item = &'_ Info> {
328 self.infos.iter().rev()
329 }
330
331 #[inline]
333 pub(crate) fn contexts(&self) -> impl Iterator<Item = &'_ HumanInfo> {
334 self.infos().filter_map(|info| match info {
335 Info::Human(info) => Some(info),
336 _ => None,
337 })
338 }
339
340 #[inline]
342 pub fn attachments<C>(&self) -> impl Iterator<Item = &'_ C>
343 where
344 C: AnyDebugSendSync + 'static,
345 {
346 #[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
347 self.infos()
348 .filter_map(|info| match info {
349 Info::Machine(info) => Some(info),
350 _ => None,
351 }) .map(|ctx| ctx.attachment.as_ref() as &(dyn Any + 'static))
353 .filter_map(|ctx| ctx.downcast_ref())
354 }
355
356 #[must_use]
358 #[inline]
359 pub fn attachment<C>(&self) -> Option<&C>
360 where
361 C: AnyDebugSendSync + 'static,
362 {
363 self.attachments().next()
364 }
365}
366
367impl From<CtxError> for CtxErrorImpl {
368 #[inline]
369 fn from(err: CtxError) -> Self {
370 err.0
371 }
372}
373
374#[diagnostic::do_not_recommend]
375impl<E> From<E> for CtxError
376where
377 E: ErrorSendSync + 'static,
378{
379 #[inline]
380 fn from(err: E) -> Self {
381 Self::from_source(err)
382 }
383}
384
385impl Error for CtxErrorImpl {
386 #[inline]
387 fn source(&self) -> Option<&(dyn Error + 'static)> {
388 #[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
389 self.source.as_deref().map(|e| e as &(dyn Error + 'static))
390 }
391}
392
393impl AsRef<dyn Error> for CtxError {
394 #[inline]
395 fn as_ref(&self) -> &(dyn Error + 'static) {
396 &self.0
397 }
398}
399
400#[cfg(feature = "send")]
401impl AsRef<dyn Error + Send> for CtxError {
402 #[inline]
403 fn as_ref(&self) -> &(dyn Error + Send + 'static) {
404 &self.0
405 }
406}
407
408#[cfg(all(feature = "send", feature = "sync"))]
409impl AsRef<dyn Error + Send + Sync> for CtxError {
410 #[inline]
411 fn as_ref(&self) -> &(dyn Error + Send + Sync + 'static) {
412 &self.0
413 }
414}
415
416impl From<CtxError> for Box<dyn Error> {
417 #[inline]
418 fn from(this: CtxError) -> Self {
419 Box::new(this.into_error())
420 }
421}
422
423#[cfg(feature = "send")]
424impl From<CtxError> for Box<dyn Error + Send> {
425 #[inline]
426 fn from(this: CtxError) -> Self {
427 Box::new(this.into_error())
428 }
429}
430
431#[cfg(all(feature = "send", feature = "sync"))]
432impl From<CtxError> for Box<dyn Error + Send + Sync> {
433 #[inline]
434 fn from(this: CtxError) -> Self {
435 Box::new(this.into_error())
436 }
437}
438
439
440#[cfg(feature = "std")]
441impl std::process::Termination for CtxError {
442 #[inline]
443 fn report(self) -> std::process::ExitCode {
444 std::process::Termination::report(self.0)
445 }
446}
447
448#[cfg(feature = "std")]
449impl std::process::Termination for CtxErrorImpl {
450 #[inline]
451 fn report(self) -> std::process::ExitCode {
452 self.attachment::<std::process::ExitCode>()
453 .copied()
454 .unwrap_or(std::process::ExitCode::FAILURE)
455 }
456}