1#[cfg(feature = "backtrace")]
2use std::backtrace::Backtrace;
3use std::fmt::{self};
4
5use crate::{str_error::StrError, ErrorUnion};
6
7pub trait AnyError: std::any::Any + std::error::Error + Send + Sync + 'static {}
9
10impl<T> AnyError for T where T: std::error::Error + Send + Sync + 'static {}
11
12impl std::error::Error for Box<dyn AnyError> {}
13
14pub struct TracedError<T = Box<dyn AnyError>>
24where
25 T: AnyError,
26{
27 inner: T,
28 #[cfg(feature = "backtrace")]
29 pub(crate) backtrace: Backtrace,
30 #[cfg(feature = "context")]
31 pub(crate) context: Vec<StrError>,
32}
33
34impl TracedError {
35 pub fn boxed<E: AnyError>(source: E) -> Self {
37 TracedError::new(Box::new(source))
38 }
39}
40
41impl<T: AnyError> TracedError<T> {
42 pub fn new(source: T) -> Self {
43 Self {
44 inner: source,
45 #[cfg(feature = "backtrace")]
46 backtrace: Backtrace::capture(),
47 #[cfg(feature = "context")]
48 context: Vec::new(),
49 }
50 }
51
52 pub fn traced_dyn(self) -> TracedError {
54 debug_assert!(
55 std::any::TypeId::of::<T>() != std::any::TypeId::of::<Box<dyn AnyError>>(),
56 "traced_dyn() called on already boxed TracedError"
57 );
58
59 TracedError {
60 inner: Box::new(self.inner),
61 #[cfg(feature = "backtrace")]
62 backtrace: self.backtrace,
63 #[cfg(feature = "context")]
64 context: self.context,
65 }
66 }
67
68 pub fn into_inner(self) -> T {
70 self.inner
71 }
72
73 pub fn inner(&self) -> &T {
75 &self.inner
76 }
77
78 pub fn inner_mut(&mut self) -> &mut T {
80 &mut self.inner
81 }
82
83 pub fn map<U, F>(self, f: F) -> TracedError<U>
85 where
86 U: AnyError,
87 F: FnOnce(T) -> U,
88 {
89 TracedError {
90 inner: f(self.inner),
91 #[cfg(feature = "backtrace")]
92 backtrace: self.backtrace,
93 #[cfg(feature = "context")]
94 context: self.context,
95 }
96 }
97
98 #[allow(unused_mut)]
100 #[allow(unused_variables)]
101 pub fn context<C: Into<StrError>>(mut self, context: C) -> Self {
102 #[cfg(feature = "context")]
103 self.context.push(context.into());
104 self
105 }
106
107 #[allow(unused_mut)]
109 #[allow(unused_variables)]
110 pub fn with_context<F, C: Into<StrError>>(mut self, f: F) -> TracedError<T>
111 where
112 F: FnOnce() -> C,
113 {
114 #[cfg(feature = "context")]
115 self.context.push(f().into());
116 self
117 }
118
119 pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
124 self.inner.source()
125 }
126
127 }
154
155#[cfg(feature = "anyhow")]
158impl TracedError {
159 pub fn anyhow(error: anyhow::Error) -> TracedError {
160 let mut chain = error.chain();
161 let root = chain.next().unwrap().to_string();
162 #[cfg(feature = "backtrace")]
163 let (root, backtrace) = {
164 let backtrace: &Backtrace = error.backtrace();
165 if matches!(
166 backtrace.status(),
167 std::backtrace::BacktraceStatus::Captured
168 ) {
169 (
171 format!("{root}\n\nBacktrace:\n{}", backtrace.to_string()),
172 Backtrace::disabled(),
173 )
174 } else {
175 (root, Backtrace::capture())
176 }
177 };
178 let root = StrError::Owned(root);
179 #[cfg(feature = "context")]
180 let context = {
181 let mut context = Vec::new();
182 for link in chain {
183 context.push(StrError::Owned(link.to_string()));
184 }
185 context
186 };
187 TracedError {
188 inner: Box::new(root),
189 #[cfg(feature = "backtrace")]
190 backtrace,
191 #[cfg(feature = "context")]
192 context,
193 }
194 }
195}
196
197impl<T: AnyError> fmt::Display for TracedError<T> {
200 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
201 write!(formatter, "{}", self.inner)?;
202 Ok(())
203 }
204}
205
206impl<T: AnyError> fmt::Debug for TracedError<T> {
207 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
208 write!(formatter, "{}", self.inner)?;
209 #[cfg(feature = "context")]
210 {
211 if !self.context.is_empty() {
212 write!(formatter, "\n\nContext:")?;
213 for context_item in self.context.iter() {
214 write!(formatter, "\n\t- {}", context_item)?;
215 }
216 }
217 }
218 #[cfg(feature = "backtrace")]
219 {
220 use std::backtrace::BacktraceStatus;
221
222 if matches!(self.backtrace.status(), BacktraceStatus::Captured) {
223 write!(formatter, "\n\nBacktrace:\n")?;
224 fmt::Display::fmt(&self.backtrace, formatter)?;
225 }
226 }
227 Ok(())
228 }
229}
230
231fn _send_sync_error_assert() {
235 fn is_send<T: Send>(_: &T) {}
236 fn is_sync<T: Sync>(_: &T) {}
237 fn is_error<T: std::error::Error>(_: &T) {}
238
239 let traced_error: TracedError = crate::traced!("");
240 is_send(&traced_error);
241 is_sync(&traced_error);
242 is_error(&&traced_error);
244}
245
246impl<T: AnyError> std::error::Error for &TracedError<T> {
257 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
258 self.inner.source()
259 }
260}
261
262impl<T: AnyError> std::error::Error for &mut TracedError<T> {
263 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
264 self.inner.source()
265 }
266}
267
268impl<T: AnyError> From<T> for TracedError {
277 #[cfg(feature = "min_specialization")]
278 default fn from(e: T) -> Self {
279 TracedError::boxed(e)
280 }
281
282 #[cfg(not(feature = "min_specialization"))]
283 fn from(e: T) -> Self {
284 TracedError::boxed(e)
285 }
286}
287
288#[cfg(feature = "min_specialization")]
289impl From<Box<dyn AnyError + '_>> for TracedError {
290 fn from(e: Box<dyn AnyError>) -> Self {
291 TracedError::new(e)
292 }
293}
294
295pub trait TracedDyn<O2> {
299 fn traced_dyn(self) -> O2;
301}
302
303impl<E> TracedDyn<TracedError> for E
304where
305 E: AnyError,
306{
307 #[cfg(feature = "min_specialization")]
308 default fn traced_dyn(self) -> TracedError {
309 TracedError::new(Box::new(self))
310 }
311
312 #[cfg(not(feature = "min_specialization"))]
313 fn traced_dyn(self) -> TracedError {
314 TracedError::new(Box::new(self))
315 }
316}
317
318#[cfg(feature = "min_specialization")]
319impl TracedDyn<TracedError> for Box<dyn AnyError + '_> {
320 fn traced_dyn(self) -> TracedError {
321 TracedError::new(self)
322 }
323}
324
325impl<S, E> TracedDyn<Result<S, TracedError>> for Result<S, E>
326where
327 E: AnyError,
328{
329 fn traced_dyn(self) -> Result<S, TracedError> {
330 self.map_err(|e| e.traced_dyn())
331 }
332}
333
334impl<S, E: AnyError> TracedDyn<Result<S, TracedError>> for Result<S, TracedError<E>> {
335 #[cfg(feature = "min_specialization")]
336 default fn traced_dyn(self) -> Result<S, TracedError> {
337 self.map_err(|e| e.traced_dyn())
338 }
339
340 #[cfg(not(feature = "min_specialization"))]
341 fn traced_dyn(self) -> Result<S, TracedError> {
342 self.map_err(|e| e.traced_dyn())
343 }
344}
345
346#[cfg(feature = "min_specialization")]
347impl<S> TracedDyn<Result<S, TracedError<Box<dyn AnyError + '_>>>>
348 for Result<S, TracedError<Box<dyn AnyError + '_>>>
349{
350 fn traced_dyn(self) -> Result<S, TracedError> {
351 self
352 }
353}
354
355pub trait Traced<O1> {
359 fn traced(self) -> O1;
361}
362
363impl<E> Traced<TracedError<E>> for E
364where
365 E: AnyError,
366{
367 fn traced(self) -> TracedError<E> {
368 TracedError::new(self)
369 }
370}
371impl<S, E> Traced<Result<S, TracedError<E>>> for Result<S, E>
372where
373 E: AnyError,
374{
375 fn traced(self) -> Result<S, TracedError<E>> {
376 self.map_err(|e| e.traced())
377 }
378}
379
380impl<S, E> Traced<Result<S, TracedError<E>>> for Result<S, ErrorUnion<(TracedError<E>,)>>
390where
391 E: AnyError,
392{
393 fn traced(self) -> Result<S, TracedError<E>> {
394 self.map_err(|e| e.into_inner())
395 }
396}
397
398pub trait IntoTraced<O1> {
408 fn into_traced(self) -> O1;
410}
411
412impl<E1, E2> IntoTraced<TracedError<E2>> for E1
413where
414 E1: AnyError,
415 E2: AnyError,
416 E1: Into<E2>,
417{
418 fn into_traced(self) -> TracedError<E2> {
419 TracedError::new(self.into())
420 }
421}
422impl<S, E1, E2> IntoTraced<Result<S, TracedError<E2>>> for Result<S, E1>
423where
424 E1: AnyError,
425 E2: AnyError,
426 E1: Into<E2>,
427{
428 fn into_traced(self) -> Result<S, TracedError<E2>> {
429 self.map_err(|e| e.into_traced())
430 }
431}
432
433impl<S, E1, E2> IntoTraced<Result<S, TracedError<E2>>> for Result<S, TracedError<E1>>
434where
435 E1: AnyError,
436 E2: AnyError,
437 E1: Into<E2>,
438{
439 fn into_traced(self) -> Result<S, TracedError<E2>> {
440 self.map_err(|e| e.map(|e| e.into()))
441 }
442}
443
444impl<S, E> IntoTraced<Result<S, TracedError<E>>> for Result<S, ErrorUnion<(TracedError<E>,)>>
445where
446 E: AnyError,
447{
448 fn into_traced(self) -> Result<S, TracedError<E>> {
449 self.map_err(|e| e.into_inner())
450 }
451}
452
453#[cfg(feature = "min_specialization")]
487#[cfg(all(feature = "context", feature = "backtrace"))]
488#[cfg(test)]
489mod test {
490 use crate::{Context, ErrorUnion, StrError, TracedError};
491
492 #[test]
493 fn adding_context_to_union() {
494 let concrete_traced_error: TracedError<std::io::Error> = TracedError::new(
495 std::io::Error::new(std::io::ErrorKind::AddrInUse, "Address in use"),
496 );
497 let concrete_union_error: ErrorUnion<(
498 TracedError<std::io::Error>,
499 i32,
500 TracedError<StrError>,
501 )> = ErrorUnion::new(concrete_traced_error);
502 let result: Result<
503 (),
504 ErrorUnion<(TracedError<std::io::Error>, i32, TracedError<StrError>)>,
505 > = Err(concrete_union_error).context("Context 1");
506 let concrete_union_error = result.unwrap_err();
507 let concrete_traced_error: TracedError<std::io::Error> =
508 match concrete_union_error.to_enum() {
509 crate::E3::A(traced_error) => traced_error,
510 _ => panic!("Wrong type"),
511 };
512 assert_eq!(
513 concrete_traced_error.context,
514 vec![StrError::from("Context 1")]
515 );
516 }
517}