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 use the Error + ErrorKind paradigm while having different ErrorKinds per module?

linclelinkpart5 opened this issue · comments

I was wondering the best course of action to take for using failure within my library. Right now, I've created a crate-level error module, with the following:

#[derive(Debug)]
pub struct Error {
    inner: Context<ErrorKind>,
}

#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
pub enum ErrorKind {
    #[fail(display = "path is not a directory")]
    NotADirPath,
    #[fail(display = "path is not a file")]
    NotAFilePath,
    #[fail(display = "path does not exist")]
    NonexistentPath,
    #[fail(display = "path does not have a parent or is filesystem root")]
    NoPathParent,
    #[fail(display = "item path not found in processed metadata")]
    NoMetadataFound,
    #[fail(display = "unable to parse/read meatadata file")]
    CannotParseMetadata,
    #[fail(display = "unable to read entries in directory")]
    CannotReadDir,
    #[fail(display = "unable to read directory entry")]
    CannotReadDirEntry,
    #[fail(display = "unable to find meta path from item path")]
    CannotFindMetaPath,
    #[fail(display = "invalid glob pattern")]
    InvalidGlobPattern,
    #[fail(display = "unable to build selector")]
    CannotBuildSelector,
    #[fail(display = "unable to open metadata file")]
    CannotOpenMetadataFile,
    #[fail(display = "unable to read metadata file")]
    CannotReadMetadataFile,

    #[fail(display = "unable to read YAML file")]
    CannotReadYamlFile,
}

impl Fail for Error {
    fn cause(&self) -> Option<&Fail> {
        self.inner.cause()
    }

    fn backtrace(&self) -> Option<&Backtrace> {
        self.inner.backtrace()
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        Display::fmt(&self.inner, f)
    }
}

impl Error {
    pub fn kind(&self) -> ErrorKind {
        *self.inner.get_context()
    }
}

impl From<ErrorKind> for Error {
    fn from(kind: ErrorKind) -> Error {
        Error { inner: Context::new(kind) }
    }
}

impl From<Context<ErrorKind>> for Error {
    fn from(inner: Context<ErrorKind>) -> Error {
        Error { inner: inner }
    }
}

However, I really feel that the YAML (and eventually possibly other file formats) and Glob error kinds should be moved to the specific YAML-reading/Glob-parsing module that uses them. And if I'm going that route, it makes sens to split off other ErrorKinds into their own modules as appropriate. However, I'm not sure what the best practice is, or if this can even work with the Error + ErrorKind paradigm. Not to mention, some ErrorKinds are generic across multiple modules, so those would make sense to remain at the crate level.

Any guidance would be very helpful!

commented

A guiding principle I've picked up recently for working with failure is:

  • Use Error / ErrorKind pairs to define new errors (leaf nodes).
  • Use failure::Error with ResultExt (e.g. .context("foo")) to return errors from everywhere else (parent nodes).

That way you can get an overview of new errors defined by the module, get helpful context messages when propagating errors upwards, and reduce the amount of boilerplate needed all together.

@yoshuawuyts This is what I was thinking, but it seems like I'd still need to have this boilerplate for each module I want to have specific ErrorKinds for?

For example, let's say in yaml.rs:

#[derive(Debug)]
pub struct Error {
    inner: Context<ErrorKind>,
}

#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
pub enum ErrorKind {
    #[fail(display = "unable to read YAML file")]
    CannotRead,
    #[fail(display = "unexpected mapping in YAML file")]
    UnexpectedMapping,
}

impl Fail for Error {
    fn cause(&self) -> Option<&Fail> {
        self.inner.cause()
    }

    fn backtrace(&self) -> Option<&Backtrace> {
        self.inner.backtrace()
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        Display::fmt(&self.inner, f)
    }
}

impl Error {
    pub fn kind(&self) -> ErrorKind {
        *self.inner.get_context()
    }
}

impl From<ErrorKind> for Error {
    fn from(kind: ErrorKind) -> Error {
        Error { inner: Context::new(kind) }
    }
}

impl From<Context<ErrorKind>> for Error {
    fn from(inner: Context<ErrorKind>) -> Error {
        Error { inner: inner }
    }
}

If I create a path_manip.rs, and want to have new ErrorKinds in there, I'd need to redo all that code code above (with different variants in the ErrorKind enum, of course), but for this new module?

commented

@ThatsGobbles yeah, that's what I've been doing too. It should be possible to write a macro to automate most of this, but personally I'm still hoping for language features down the line to help with this. Crossing fingers we'll eventually have something ✨

I'm going to close this issue as it's not really a bug report.