Skip to content

Commit

Permalink
Add Report for user-friendly error output [todo]
Browse files Browse the repository at this point in the history
[todo] rewrite feature flag docs
[todo] chain rust feature flag to lower version
  • Loading branch information
shepmaster committed Sep 28, 2022
1 parent d18ab69 commit 7b0f53c
Show file tree
Hide file tree
Showing 10 changed files with 506 additions and 1 deletion.
8 changes: 7 additions & 1 deletion Cargo.toml
Expand Up @@ -25,7 +25,7 @@ exclude = [
features = [ "std", "backtraces", "futures", "guide" ]

[features]
default = ["std", "rust_1_46"]
default = ["std", "rust_1_46", "rust_1_61"]

# Implement the `std::error::Error` trait.
std = []
Expand All @@ -36,6 +36,9 @@ unstable-core-error = []
# Add support for `#[track_caller]`
rust_1_46 = ["snafu-derive/rust_1_46"]

# Add support for `Termination` for `Report`
rust_1_61 = ["snafu-derive/rust_1_61"]

# Makes the backtrace type live
backtraces = ["std", "backtrace"]

Expand All @@ -49,6 +52,9 @@ unstable-backtraces-impl-std = ["backtraces", "snafu-derive/unstable-backtraces-
# The std::error::Error provider API will be implemented.
unstable-provider-api = ["snafu-derive/unstable-provider-api"]

# Add support for `FromResidual` for `Report`
unstable-try-trait = []

# The standard library's implementation of futures
futures = ["futures-core-crate", "pin-project"]

Expand Down
1 change: 1 addition & 0 deletions snafu-derive/Cargo.toml
Expand Up @@ -12,6 +12,7 @@ license = "MIT OR Apache-2.0"

[features]
rust_1_46 = []
rust_1_61 = []
unstable-backtraces-impl-std = []
unstable-provider-api = []

Expand Down
8 changes: 8 additions & 0 deletions snafu-derive/src/lib.rs
Expand Up @@ -19,6 +19,14 @@ pub fn snafu_derive(input: TokenStream) -> TokenStream {
impl_snafu_macro(ast)
}

mod report;
#[proc_macro_attribute]
pub fn report(attr: TokenStream, item: TokenStream) -> TokenStream {
report::body(attr, item)
.unwrap_or_else(|e| e.into_compile_error())
.into()
}

type MultiSynResult<T> = std::result::Result<T, Vec<syn::Error>>;

/// Some arbitrary tokens we treat as a black box
Expand Down
70 changes: 70 additions & 0 deletions snafu-derive/src/report.rs
@@ -0,0 +1,70 @@
use quote::quote;
use syn::{spanned::Spanned, Item};

pub fn body(
_attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> syn::Result<proc_macro2::TokenStream> {
let item = syn::parse::<Item>(item)?;

match item {
Item::Fn(f) => {
let syn::ItemFn {
attrs,
vis,
sig,
block,
} = f;

let syn::Signature {
constness,
asyncness,
unsafety,
abi,
fn_token,
ident,
generics,
paren_token: _,
inputs,
variadic,
output,
} = sig;

let output = match output {
syn::ReturnType::Default => quote! {},
syn::ReturnType::Type(_, ty) => {
if cfg!(feature = "rust_1_61") {
quote! {
-> ::snafu::Report<<#ty as ::snafu::__InternalExtractErrorType>::Err>
}
} else {
quote! {
-> ::core::result::Result<(), snafu::Report<<#ty as ::snafu::__InternalExtractErrorType>::Err>>
}
}
}
};

Ok(quote! {
#(#attrs)*
#vis
#constness
#asyncness
#unsafety
#abi
#fn_token
#ident
#generics
(#inputs #variadic)
#output
{
snafu::Report::capture(|| #block)
}
})
}
_ => Err(syn::Error::new(
item.span(),
"`#[snafu::report]` may only be used on functions",
)),
}
}
12 changes: 12 additions & 0 deletions src/guide/compatibility.md
Expand Up @@ -11,3 +11,15 @@ When enabled, SNAFU will assume that it's safe to target features
available in Rust 1.46. Notably, the `#[track_caller]` feature is
needed to allow [`Location`][crate::Location] to automatically discern
the source code location.

## `rust_1_61`

**default**: enabled

When enabled, SNAFU will assume that it's safe to target features
available in Rust 1.61. Notably, the [`Termination`][] trait is
implemented for [`Report`][] to allow it to be returned from `main`
and test functions.

[`Termination`]: std::process::Termination
[`Report`]: crate::Report
12 changes: 12 additions & 0 deletions src/guide/feature_flags.md
Expand Up @@ -12,6 +12,7 @@ cases:
- [`unstable-backtraces-impl-std`](#unstable-backtraces-impl-std)
- [`unstable-provider-api`](#unstable-provider-api)
- [`futures`](#futures)
- [`unstable-try-trait`](#unstable-try-trait)

[controlling compatibility]: super::guide::compatibility
[feature flags]: https://doc.rust-lang.org/stable/cargo/reference/specifying-dependencies.html#choosing-features
Expand Down Expand Up @@ -102,3 +103,14 @@ and streams returning `Result`s.

[`futures::TryFutureExt`]: crate::futures::TryFutureExt
[`futures::TryStreamExt`]: crate::futures::TryStreamExt

## `unstable-try-trait`

**default**: disabled

When enabled, the `?` operator can be used on [`Result`][] values in
functions where a [`Report`][] type is returned.

It is recommended that only applications make use of this feature.

[`Report`]: crate::Report
8 changes: 8 additions & 0 deletions src/lib.rs
Expand Up @@ -267,11 +267,19 @@ pub mod futures;
mod error_chain;
pub use crate::error_chain::*;

mod report;
pub use report::{Report, __InternalExtractErrorType};

doc_comment::doc_comment! {
include_str!("Snafu.md"),
pub use snafu_derive::Snafu;
}

doc_comment::doc_comment! {
include_str!("report.md"),
pub use snafu_derive::report;
}

macro_rules! generate_guide {
(pub mod $name:ident { $($children:tt)* } $($rest:tt)*) => {
generate_guide!(@gen ".", pub mod $name { $($children)* } $($rest)*);
Expand Down
76 changes: 76 additions & 0 deletions src/report.md
@@ -0,0 +1,76 @@
Adapts a function to provide user-friendly error output for `main`
functions and tests.

```rust,no_run
use snafu::prelude::*;
#[snafu::report]
fn main() -> Result<(), ApplicationError> {
let _v = may_fail_with_application_error()?;
Ok(())
}
#[derive(Debug, Snafu)]
#[snafu(display("Unable to frobnicate the mumbletypeg"))]
struct ApplicationError {
source: SpecificError
}
#[derive(Debug, Snafu)]
#[snafu(display("The mumbletypeg could not be found"))]
struct SpecificError {
backtrace: snafu::Backtrace,
}
fn may_fail_with_application_error() -> Result<u8, ApplicationError> {
may_fail_with_specific_error().context(ApplicationSnafu)
}
fn may_fail_with_specific_error() -> Result<u8, SpecificError> {
SpecificSnafu.fail()
}
```

When using `#[snafu::report]`, the output of running this program
may look like (backtrace edited for clarity and brevity):

```text
Unable to frobnicate the mumbletypeg
Caused by:
0: The mumbletypeg could not be found
Backtrace:
4 <snafu::backtrace_shim::Backtrace as snafu::GenerateImplicitData>::generate::h57208e368a5db816
/snafu/src/backtrace_shim.rs:15
example::SpecificSnafu::build::hff0254e61866cabb
example.rs:18
5 example::SpecificSnafu::fail::hec8a0999ec6ee527
example.rs:18
6 example::may_fail_with_specific_error::h271b4f6daa5d46aa
example.rs:29
7 example::may_fail_with_application_error::h20cbd2f6aead9af3
example.rs:25
8 example::main::{{closure}}::hc62fd2b706cb40f3
example.rs:7
9 snafu::report::Report<E>::capture::h81d9883cf934c7bb
/snafu/src/report.rs:132
10 example::main::hf53e4b9f8a221d64
example.rs:5
```

Contrast this to the default output produced when returning a
`Result`:

```text
Error: ApplicationError { source: DetailedError { backtrace: Backtrace(...same backtrace as above...) } }
```

This macro is syntax sugar for using [`snafu::Report`][]; please read
its documentation for detailed information, especially if you wish to
[see backtraces][] in the output.

[`snafu::Report`]: crate::Report
[see backtraces]: crate::Report#interaction-with-the-provider-api

0 comments on commit 7b0f53c

Please sign in to comment.