diff --git a/examples/simple.rs b/examples/simple.rs index 35d25e1..fc39601 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -16,7 +16,7 @@ fn bad_function() -> Result<(), WrappingError> { } fn main() { - for cause in Fail::iter_causes(&bad_function().unwrap_err()) { - println!("{}", cause); + for cause in Fail::iter_chain(&bad_function().unwrap_err()) { + println!("{}: {}", cause.name().unwrap_or("Error"), cause); } } diff --git a/failure_derive/src/lib.rs b/failure_derive/src/lib.rs index 4c223eb..7df10d1 100644 --- a/failure_derive/src/lib.rs +++ b/failure_derive/src/lib.rs @@ -7,6 +7,7 @@ extern crate synstructure; extern crate quote; use proc_macro2::{TokenStream, Span}; +use syn::LitStr; use syn::spanned::Spanned; #[derive(Debug)] @@ -40,6 +41,8 @@ fn fail_derive_impl(s: synstructure::Structure) -> Result { quote! { & } }; + let ty_name = LitStr::new(&s.ast().ident.to_string(), Span::call_site()); + 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))) @@ -59,6 +62,10 @@ fn fail_derive_impl(s: synstructure::Structure) -> Result { let fail = s.unbound_impl( quote!(::failure::Fail), quote! { + fn name(&self) -> Option<&str> { + Some(concat!(module_path!(), "::", #ty_name)) + } + #[allow(unreachable_code)] fn cause(&self) -> ::failure::_core::option::Option<#make_dyn(::failure::Fail)> { match *self { #cause_body } diff --git a/src/context.rs b/src/context.rs index 3316b76..9ab0cc4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -108,6 +108,10 @@ with_std! { } impl Fail for Context { + fn name(&self) -> Option<&str> { + self.failure.as_cause().and_then(|x| x.name()) + } + fn cause(&self) -> Option<&Fail> { self.failure.as_cause() } diff --git a/src/error/mod.rs b/src/error/mod.rs index 30e69cc..152807b 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -70,6 +70,11 @@ impl Error { self.imp.failure() } + /// Returns the name of the underlying fail. + pub fn name(&self) -> Option<&str> { + self.as_fail().name() + } + /// Returns a reference to the underlying cause of this `Error`. Unlike the /// method on `Fail`, this does not return an `Option`. The `Error` type /// always has an underlying failure. diff --git a/src/error_message.rs b/src/error_message.rs index 01ff1ae..560d317 100644 --- a/src/error_message.rs +++ b/src/error_message.rs @@ -19,7 +19,11 @@ struct ErrorMessage { msg: D, } -impl Fail for ErrorMessage { } +impl Fail for ErrorMessage { + fn name(&self) -> Option<&str> { + Some("failure::ErrorMessage") + } +} impl Display for ErrorMessage { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/lib.rs b/src/lib.rs index 498b814..02c239f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,6 +106,16 @@ with_std! { /// `std::error::Error`, and are also `Send`, `Sync`, and `'static`, implement /// `Fail` by a blanket impl. pub trait Fail: Display + Debug + Send + Sync + 'static { + /// Returns the "name" of the error. + /// + /// This is typically the type name. Not all errors will implement + /// this. This method is expected to be most useful in situations + /// where errors need to be reported to external instrumentation systems + /// such as crash reporters. + fn name(&self) -> Option<&str> { + None + } + /// Returns a reference to the underlying cause of this failure, if it /// is an error that wraps other errors. /// diff --git a/tests/basic_fail.rs b/tests/basic_fail.rs new file mode 100644 index 0000000..574886d --- /dev/null +++ b/tests/basic_fail.rs @@ -0,0 +1,21 @@ +#[macro_use] +extern crate failure; + +use failure::Fail; + +#[test] +fn test_name() { + #[derive(Fail, Debug)] + #[fail(display = "my error")] + struct MyError; + + let err = MyError; + + assert_eq!(err.to_string(), "my error"); + assert_eq!(err.name(), Some("basic_fail::MyError")); + + let ctx = err.context("whatever"); + + assert_eq!(ctx.to_string(), "whatever"); + assert_eq!(ctx.name(), Some("basic_fail::MyError")); +} \ No newline at end of file