#![cfg_attr(any(doc, test), doc = include_str!("../README.md"))]
#![cfg_attr(not(any(doc, test)), doc = env!("CARGO_PKG_NAME"))]
#![deny(nonstandard_style, rustdoc::all, trivial_casts, trivial_numeric_casts)]
#![forbid(non_ascii_idents, unsafe_code)]
#![warn(
clippy::absolute_paths,
clippy::allow_attributes,
clippy::allow_attributes_without_reason,
clippy::as_conversions,
clippy::as_pointer_underscore,
clippy::as_ptr_cast_mut,
clippy::assertions_on_result_states,
clippy::branches_sharing_code,
clippy::cfg_not_test,
clippy::clear_with_drain,
clippy::clone_on_ref_ptr,
clippy::coerce_container_to_any,
clippy::collection_is_never_read,
clippy::create_dir,
clippy::dbg_macro,
clippy::debug_assert_with_mut_call,
clippy::decimal_literal_representation,
clippy::default_union_representation,
clippy::derive_partial_eq_without_eq,
clippy::doc_include_without_cfg,
clippy::doc_paragraphs_missing_punctuation,
clippy::empty_drop,
clippy::empty_structs_with_brackets,
clippy::equatable_if_let,
clippy::empty_enum_variants_with_brackets,
clippy::exit,
clippy::expect_used,
clippy::fallible_impl_from,
clippy::filetype_is_file,
clippy::float_cmp_const,
clippy::fn_to_numeric_cast_any,
clippy::get_unwrap,
clippy::if_then_some_else_none,
clippy::imprecise_flops,
clippy::infinite_loop,
clippy::iter_on_empty_collections,
clippy::iter_on_single_items,
clippy::iter_over_hash_type,
clippy::iter_with_drain,
clippy::large_include_file,
clippy::large_stack_frames,
clippy::let_underscore_untyped,
clippy::literal_string_with_formatting_args,
clippy::lossy_float_literal,
clippy::map_err_ignore,
clippy::map_with_unused_argument_over_ranges,
clippy::mem_forget,
clippy::missing_assert_message,
clippy::missing_asserts_for_indexing,
clippy::missing_const_for_fn,
clippy::missing_docs_in_private_items,
clippy::module_name_repetitions,
clippy::multiple_inherent_impl,
clippy::multiple_unsafe_ops_per_block,
clippy::mutex_atomic,
clippy::mutex_integer,
clippy::needless_collect,
clippy::needless_pass_by_ref_mut,
clippy::needless_raw_strings,
clippy::needless_type_cast,
clippy::non_zero_suggestions,
clippy::nonstandard_macro_braces,
clippy::option_if_let_else,
clippy::or_fun_call,
clippy::panic_in_result_fn,
clippy::partial_pub_fields,
clippy::pathbuf_init_then_push,
clippy::pedantic,
clippy::precedence_bits,
clippy::print_stderr,
clippy::print_stdout,
clippy::pub_without_shorthand,
clippy::rc_buffer,
clippy::rc_mutex,
clippy::read_zero_byte_vec,
clippy::redundant_clone,
clippy::redundant_test_prefix,
clippy::redundant_type_annotations,
clippy::renamed_function_params,
clippy::ref_patterns,
clippy::rest_pat_in_fully_bound_structs,
clippy::return_and_then,
clippy::same_name_method,
clippy::semicolon_inside_block,
clippy::set_contains_or_insert,
clippy::shadow_unrelated,
clippy::significant_drop_in_scrutinee,
clippy::significant_drop_tightening,
clippy::single_option_map,
clippy::str_to_string,
clippy::string_add,
clippy::string_lit_as_bytes,
clippy::string_lit_chars_any,
clippy::string_slice,
clippy::suboptimal_flops,
clippy::suspicious_operation_groupings,
clippy::suspicious_xor_used_as_pow,
clippy::tests_outside_test_module,
clippy::todo,
clippy::too_long_first_doc_paragraph,
clippy::trailing_empty_array,
clippy::transmute_undefined_repr,
clippy::trivial_regex,
clippy::try_err,
clippy::undocumented_unsafe_blocks,
clippy::unimplemented,
clippy::uninhabited_references,
clippy::unnecessary_safety_comment,
clippy::unnecessary_safety_doc,
clippy::unnecessary_self_imports,
clippy::unnecessary_struct_initialization,
clippy::unused_peekable,
clippy::unused_result_ok,
clippy::unused_trait_names,
clippy::unwrap_in_result,
clippy::unwrap_used,
clippy::use_debug,
clippy::use_self,
clippy::useless_let_if_seq,
clippy::verbose_file_reads,
clippy::volatile_composites,
clippy::while_float,
clippy::wildcard_enum_match_arm,
ambiguous_negative_literals,
closure_returning_async_block,
future_incompatible,
impl_trait_redundant_captures,
let_underscore_drop,
macro_use_extern_crate,
meta_variable_misuse,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
redundant_lifetimes,
rust_2018_idioms,
single_use_lifetimes,
unit_bindings,
unnameable_types,
unreachable_pub,
unstable_features,
unused,
variant_size_differences
)]
mod trace;
use std::{
error::Error,
sync::atomic::{AtomicUsize, Ordering},
};
use derive_more::with_trait::{AsMut, AsRef, Display};
use sealed::sealed;
#[doc(inline)]
pub use self::trace::*;
pub static DEFAULT_FRAMES_CAPACITY: AtomicUsize = AtomicUsize::new(10);
#[derive(AsMut, AsRef, Clone, Debug, Display)]
#[display("{err}")]
pub struct Traced<E: ?Sized> {
trace: Trace,
#[as_mut]
#[as_ref]
err: E,
}
impl<E: ?Sized> Traced<E> {
#[must_use]
pub const fn trace(&self) -> &Trace {
&self.trace
}
}
impl<E> Traced<E> {
#[must_use]
pub fn into_inner(self) -> E {
self.err
}
#[must_use]
pub fn split(self) -> (E, Trace) {
(self.err, self.trace)
}
#[must_use]
pub const fn compose(error: E, trace: Trace) -> Self {
Self { err: error, trace }
}
}
impl<E> From<(E, Frame)> for Traced<E> {
fn from((err, f): (E, Frame)) -> Self {
err.wrap_traced(f)
}
}
impl<E> From<(E, Trace)> for Traced<E> {
fn from((err, trace): (E, Trace)) -> Self {
Self::compose(err, trace)
}
}
impl<E: Error + ?Sized> Error for Traced<E> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.err.source()
}
}
#[sealed]
pub trait WrapTraced<E> {
#[must_use]
fn wrap_traced(self, f: Frame) -> Traced<E>;
}
#[sealed]
impl<E> WrapTraced<E> for E {
fn wrap_traced(self, f: Frame) -> Traced<Self> {
let mut trace = Trace::new(Vec::with_capacity(
DEFAULT_FRAMES_CAPACITY.load(Ordering::Relaxed),
));
trace.push(f);
Traced { err: self, trace }
}
}
#[sealed]
impl<E> WrapTraced<E> for Traced<E> {
fn wrap_traced(mut self, f: Frame) -> Self {
self.trace.push(f);
self
}
}
#[must_use]
pub fn map_from<F, T: From<F>>(e: Traced<F>) -> Traced<T> {
Traced { err: T::from(e.err), trace: e.trace }
}
#[macro_export]
macro_rules! new {
($e:expr) => {
$crate::WrapTraced::wrap_traced($e, $crate::new_frame!())
};
}
#[macro_export]
macro_rules! map_from_and_new {
($e:expr) => {
$crate::new!($crate::map_from($e))
};
}
#[macro_export]
macro_rules! wrap {
() => ($crate::wrap!(_ => _));
($from:ty) => ($crate::wrap!($from => _));
(=> $to:ty) => ($crate::wrap!(_ => $to));
($from:ty => $to:ty) => {
|err: $from| -> $crate::Traced<$to> {
$crate::new!(err)
}
};
}
#[macro_export]
macro_rules! map_from_and_wrap {
() => ($crate::map_from_and_wrap!(_ => _));
($from:ty) => ($crate::map_from_and_wrap!($from => _));
(=> $to:ty) => ($crate::map_from_and_wrap!(_ => $to));
($from:ty => $to:ty) => {
|err: $crate::Traced<$from>| -> $crate::Traced<$to> {
$crate::new!($crate::map_from::<$from, $to>(err))
}
};
}
#[macro_export]
macro_rules! from_and_wrap {
() => ($crate::from_and_wrap!(_ => _));
($from:ty) => ($crate::from_and_wrap!($from => _));
(=> $to:ty) => ($crate::from_and_wrap!(_ => $to));
($from:ty => $to:ty) => {
|err: $from| -> $crate::Traced<$to> {
$crate::map_from::<$from, $to>($crate::new!(err))
}
};
}
#[cfg(test)]
mod new_macro_spec {
use super::Traced;
#[test]
fn creates_new_trace_frame_on_initialization() {
let err = super::new!(());
assert_eq!(err.trace.len(), 1, "creates initial frame");
}
#[test]
fn fills_trace_on_subsequent_calls() {
let err = super::new!(());
let err = super::new!(err);
let err = super::new!(err);
let err: Traced<()> = super::new!(err);
assert_eq!(err.trace.len(), 4, "trace growths with each call");
}
}
#[cfg(test)]
mod map_from_and_new_macro_spec {
use super::Traced;
#[test]
fn fills_trace_on_subsequent_calls() {
let err = super::new!(());
let err = super::map_from_and_new!(err);
let err = super::map_from_and_new!(err);
let err: Traced<()> = super::map_from_and_new!(err);
assert_eq!(err.trace.len(), 4, "trace growths with each call");
}
#[test]
fn applies_required_from_implementation() {
let err = super::new!(4u8);
let err: Traced<u32> = super::map_from_and_new!(err);
assert!(!err.trace.is_empty(), "captures frames");
}
}
#[cfg(test)]
mod wrap_macro_spec {
use super::Traced;
#[test]
fn creates_new_trace_frame_on_initialization() {
let res: Result<(), ()> = Err(());
let err = res.map_err(super::wrap!()).unwrap_err();
assert_eq!(err.trace.len(), 1, "creates initial frame");
}
#[test]
fn fills_trace_on_subsequent_calls() {
let res: Result<(), ()> = Err(());
let res = res.map_err(super::wrap!());
let res = res.map_err(super::wrap!());
let res = res.map_err(super::wrap!(Traced<_>));
let err = res.map_err(super::wrap!(=> ())).unwrap_err();
assert_eq!(err.trace.len(), 4, "trace growths with each call");
}
}
#[cfg(test)]
mod map_from_and_wrap_macro_spec {
use super::Traced;
#[test]
fn fills_trace_on_subsequent_calls() {
let res: Result<(), ()> = Err(());
let res = res.map_err(super::wrap!());
let res = res.map_err(super::map_from_and_wrap!());
let res = res.map_err(super::map_from_and_wrap!());
let err = res.map_err(super::map_from_and_wrap!(=> ())).unwrap_err();
assert_eq!(err.trace.len(), 4, "trace growths with each call");
}
#[test]
fn applies_required_from_implementation() {
let res: Result<(), u8> = Err(54);
let res = res.map_err(super::wrap!());
let err: Traced<u64> =
res.map_err(super::map_from_and_wrap!()).unwrap_err();
assert!(!err.trace.is_empty(), "captures frames");
}
}
#[cfg(test)]
mod from_and_wrap_macro_spec {
use super::Traced;
#[test]
fn fills_trace_on_subsequent_calls() {
let res: Result<(), ()> = Err(());
let res = res.map_err(super::wrap!());
let res = res.map_err(super::from_and_wrap!());
let res = res.map_err(super::from_and_wrap!());
let err = res.map_err(super::from_and_wrap!(=> ())).unwrap_err();
assert_eq!(err.trace.len(), 4, "trace growths with each call");
}
#[test]
fn applies_required_from_implementation() {
let res: Result<(), u8> = Err(54);
let err: Traced<u64> =
res.map_err(super::from_and_wrap!()).unwrap_err();
assert!(!err.trace.is_empty(), "captures frames");
}
}