diff --git a/snafu-derive/src/lib.rs b/snafu-derive/src/lib.rs index 99568737..26dcc63b 100644 --- a/snafu-derive/src/lib.rs +++ b/snafu-derive/src/lib.rs @@ -117,6 +117,7 @@ fn parse_snafu_enum( match attr { SnafuAttribute::Display(d) => display_format = Some(d), SnafuAttribute::Visibility(v) => visibility = Some(v), + SnafuAttribute::Source(..) => { /* Report this isn't valid here? */ } SnafuAttribute::Backtrace => { /* Report this isn't valid here? */ } } } @@ -147,11 +148,23 @@ fn parse_snafu_enum( ty: syn_field.ty, }; - let has_backtrace = attributes_from_syn(syn_field.attrs)? - .iter() - .any(SnafuAttribute::is_backtrace); + let mut has_backtrace = false; + let mut is_source = None; + + for attr in attributes_from_syn(syn_field.attrs)? { + match attr { + SnafuAttribute::Source(s) => match s { + Source::Flag(v) => is_source = Some(v), + }, + SnafuAttribute::Backtrace => has_backtrace = true, + SnafuAttribute::Visibility(_) => { /* Report this isn't valid here? */ } + SnafuAttribute::Display(_) => { /* Report this isn't valid here? */ } + } + } + + let is_source = is_source.unwrap_or(field.name == "source"); - if field.name == "source" { + if is_source { if has_backtrace { backtrace_delegates.push(field.clone()); } @@ -310,9 +323,22 @@ impl syn::parse::Parse for MyExprList { } } +enum Source { + Flag(bool), +} + +impl syn::parse::Parse for Source { + fn parse(input: syn::parse::ParseStream) -> SynResult { + use syn::LitBool; + let val: LitBool = input.parse()?; + Ok(Source::Flag(val.value)) + } +} + enum SnafuAttribute { Display(UserInput), Visibility(UserInput), + Source(Source), Backtrace, } @@ -323,13 +349,6 @@ impl SnafuAttribute { _ => None, } } - - fn is_backtrace(&self) -> bool { - match *self { - SnafuAttribute::Backtrace => true, - _ => false, - } - } } impl syn::parse::Parse for SnafuAttribute { @@ -353,13 +372,20 @@ impl syn::parse::Parse for SnafuAttribute { .into_option() .map_or_else(private_visibility, |v| Box::new(v) as UserInput); Ok(SnafuAttribute::Visibility(v)) + } else if name == "source" { + if inside.is_empty() { + Ok(SnafuAttribute::Source(Source::Flag(true))) + } else { + let v: MyParens = inside.parse()?; + Ok(SnafuAttribute::Source(v.0)) + } } else if name == "backtrace" { let _: MyParens = inside.parse()?; Ok(SnafuAttribute::Backtrace) } else { Err(SynError::new( name.span(), - "expected `display`, `visibility`, or `backtrace`", + "expected `display`, `visibility`, `source`, or `backtrace`", )) } } diff --git a/src/guide/attributes.rs b/src/guide/attributes.rs index 21b1012c..0d91fc7e 100644 --- a/src/guide/attributes.rs +++ b/src/guide/attributes.rs @@ -56,6 +56,34 @@ //! } //! ``` //! +//! ## Controlling error sources +//! +//! If your error enum variant contains other errors but the field +//! cannot be named `source`, or if it contains a field named `source` +//! which is not actually an error, you can use `#[snafu(source)]` to +//! indicate if a field is an underlying cause or not: +//! +//! ```rust +//! # mod another { +//! # use snafu::Snafu; +//! # #[derive(Debug, Snafu)] +//! # pub enum Error {} +//! # } +//! # use snafu::Snafu; +//! #[derive(Debug, Snafu)] +//! enum Error { +//! SourceIsNotAnError { +//! #[snafu(source(false))] +//! source: String, +//! }, +//! +//! CauseIsAnError { +//! #[snafu(source)] +//! cause: another::Error, +//! }, +//! } +//! ``` +//! //! ## Controlling backtraces //! //! If your error contains other SNAFU errors which can report diff --git a/tests/source_attributes.rs b/tests/source_attributes.rs new file mode 100644 index 00000000..5338dd66 --- /dev/null +++ b/tests/source_attributes.rs @@ -0,0 +1,44 @@ +extern crate snafu; + +use snafu::{ResultExt, Snafu}; + +#[derive(Debug, Snafu)] +enum InnerError { + Boom, +} + +#[derive(Debug, Snafu)] +enum Error { + NoArgument { + #[snafu(source)] + cause: InnerError, + }, + + ExplicitTrue { + #[snafu(source(true))] + cause: InnerError, + }, + + ExplicitFalse { + #[snafu(source(false))] + source: i32, + }, +} + +fn inner() -> Result<(), InnerError> { + Ok(()) +} + +fn example() -> Result<(), Error> { + inner().context(NoArgument)?; + inner().context(ExplicitTrue)?; + ExplicitFalse { source: 42 }.fail()?; + Ok(()) +} + +#[test] +fn implements_error() { + fn check() {} + check::(); + example().unwrap_err(); +}