Skip to content

Commit

Permalink
Support #[snafu(source)]
Browse files Browse the repository at this point in the history
Addresses #10
  • Loading branch information
shepmaster committed May 5, 2019
1 parent 3decfdb commit 0278c87
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 12 deletions.
50 changes: 38 additions & 12 deletions snafu-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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? */ }
}
}
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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<Self> {
use syn::LitBool;
let val: LitBool = input.parse()?;
Ok(Source::Flag(val.value))
}
}

enum SnafuAttribute {
Display(UserInput),
Visibility(UserInput),
Source(Source),
Backtrace,
}

Expand All @@ -323,13 +349,6 @@ impl SnafuAttribute {
_ => None,
}
}

fn is_backtrace(&self) -> bool {
match *self {
SnafuAttribute::Backtrace => true,
_ => false,
}
}
}

impl syn::parse::Parse for SnafuAttribute {
Expand All @@ -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<Source> = inside.parse()?;
Ok(SnafuAttribute::Source(v.0))
}
} else if name == "backtrace" {
let _: MyParens<Ident> = inside.parse()?;
Ok(SnafuAttribute::Backtrace)
} else {
Err(SynError::new(
name.span(),
"expected `display`, `visibility`, or `backtrace`",
"expected `display`, `visibility`, `source`, or `backtrace`",
))
}
}
Expand Down
28 changes: 28 additions & 0 deletions src/guide/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions tests/source_attributes.rs
Original file line number Diff line number Diff line change
@@ -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<T: std::error::Error>() {}
check::<Error>();
example().unwrap_err();
}

0 comments on commit 0278c87

Please sign in to comment.