rust-lang-deprecated / failure

Error management

Home Page:https://rust-lang-nursery.github.io/failure/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.