tracerr/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(
3    macro_use_extern_crate,
4    nonstandard_style,
5    rust_2018_idioms,
6    rustdoc::all,
7    trivial_casts,
8    trivial_numeric_casts
9)]
10#![forbid(non_ascii_idents, unsafe_code)]
11#![warn(
12    clippy::absolute_paths,
13    clippy::allow_attributes,
14    clippy::allow_attributes_without_reason,
15    clippy::as_conversions,
16    clippy::as_ptr_cast_mut,
17    clippy::assertions_on_result_states,
18    clippy::branches_sharing_code,
19    clippy::cfg_not_test,
20    clippy::clear_with_drain,
21    clippy::clone_on_ref_ptr,
22    clippy::collection_is_never_read,
23    clippy::create_dir,
24    clippy::dbg_macro,
25    clippy::debug_assert_with_mut_call,
26    clippy::decimal_literal_representation,
27    clippy::default_union_representation,
28    clippy::derive_partial_eq_without_eq,
29    clippy::else_if_without_else,
30    clippy::empty_drop,
31    clippy::empty_structs_with_brackets,
32    clippy::equatable_if_let,
33    clippy::empty_enum_variants_with_brackets,
34    clippy::exit,
35    clippy::expect_used,
36    clippy::fallible_impl_from,
37    clippy::filetype_is_file,
38    clippy::float_cmp_const,
39    clippy::fn_to_numeric_cast_any,
40    clippy::format_push_string,
41    clippy::get_unwrap,
42    clippy::if_then_some_else_none,
43    clippy::imprecise_flops,
44    clippy::infinite_loop,
45    clippy::iter_on_empty_collections,
46    clippy::iter_on_single_items,
47    clippy::iter_over_hash_type,
48    clippy::iter_with_drain,
49    clippy::large_include_file,
50    clippy::large_stack_frames,
51    clippy::let_underscore_untyped,
52    clippy::lossy_float_literal,
53    clippy::map_err_ignore,
54    clippy::map_with_unused_argument_over_ranges,
55    clippy::mem_forget,
56    clippy::missing_assert_message,
57    clippy::missing_asserts_for_indexing,
58    clippy::missing_const_for_fn,
59    clippy::missing_docs_in_private_items,
60    clippy::module_name_repetitions,
61    clippy::multiple_inherent_impl,
62    clippy::multiple_unsafe_ops_per_block,
63    clippy::mutex_atomic,
64    clippy::mutex_integer,
65    clippy::needless_collect,
66    clippy::needless_pass_by_ref_mut,
67    clippy::needless_raw_strings,
68    clippy::non_zero_suggestions,
69    clippy::nonstandard_macro_braces,
70    clippy::option_if_let_else,
71    clippy::or_fun_call,
72    clippy::panic_in_result_fn,
73    clippy::partial_pub_fields,
74    clippy::pathbuf_init_then_push,
75    clippy::pedantic,
76    clippy::print_stderr,
77    clippy::print_stdout,
78    clippy::pub_without_shorthand,
79    clippy::rc_buffer,
80    clippy::rc_mutex,
81    clippy::read_zero_byte_vec,
82    clippy::redundant_clone,
83    clippy::redundant_type_annotations,
84    clippy::renamed_function_params,
85    clippy::ref_patterns,
86    clippy::rest_pat_in_fully_bound_structs,
87    clippy::same_name_method,
88    clippy::semicolon_inside_block,
89    clippy::set_contains_or_insert,
90    clippy::shadow_unrelated,
91    clippy::significant_drop_in_scrutinee,
92    clippy::significant_drop_tightening,
93    clippy::str_to_string,
94    clippy::string_add,
95    clippy::string_lit_as_bytes,
96    clippy::string_lit_chars_any,
97    clippy::string_slice,
98    clippy::string_to_string,
99    clippy::suboptimal_flops,
100    clippy::suspicious_operation_groupings,
101    clippy::suspicious_xor_used_as_pow,
102    clippy::tests_outside_test_module,
103    clippy::todo,
104    clippy::too_long_first_doc_paragraph,
105    clippy::trailing_empty_array,
106    clippy::transmute_undefined_repr,
107    clippy::trivial_regex,
108    clippy::try_err,
109    clippy::undocumented_unsafe_blocks,
110    clippy::unimplemented,
111    clippy::uninhabited_references,
112    clippy::unnecessary_safety_comment,
113    clippy::unnecessary_safety_doc,
114    clippy::unnecessary_self_imports,
115    clippy::unnecessary_struct_initialization,
116    clippy::unneeded_field_pattern,
117    clippy::unused_peekable,
118    clippy::unused_result_ok,
119    clippy::unused_trait_names,
120    clippy::unwrap_in_result,
121    clippy::unwrap_used,
122    clippy::use_debug,
123    clippy::use_self,
124    clippy::useless_let_if_seq,
125    clippy::verbose_file_reads,
126    clippy::while_float,
127    clippy::wildcard_enum_match_arm,
128    explicit_outlives_requirements,
129    future_incompatible,
130    let_underscore_drop,
131    meta_variable_misuse,
132    missing_abi,
133    missing_copy_implementations,
134    missing_debug_implementations,
135    missing_docs,
136    redundant_lifetimes,
137    semicolon_in_expressions_from_macros,
138    single_use_lifetimes,
139    unit_bindings,
140    unnameable_types,
141    unreachable_pub,
142    unsafe_op_in_unsafe_fn,
143    unstable_features,
144    unused_crate_dependencies,
145    unused_extern_crates,
146    unused_import_braces,
147    unused_lifetimes,
148    unused_macro_rules,
149    unused_qualifications,
150    unused_results,
151    variant_size_differences
152)]
153
154mod trace;
155
156use std::{
157    error::Error,
158    sync::atomic::{AtomicUsize, Ordering},
159};
160
161use derive_more::with_trait::{AsMut, AsRef, Display};
162use sealed::sealed;
163
164#[doc(inline)]
165pub use self::trace::*;
166
167/// Default capacity for a [`Trace`] buffer initialization.
168///
169/// May be changed if your application requires larger size for better
170/// performance and re-allocation avoidance.
171pub static DEFAULT_FRAMES_CAPACITY: AtomicUsize = AtomicUsize::new(10);
172
173/// Wrapper for an arbitrary error holding the captured error trace along.
174#[derive(AsMut, AsRef, Clone, Debug, Display)]
175#[display("{err}")]
176pub struct Traced<E: ?Sized> {
177    /// Captured error trace.
178    trace: Trace,
179
180    /// Original error.
181    #[as_mut]
182    #[as_ref]
183    err: E,
184}
185
186impl<E: ?Sized> Traced<E> {
187    /// References to the captured [`Trace`].
188    ///
189    /// This is a raw equivalent of `AsRef<Trace>` (which cannot be implemented
190    /// at the moment due to the lack of specialization in Rust).
191    #[must_use]
192    pub const fn trace(&self) -> &Trace {
193        &self.trace
194    }
195}
196
197impl<E> Traced<E> {
198    /// Destructs this [`Traced`] wrapper returning only the underlying error
199    /// and loosing the captured [`Trace`].
200    #[must_use]
201    pub fn into_inner(self) -> E {
202        self.err
203    }
204
205    /// Splits this [`Traced`] wrapper into the underlying error and the
206    /// captured [`Trace`].
207    #[must_use]
208    pub fn split(self) -> (E, Trace) {
209        (self.err, self.trace)
210    }
211
212    /// Composes the given error and the captured [`Trace`] into a [`Traced`]
213    /// wrapper.
214    #[must_use]
215    pub const fn compose(error: E, trace: Trace) -> Self {
216        Self { err: error, trace }
217    }
218}
219
220impl<E> From<(E, Frame)> for Traced<E> {
221    fn from((err, f): (E, Frame)) -> Self {
222        err.wrap_traced(f)
223    }
224}
225
226impl<E> From<(E, Trace)> for Traced<E> {
227    fn from((err, trace): (E, Trace)) -> Self {
228        Self::compose(err, trace)
229    }
230}
231
232// TODO: Use when Rust will allow specialization... T_T
233/*
234impl<E> AsRef<Trace> for Traced<E> {
235    fn as_ref(&self) -> &Trace {
236        &self.trace
237    }
238}
239*/
240
241// TODO: Use `#[warn(clippy::missing_trait_methods)]` once its more clever about
242//       unstable methods.
243impl<E: Error + ?Sized> Error for Traced<E> {
244    fn source(&self) -> Option<&(dyn Error + 'static)> {
245        self.err.source()
246    }
247}
248
249/// Trait for wrapping errors into a [`Traced`] wrapper and growing its
250/// [`Trace`] inside.
251///
252/// # Sealed
253///
254/// This trait is exposed only for being available inside macro invocations,
255/// so, outside this library in any code the following MUST BE met:
256/// - NEITHER this trait is implemented directly;
257/// - NOR its methods are invoked directly.
258#[sealed]
259pub trait WrapTraced<E> {
260    /// Wraps this error into a [`Traced`] wrapper, storing the given [`Frame`]
261    /// of a [`Trace`] inside.
262    #[must_use]
263    fn wrap_traced(self, f: Frame) -> Traced<E>;
264}
265
266#[sealed]
267impl<E> WrapTraced<E> for E {
268    fn wrap_traced(self, f: Frame) -> Traced<Self> {
269        let mut trace = Trace::new(Vec::with_capacity(
270            DEFAULT_FRAMES_CAPACITY.load(Ordering::Relaxed),
271        ));
272        trace.push(f);
273        Traced { err: self, trace }
274    }
275}
276
277#[sealed]
278impl<E> WrapTraced<E> for Traced<E> {
279    /// Pushes the given [`Frame`] into the already captured [`Trace`] of this
280    /// [`Traced`] wrapper.
281    fn wrap_traced(mut self, f: Frame) -> Self {
282        self.trace.push(f);
283        self
284    }
285}
286
287// TODO: deprecate when Rust will allow specialization
288/// Maps an inner value of an error wrapped in a [`Traced`] with its [`From`]
289/// implementation.
290///
291/// This is an equivalent of
292/// `impl<E1, E2: From<E1>> From<Traced<E1>> for Traced<E2>`
293/// (which cannot be implemented at the moment due to the lack of specialization
294/// in Rust).
295#[must_use]
296pub fn map_from<F, T: From<F>>(e: Traced<F>) -> Traced<T> {
297    Traced {
298        err: T::from(e.err),
299        trace: e.trace,
300    }
301}
302
303// TODO: Use when Rust will allow specialization... T_T
304/*
305impl<E1, E2> From<Traced<E1>> for Traced<E2>
306where
307    E2: From<E1>,
308{
309    fn from(e: Traced<E1>) -> Self {
310        unimplemented!()
311    }
312}
313*/
314
315/// Captures a new [`Frame`] in the invocation place and wraps the given error
316/// into a [`Traced`] wrapper containing this [`Frame`].
317///
318/// If the error represents a [`Traced`] already, then just growths its
319/// [`Trace`] with the captured [`Frame`].
320///
321/// # Example
322///
323/// ```rust
324/// use tracerr::Traced;
325///
326/// let err: u32 = 89;
327/// let err: Traced<u32> = tracerr::new!(err);
328/// let err: Traced<u32> = tracerr::new!(err);
329/// ```
330#[macro_export]
331macro_rules! new {
332    ($e:expr) => {
333        $crate::WrapTraced::wrap_traced($e, $crate::new_frame!())
334    };
335}
336
337/// Captures a new [`Frame`] in the invocation place and wraps the given error
338/// into a [`Traced`] wrapper containing this [`Frame`] with applying the
339/// required [`From`] conversion for the wrapped error.
340///
341/// If the error represents a [`Traced`] already, then just applies [`From`]
342/// conversion and growths its [`Trace`] with the captured [`Frame`].
343///
344/// # Example
345///
346/// ```rust
347/// use tracerr::Traced;
348///
349/// let err: Traced<u8> = tracerr::new!(8);
350/// let err: Traced<u64> = tracerr::map_from_and_new!(err);
351/// ```
352#[macro_export]
353macro_rules! map_from_and_new {
354    ($e:expr) => {
355        $crate::new!($crate::map_from($e))
356    };
357}
358
359/// Provides a closure, which captures a new [`Frame`] in the invocation place
360/// and wraps the given error into a [`Traced`] wrapper containing this
361/// [`Frame`].
362///
363/// If the error represents a [`Traced`] already, then just growths its
364/// [`Trace`] with the captured [`Frame`].
365///
366/// # Example
367///
368/// ```rust
369/// use tracerr::Traced;
370///
371/// let res: Result<(), u32> = Err(89);
372/// let err: Traced<u32> = res
373///     .map_err(tracerr::wrap!())
374///     .map_err(tracerr::wrap!())
375///     .unwrap_err();
376/// ```
377#[macro_export]
378macro_rules! wrap {
379    () => ($crate::wrap!(_ => _));
380    ($from:ty) => ($crate::wrap!($from => _));
381    (=> $to:ty) => ($crate::wrap!(_ => $to));
382    ($from:ty => $to:ty) => {
383        |err: $from| -> $crate::Traced<$to> {
384            $crate::new!(err)
385        }
386    };
387}
388
389/// Provides a closure, which captures a new [`Frame`] in the invocation place
390/// for the given [`Traced`] wrapper and applies the required [`From`]
391/// conversion for the wrapped error.
392///
393/// # Examples
394///
395/// ```rust
396/// use tracerr::Traced;
397///
398/// let res: Result<(), Traced<u8>> = Err(tracerr::new!(7));
399/// let err: Traced<u64> =
400///     res.map_err(tracerr::map_from_and_wrap!()).unwrap_err();
401/// ```
402#[macro_export]
403macro_rules! map_from_and_wrap {
404    () => ($crate::map_from_and_wrap!(_ => _));
405    ($from:ty) => ($crate::map_from_and_wrap!($from => _));
406    (=> $to:ty) => ($crate::map_from_and_wrap!(_ => $to));
407    ($from:ty => $to:ty) => {
408        |err: $crate::Traced<$from>| -> $crate::Traced<$to> {
409            $crate::new!($crate::map_from::<$from, $to>(err))
410        }
411    };
412}
413
414/// Provides a closure, which captures a new [`Frame`] in the invocation place,
415/// applies the required [`From`] conversion for the given error, and wraps it
416/// into a [`Traced`] wrapper with this [`Frame`].
417///
418/// If the error represents a [`Traced`] already, then just growths its
419/// [`Trace`] with the captured [`Frame`].
420///
421/// # Example
422///
423/// ```rust
424/// use tracerr::Traced;
425///
426/// let res: Result<(), u8> = Err(7);
427/// let err: Traced<u64> = res.map_err(tracerr::from_and_wrap!()).unwrap_err();
428/// ```
429#[macro_export]
430macro_rules! from_and_wrap {
431    () => ($crate::from_and_wrap!(_ => _));
432    ($from:ty) => ($crate::from_and_wrap!($from => _));
433    (=> $to:ty) => ($crate::from_and_wrap!(_ => $to));
434    ($from:ty => $to:ty) => {
435        |err: $from| -> $crate::Traced<$to> {
436            $crate::map_from::<$from, $to>($crate::new!(err))
437        }
438    };
439}
440
441#[cfg(test)]
442mod new_macro_spec {
443    use super::Traced;
444
445    #[test]
446    fn creates_new_trace_frame_on_initialization() {
447        let err = super::new!(());
448        assert_eq!(err.trace.len(), 1, "creates initial frame");
449    }
450
451    #[test]
452    fn fills_trace_on_subsequent_calls() {
453        let err = super::new!(());
454        let err = super::new!(err);
455        let err = super::new!(err);
456        let err: Traced<()> = super::new!(err);
457        assert_eq!(err.trace.len(), 4, "trace growths with each call");
458    }
459}
460
461#[cfg(test)]
462mod map_from_and_new_macro_spec {
463    use super::Traced;
464
465    #[test]
466    fn fills_trace_on_subsequent_calls() {
467        let err = super::new!(());
468        let err = super::map_from_and_new!(err);
469        let err = super::map_from_and_new!(err);
470        let err: Traced<()> = super::map_from_and_new!(err);
471        assert_eq!(err.trace.len(), 4, "trace growths with each call");
472    }
473
474    #[test]
475    fn applies_required_from_implementation() {
476        let err = super::new!(4u8);
477        let err: Traced<u32> = super::map_from_and_new!(err);
478        assert!(!err.trace.is_empty(), "captures frames");
479    }
480}
481
482#[cfg(test)]
483mod wrap_macro_spec {
484    use super::Traced;
485
486    #[test]
487    fn creates_new_trace_frame_on_initialization() {
488        let res: Result<(), ()> = Err(());
489        let err = res.map_err(super::wrap!()).unwrap_err();
490        assert_eq!(err.trace.len(), 1, "creates initial frame");
491    }
492
493    #[test]
494    fn fills_trace_on_subsequent_calls() {
495        let res: Result<(), ()> = Err(());
496        let res = res.map_err(super::wrap!());
497        let res = res.map_err(super::wrap!());
498        let res = res.map_err(super::wrap!(Traced<_>));
499        let err = res.map_err(super::wrap!(=> ())).unwrap_err();
500        assert_eq!(err.trace.len(), 4, "trace growths with each call");
501    }
502}
503
504#[cfg(test)]
505mod map_from_and_wrap_macro_spec {
506    use super::Traced;
507
508    #[test]
509    fn fills_trace_on_subsequent_calls() {
510        let res: Result<(), ()> = Err(());
511        let res = res.map_err(super::wrap!());
512        let res = res.map_err(super::map_from_and_wrap!());
513        let res = res.map_err(super::map_from_and_wrap!());
514        let err = res.map_err(super::map_from_and_wrap!(=> ())).unwrap_err();
515        assert_eq!(err.trace.len(), 4, "trace growths with each call");
516    }
517
518    #[test]
519    fn applies_required_from_implementation() {
520        let res: Result<(), u8> = Err(54);
521        let res = res.map_err(super::wrap!());
522        let err: Traced<u64> =
523            res.map_err(super::map_from_and_wrap!()).unwrap_err();
524        assert!(!err.trace.is_empty(), "captures frames");
525    }
526}
527
528#[cfg(test)]
529mod from_and_wrap_macro_spec {
530    use super::Traced;
531
532    #[test]
533    fn fills_trace_on_subsequent_calls() {
534        let res: Result<(), ()> = Err(());
535        let res = res.map_err(super::wrap!());
536        let res = res.map_err(super::from_and_wrap!());
537        let res = res.map_err(super::from_and_wrap!());
538        let err = res.map_err(super::from_and_wrap!(=> ())).unwrap_err();
539        assert_eq!(err.trace.len(), 4, "trace growths with each call");
540    }
541
542    #[test]
543    fn applies_required_from_implementation() {
544        let res: Result<(), u8> = Err(54);
545        let err: Traced<u64> =
546            res.map_err(super::from_and_wrap!()).unwrap_err();
547        assert!(!err.trace.is_empty(), "captures frames");
548    }
549}