how to chain custom errors?
little-dude opened this issue · comments
Hi,
I know it's possible to chain failure::Error
, by calling context()
, but I don't want to have all my method return the same error type. Instead, I want to define MyError
and be able to chain them.
I can define my error type like this:
#[derive(Debug)]
pub struct MyError {
inner: Context<String>,
}
impl<'a> From<&'a str> for MyError {
fn from(msg: &'a str) -> MyError {
MyError {
inner: Context::new(msg.into()),
}
}
}
impl Fail for MyError {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
impl Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl<'a> From<Context<&'a str>> for MyError {
fn from(inner: Context<&'a str>) -> MyError {
MyError {
inner: Context::new(inner.get_context().to_string()),
}
}
}
impl From<Context<String>> for MyError {
fn from(inner: Context<String>) -> MyError {
MyError { inner }
}
}
Now, chaining does not work when MyError
comes from a Context<&str>
because I have to create a new context. This code prints err2 and err3 but not err1:
use core::fmt::{self, Display};
use failure::{Backtrace, Context, Fail, ResultExt};
fn main() {
println!("{:?}", err3());
}
fn err1() -> Result<(), MyError> {
Ok(Err(MyError::from("err1"))?)
}
fn err2() -> Result<(), MyError> {
Ok(err1().context("err2")?) // losing err1 here
}
fn err3() -> Result<(), MyError> {
Ok(err2().context("err3".to_string())?) // chaining correctly
}
I see in the source that there's a Context::with_err
that would allow my to fix the implementation of From<Context<&'a str>>
for MyError
but it's private.
In theory I could just call to_string()
everywhere I use context()
but I feel this isn't right. I'm either mis-using this crate, or it's lacking a feature. What do you think?
This patch would solve my problem:
diff --git a/src/context.rs b/src/context.rs
index 6e1fe90..2ae3de8 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -74,6 +74,17 @@ with_std! {
&self.context
}
+ /// Maps `Context<D>` to `Context<T>` by applying a function to the contained context.
+ pub fn map<F, T>(self, op: F) -> Context<T>
+ where F: FnOnce(D) -> T,
+ T: Display + Send + Sync + 'static
+ {
+ Context {
+ context: op(self.context),
+ failure: self.failure,
+ }
+ }
+
pub(crate) fn with_err<E: Into<Error>>(context: D, error: E) -> Context<D> {
let failure = Either::That(error.into());
Context { context, failure }
With that I can do chaining of custom error types:
use core::fmt::{self, Display};
use failure::{Backtrace, Context, Fail, ResultExt};
fn main() {
println!("{:?}", err3());
}
fn err1() -> Result<(), MyError> {
Ok(Err(MyError::from("err1"))?)
}
fn err2() -> Result<(), MyError> {
Ok(err1().context("err2")?)
}
fn err3() -> Result<(), MyError> {
Ok(err2().context("err3")?)
}
#[derive(Debug)]
pub struct MyError {
inner: Context<String>,
}
impl From<&'static str> for MyError {
fn from(msg: &'static str) -> MyError {
MyError {
inner: Context::new(msg.into()),
}
}
}
impl Fail for MyError {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
impl Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl From<Context<&'static str>> for MyError {
fn from(inner: Context<&'static str>) -> MyError {
MyError {
inner: inner.map(|s| s.to_string()),
}
}
}
impl From<Context<String>> for MyError {
fn from(inner: Context<String>) -> MyError {
MyError { inner }
}
}
Is this a change you'd consider for inclusion if I opened a PR?
It would be awesome to have this supported. I second the PR submission.