Skip to content
This repository was archived by the owner on Aug 16, 2021. It is now read-only.

Override name in derive #306

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Version 0.1.6

- Add `#[fail(name = ...)]` for overriding derived error's name.

# Version 0.1.5

- Resolve a regression with error conversions (#290)
Expand Down
36 changes: 36 additions & 0 deletions book/src/derive-fail.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,39 @@ enum MyEnumError {
Variant2(#[fail(cause)] io::Error),
}
```

## Overriding `name`

By default, `name()` method of derived implementation of `Fail` returns absolute type name:
```rust
#[derive(Fail, Debug)]
struct MyError;

assert_eq!(MyError.name(), Some("crate_name::MyError"));
```

To specify your own value for error's name use the `#[fail(name = ...)]` attribute:
```rust
#[macro_use] extern crate failure;

use std::io;

/// MyError::name will return Some("MY_ERROR") now.
#[derive(Fail, Debug)]
#[fail(name = "MY_ERROR")]
struct MyError {
io_error: io::Error,
}

/// MyEnumError::name will return Some("MY_VARIANT_1") for Variant1
/// and Some("MY_VARIANT_2") for Variant2,
/// but None for Variant 3.
#[derive(Fail, Debug)]
enum MyEnumError {
#[fail(name = "MY_VARIANT_1")]
Variant1,
#[fail(name = "MY_VARIANT_2")]
Variant2(#[fail(cause)] io::Error),
Variant3,
}
```
3 changes: 2 additions & 1 deletion examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ struct MyError;

#[derive(Debug, Fail)]
#[fail(display = "my wrapping error")]
#[fail(name = "WRAPPING_ERROR")]
struct WrappingError(#[fail(cause)] MyError);

fn bad_function() -> Result<(), WrappingError> {
Expand All @@ -17,6 +18,6 @@ fn bad_function() -> Result<(), WrappingError> {

fn main() {
for cause in Fail::iter_chain(&bad_function().unwrap_err()) {
println!("{}: {}", cause.name().unwrap_or("Error"), cause);
println!("{}: {}", cause.name().unwrap(), cause);
}
}
136 changes: 89 additions & 47 deletions failure_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ extern crate synstructure;
#[macro_use]
extern crate quote;

use proc_macro2::{TokenStream, Span};
use syn::LitStr;
use proc_macro2::{Span, TokenStream};
use syn::spanned::Spanned;
use syn::LitStr;

#[derive(Debug)]
struct Error(TokenStream);
Expand Down Expand Up @@ -41,16 +41,14 @@ fn fail_derive_impl(s: synstructure::Structure) -> Result<TokenStream, Error> {
quote! { & }
};

let ty_name = LitStr::new(&s.ast().ident.to_string(), Span::call_site());

let name_body = name_body(&s)?;
let cause_body = s.each_variant(|v| {
if let Some(cause) = v.bindings().iter().find(is_cause) {
quote!(return Some(::failure::AsFail::as_fail(#cause)))
} else {
quote!(return None)
}
});

let bt_body = s.each_variant(|v| {
if let Some(bi) = v.bindings().iter().find(is_backtrace) {
quote!(return Some(#bi))
Expand All @@ -63,7 +61,7 @@ fn fail_derive_impl(s: synstructure::Structure) -> Result<TokenStream, Error> {
quote!(::failure::Fail),
quote! {
fn name(&self) -> Option<&str> {
Some(concat!(module_path!(), "::", #ty_name))
#name_body
}

#[allow(unreachable_code)]
Expand Down Expand Up @@ -98,37 +96,54 @@ fn fail_derive_impl(s: synstructure::Structure) -> Result<TokenStream, Error> {
})
}

fn name_body(s: &synstructure::Structure) -> Result<quote::__rt::TokenStream, Error> {
let mut msgs = s
.variants()
.iter()
.map(|v| find_msg_of("name", &v.ast().attrs));
if msgs.all(|msg| msg.map(|m| m.is_none()).unwrap_or(true)) {
let ty_name = LitStr::new(&s.ast().ident.to_string(), Span::call_site());
return Ok(quote!(return Some(concat!(module_path!(), "::", #ty_name))));
}

let mut match_body = TokenStream::new();
for v in s.variants() {
if let Some(msg) = find_msg_of("name", &v.ast().attrs)? {
let name_value = match msg.nested[0] {
syn::NestedMeta::Meta(syn::Meta::NameValue(ref nv)) => nv.lit.clone(),
_ => unreachable!(),
};
let pat = v.pat();
match_body.extend(quote!(#pat => { return Some(#name_value) }));
} else {
let pat = v.pat();
match_body.extend(quote!(#pat => { return None }));
}
}
Ok(quote!(match *self { #match_body }))
}

fn display_body(s: &synstructure::Structure) -> Result<Option<quote::__rt::TokenStream>, Error> {
let mut msgs = s.variants().iter().map(|v| find_error_msg(&v.ast().attrs));
let mut msgs = s
.variants()
.iter()
.map(|v| find_msg_of("display", &v.ast().attrs));
if msgs.all(|msg| msg.map(|m| m.is_none()).unwrap_or(true)) {
return Ok(None);
}

let mut tokens = TokenStream::new();
for v in s.variants() {
let msg =
find_error_msg(&v.ast().attrs)?
.ok_or_else(|| Error::new(
v.ast().ident.span(),
"All variants must have display attribute."
))?;
if msg.nested.is_empty() {
return Err(Error::new(
msg.span(),
"Expected at least one argument to fail attribute"
));
}
let msg = find_msg_of("display", &v.ast().attrs)?.ok_or_else(|| {
Error::new(
v.ast().ident.span(),
"All variants must have display attribute.",
)
})?;

let format_string = match msg.nested[0] {
syn::NestedMeta::Meta(syn::Meta::NameValue(ref nv)) if nv.ident == "display" => {
nv.lit.clone()
}
_ => {
return Err(Error::new(
msg.span(),
"Fail attribute must begin `display = \"\"` to control the Display message."
));
}
syn::NestedMeta::Meta(syn::Meta::NameValue(ref nv)) => nv.lit.clone(),
_ => unreachable!(),
};
let args = msg.nested.iter().skip(1).map(|arg| match *arg {
syn::NestedMeta::Literal(syn::Lit::Int(ref i)) => {
Expand All @@ -146,13 +161,13 @@ fn display_body(s: &synstructure::Structure) -> Result<Option<quote::__rt::Token
arg.span(),
&format!(
"display attempted to access field `{}` in `{}::{}` which \
does not exist (there are {} field{})",
does not exist (there are {} field{})",
idx,
s.ast().ident,
v.ast().ident,
v.bindings().len(),
if v.bindings().len() != 1 { "s" } else { "" }
)
),
));
}
};
Expand All @@ -171,15 +186,15 @@ fn display_body(s: &synstructure::Structure) -> Result<Option<quote::__rt::Token
id,
s.ast().ident,
v.ast().ident
)
),
));
}
ref arg => {
return Err(Error::new(
arg.span(),
"Invalid argument to fail attribute!"
"Invalid argument to fail attribute!",
));
},
}
});
let args = args.collect::<Result<Vec<_>, _>>()?;

Expand All @@ -189,30 +204,57 @@ fn display_body(s: &synstructure::Structure) -> Result<Option<quote::__rt::Token
Ok(Some(tokens))
}

fn find_error_msg(attrs: &[syn::Attribute]) -> Result<Option<syn::MetaList>, Error> {
let mut error_msg = None;
fn find_msg_of(attr_name: &str, attrs: &[syn::Attribute]) -> Result<Option<syn::MetaList>, Error> {
let mut found_msg = None;
for attr in attrs {
if let Some(meta) = attr.interpret_meta() {
if meta.name() == "fail" {
if error_msg.is_some() {
return Err(Error::new(
meta.span(),
"Cannot have two display attributes"
));
} else {
if let syn::Meta::List(list) = meta {
error_msg = Some(list);
} else {
if let syn::Meta::List(msg) = meta {
if msg.nested.is_empty() {
return Err(Error::new(
meta.span(),
"fail attribute must take a list in parentheses"
msg.span(),
"Expected at least one argument to fail attribute",
));
}
let mut found = false;
match msg.nested[0] {
syn::NestedMeta::Meta(syn::Meta::NameValue(ref nv))
if nv.ident == "display" || nv.ident == "name" =>
{
if nv.ident == attr_name {
if found_msg.is_some() {
return Err(Error::new(
msg.span(),
&format!("Cannot have two {} attributes", attr_name),
));
}
// Can't assign directly, because of:
// error[E0505]: cannot move out of `msg` because it is borrowed
found = true;
}
}
_ => {
return Err(Error::new(
msg.span(),
"Fail attribute must begin with `display = \"\"` \
to control the Display message,\
or with `name = \"\"` to specify the name of the error.",
));
}
}
if found {
found_msg = Some(msg);
}
} else {
return Err(Error::new(
meta.span(),
"fail attribute must take a list in parentheses",
));
}
}
}
}
Ok(error_msg)
Ok(found_msg)
}

fn is_backtrace(bi: &&synstructure::BindingInfo) -> bool {
Expand Down
1 change: 0 additions & 1 deletion failure_derive/tests/backtrace.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
extern crate failure;
#[macro_use]
extern crate failure_derive;

use failure::{Backtrace, Fail};
Expand Down
1 change: 0 additions & 1 deletion failure_derive/tests/custom_type_bounds.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#[macro_use]
extern crate failure;

use std::fmt::Debug;
Expand Down
1 change: 0 additions & 1 deletion failure_derive/tests/no_derive_display.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
extern crate failure;
#[macro_use]
extern crate failure_derive;

use failure::Fail;
Expand Down
29 changes: 24 additions & 5 deletions failure_derive/tests/tests.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
extern crate failure;
#[macro_use]
extern crate failure_derive;

use failure::Fail;

#[derive(Fail, Debug)]
#[fail(display = "An error has occurred.")]
#[fail(name = "UNIT_ERROR")]
struct UnitError;

#[test]
fn unit_struct() {
let s = format!("{}", UnitError);
assert_eq!(&s[..], "An error has occurred.");

assert_eq!(UnitError.name().unwrap(), "UNIT_ERROR");
}

#[derive(Fail, Debug)]
#[fail(display = "Error code: {}", code)]
#[fail(name = "RECORD_ERROR")]
struct RecordError {
code: u32,
}

#[test]
fn record_struct() {
let s = format!("{}", RecordError { code: 0 });
let err = RecordError { code: 0 };

let s = format!("{}", err);
assert_eq!(&s[..], "Error code: 0");

assert_eq!(err.name().unwrap(), "RECORD_ERROR");
}

#[derive(Fail, Debug)]
Expand All @@ -37,19 +46,29 @@ fn tuple_struct() {
#[derive(Fail, Debug)]
enum EnumError {
#[fail(display = "Error code: {}", code)]
#[fail(name = "STRUCT")]
StructVariant { code: i32 },
#[fail(display = "Error: {}", _0)]
#[fail(name = "TUPLE")]
TupleVariant(&'static str),
#[fail(display = "An error has occurred.")]
UnitVariant,
}

#[test]
fn enum_error() {
let s = format!("{}", EnumError::StructVariant { code: 2 });
let structure = EnumError::StructVariant { code: 2 };
let s = format!("{}", structure);
assert_eq!(&s[..], "Error code: 2");
let s = format!("{}", EnumError::TupleVariant("foobar"));
assert_eq!(structure.name().unwrap(), "STRUCT");

let tuple = EnumError::TupleVariant("foobar");
let s = format!("{}", tuple);
assert_eq!(&s[..], "Error: foobar");
let s = format!("{}", EnumError::UnitVariant);
assert_eq!(tuple.name().unwrap(), "TUPLE");

let unit = EnumError::UnitVariant;
let s = format!("{}", unit);
assert_eq!(&s[..], "An error has occurred.");
assert!(unit.name().is_none());
}
Loading