snafu
                                
                                
                                
                                    snafu copied to clipboard
                            
                            
                            
                        Allow for pattern-matching of errors to different error contexts
I would love to see a way to return different error contexts based on a consumed error. As far as I'm aware, such functionality is not currently possible with Snafu.
This could be implemented by adding a new function to the ResultExt trait, nearly identically to with_context, with the only change being to provide the error as an argument to the closure:
fn map_context<F, C, E2>(self, context: F) -> Result<T, E2>
where
    F: FnOnce(&E) -> C,
    C: IntoError<E2, Source = E>,
    E2: Error + ErrorCompat,
{
    self.map_err(|error| {
        let context = context(&error);
        context.into_error(error)
    })
}
For a usage example, consider this code:
use std::{fs, io, path::PathBuf};
use snafu::{Snafu, ResultExt};
#[derive(Debug, Snafu)]
enum Error {
    #[snafu(display("Path `{}` does not exist.", path.display()))]
    PathDoesNotExist { path: PathBuf },
    #[snafu(display("Insufficient permissions when attempting to access `{}`.", path.display()))]
    PathAccessPermissionDenied { path: PathBuf },
    #[snafu(display("Could not gather metadata for `{}`: {}", path.display(), source))]
    GetPathMetadata { path: PathBuf, source: io::Error },
}
fn main() -> Result<(), Error> {
    let meta = &fs::metadata(path)
        .map_context(|e| match e.kind() {
            io::ErrorKind::NotFound => PathDoesNotExist { path },
            io::ErrorKind::PermissionDenied => PathAccessPermissionDenied { path },
            _ => GetDirectoryMetadata { path },
        })?;
    Ok(())
}
As for a little necessary bikeshedding, I do feel that the name map_context is a little ambiguous, as it implies mapping an error's context to another context, when it's really mapping an error to a new context. On the other hand, map_err_to_context seems a little verbose. For what it's worth, when I first took a look at the API, I'd expected that with_context would provide the error as a parameter.
I'm not totally following your motivating example.
- 
GetPathMetadata/GetDirectoryMetadatalook like maybe they are supposed to be the same (a typo?) but they have different fields. - 
PathDoesNotExistandPathAccessPermissionDenieddon't have anio::Errorso I don't see how they would be used on the result offs::metadata. 
I suppose one close analog you can do today is
use snafu::{IntoError, Snafu};
use std::{fs, io, path::PathBuf};
#[derive(Debug, Snafu)]
enum Error {
    #[snafu(display("Path `{}` does not exist", path.display()))]
    PathDoesNotExist { path: PathBuf, source: io::Error },
    #[snafu(display("Insufficient permissions when attempting to access `{}`", path.display()))]
    PathAccessPermissionDenied { path: PathBuf, source: io::Error },
    #[snafu(display("Could not gather metadata for `{}`", path.display()))]
    GetDirectoryMetadata { path: PathBuf, source: io::Error },
}
fn main() -> Result<(), Error> {
    let path = "/dev/null";
    let _meta = fs::metadata(path).map_err(|e| match e.kind() {
        io::ErrorKind::NotFound => PathDoesNotExist { path }.into_error(e),
        io::ErrorKind::PermissionDenied => PathAccessPermissionDenied { path }.into_error(e),
        _ => GetDirectoryMetadata { path }.into_error(e),
    })?;
    Ok(())
}
Note that you can also create your own extension trait on top of Result to immediately gain the ergonomic benefit in your code today.
you can also create your own extension trait on top of
Resultto immediately gain the ergonomic benefit in your code today.
Perhaps not! Attempting your example:
fn main() -> Result<(), Error> {
    let path = "/dev/null";
    let _meta = fs::metadata(path).map_context(|e| match e.kind() {
        io::ErrorKind::NotFound => PathDoesNotExist { path },
        io::ErrorKind::PermissionDenied => PathAccessPermissionDenied { path },
        _ => GetDirectoryMetadata { path },
    })?;
    Ok(())
}
trait TrialRun<T, E> {
    fn map_context<F, C, E2>(self, context: F) -> Result<T, E2>
    where
        F: FnOnce(&E) -> C,
        C: IntoError<E2, Source = E>,
        E2: std::error::Error + snafu::ErrorCompat;
}
impl<T, E> TrialRun<T, E> for Result<T, E> {
    fn map_context<F, C, E2>(self, context: F) -> Result<T, E2>
    where
        F: FnOnce(&E) -> C,
        C: IntoError<E2, Source = E>,
        E2: std::error::Error + snafu::ErrorCompat,
    {
        self.map_err(|error| {
            let context = context(&error);
            context.into_error(error)
        })
    }
}
Quickly yields a problem:
error[E0308]: `match` arms have incompatible types
  --> src/main.rs:20:44
   |
18 |       let _meta = fs::metadata(path).map_context(|e| match e.kind() {
   |  ____________________________________________________-
19 | |         io::ErrorKind::NotFound => PathDoesNotExist { path },
   | |                                    ------------------------- this is found to be of type `PathDoesNotExist<&str>`
20 | |         io::ErrorKind::PermissionDenied => PathAccessPermissionDenied { path },
   | |                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `PathDoesNotExist`, found struct `PathAccessPermissionDenied`
21 | |         _ => GetDirectoryMetadata { path },
22 | |     })?;
   | |_____- `match` arms have incompatible types
   |
   = note: expected type `PathDoesNotExist<&str>`
            found struct `PathAccessPermissionDenied<&str>`
Had you tried this locally in some different fashion somehow?
Closing due to a lack of clarity on what is being requested.