Skip to content

Commit

Permalink
Merge pull request #5 from risingwavelabs/bz/report-debug
Browse files Browse the repository at this point in the history
  • Loading branch information
BugenZhao authored Mar 6, 2024
2 parents 1a08ab5 + a1bbbea commit 612a737
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 0 deletions.
18 changes: 18 additions & 0 deletions derive/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,24 @@ pub fn derive_macro(input: &DeriveInput) -> Result<TokenStream> {
Ok(generated)
}

pub fn derive_report_debug(input: &DeriveInput) -> Result<TokenStream> {
let input_type = input.ident.clone();

// 1. Delegate to `Debug` impl as the backtrace provided by the error
// could be different than where panic happens.
// 2. Passthrough the `alternate` flag.
let generated = quote!(
impl ::std::fmt::Debug for #input_type {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
use ::thiserror_ext::AsReport;
::std::fmt::Debug::fmt(&self.as_report(), f)
}
}
);

Ok(generated)
}

fn big_camel_case_to_snake_case(input: &str) -> String {
let mut output = String::new();

Expand Down
46 changes: 46 additions & 0 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(rustdoc::broken_intra_doc_links)]

//! Procedural macros for `thiserror_ext`.
use expand::{DeriveCtorType, DeriveNewType};
Expand Down Expand Up @@ -298,3 +300,47 @@ pub fn derive_arc(input: TokenStream) -> TokenStream {
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// Generates the [`Debug`] implementation that delegates to the [`Report`] of
/// an error.
///
/// Generally, the [`Debug`] representation of an error should not be used in
/// user-facing scenarios. However, if [`Result::unwrap`] or [`Result::expect`]
/// is called, or an error is used as [`Termination`], the standard library
/// will format the error with [`Debug`]. By delegating to [`Report`], we ensure
/// that the error is still formatted in a user-friendly way and the source
/// chain can be kept in these cases.
///
/// # Example
/// ```no_run
/// #[derive(thiserror::Error, thiserror_ext::ReportDebug)]
/// #[error("inner")]
/// struct Inner;
///
/// #[derive(thiserror::Error, thiserror_ext::ReportDebug)]
/// #[error("outer")]
/// struct Outer {
/// #[source]
/// inner: Inner,
/// }
///
/// let error = Outer { inner: Inner };
/// println!("{:?}", error);
/// ```
///
/// [`Report`]: thiserror_ext::Report
/// [`Termination`]: std::process::Termination
///
/// # New type
///
/// Since the new type delegates its [`Debug`] implementation to the original
/// error type, if the original error type derives [`ReportDebug`], the new type
/// will also behave the same.
#[proc_macro_derive(ReportDebug)]
pub fn derive_report_debug(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

expand::derive_report_debug(&input)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
42 changes: 42 additions & 0 deletions tests/report_debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use thiserror::Error;
use thiserror_ext::ReportDebug;

#[derive(Error, ReportDebug, Default)]
#[error("inner")]
struct Inner;

#[derive(Error, ReportDebug, Default)]
#[error("outer")]
struct Outer {
#[source]
inner: Inner,
}

#[test]
fn test_report_debug() {
let error = Outer::default();

expect_test::expect!["outer: inner"].assert_eq(&format!("{:?}", error));

expect_test::expect![[r#"
outer
Caused by:
inner
"#]]
.assert_eq(&format!("{:#?}", error));
}

#[test]
#[should_panic]
fn test_unwrap() {
let error = Outer::default();
let _ = Err::<(), _>(error).unwrap();
}

#[test]
#[should_panic]
fn test_expect() {
let error = Outer::default();
let _ = Err::<(), _>(error).expect("intentional panic");
}

0 comments on commit 612a737

Please sign in to comment.