anyhow-std 0.1.4

Wrap std APIs with anyhow error context.
Documentation
# `anyhow-std`

This crate wraps certain [std] functionality to produce [anyhow::Result]s
providing better error messages w/ context.

## Example: Expecting a UTF8 Path Extension

Suppose we are processing a user-provided path and we _expect_ it to
have an extension that is valid UTF8:

```
use std::path::Path;
use anyhow_std::{PathAnyhow, OsStrAnyhow};

fn process_user_path(path: &Path) -> anyhow::Result<()> {
    let extos = path.extension_anyhow()?;
    let ext = extos.to_str_anyhow()?;
    process_user_path_with_extension(path, ext)?;
    Ok(())
}

fn process_user_path_with_extension(path: &Path, extension: &str) -> anyhow::Result<()> {
    todo!("implement path processing of {path:?} for the extension {extension:?}")
}

/*
Now if the user provides a path without an extension, they'll get an
error message with more helpful context:
*/

let res = process_user_path(Path::new("/tmp/no-extension"));
assert!(res.is_err());

let error_message = format!("{:#}", res.err().unwrap());
assert_eq!(
    error_message,
    r#"while processing path "/tmp/no-extension": missing expected extension"#
);

/*
Unix systems can have non-UTF8 paths:
*/

#[cfg(target_family = "unix")]
fn demonstrate_non_utf8_extension() {
    use std::ffi::OsStr;
    use std::os::unix::ffi::OsStrExt;

    let res = process_user_path(Path::new(OsStr::from_bytes(b"/tmp/non-unicode-extension.wacky-\xf3-extension")));
    assert!(res.is_err());

    let error_message = format!("{:#}", res.err().unwrap());
    assert_eq!(
        error_message,
        r#"while processing os string "wacky-�-extension": not valid utf8"#,
    );
}

#[cfg(target_family = "unix")]
demonstrate_non_utf8_extension();
```

## Extension Traits

A common pattern is used to extend [std] types:

- An extension trait is provided named `<std type>Anyhow`, for example: [OsStrAnyhow]
- An impl is provided for the target type, for example `impl OsStrAnyhow for OsStr { … }`
- For a subset of methods of the target type, extension trait methods are provided named `<method name>_anyhow`. These methods always return [anyhow::Result] types, for example: [`OsStrAnyhow::to_str_anyhow`].
- Additionally, some `…_anyhow` methods may be provided to wrap functionality not found directly on the target type. For example `Path::read_to_string` does not exist, but would be a straightforward wrapper for [`std::fs::read_to_string`], so this crate provides [`PathAnyhow::read_to_string_anyhow`]

By consistently appending `_anyhow` to wrapped methods, callers can
unambiguously choose when to use these methods versus the [std] methods.

### `…_anyhow` Methods

These methods convert `Option<T>` or `Result<T, E>` return types of
the [std] method into `anyhow::Result<T>`, where the [anyhow::Error]
has context added specific to the [std] type.

For [std] methods that return `Option<T>`, often a `None` return type
isn't necessarily an "error" per se, but the `…_anyhow` methods result
in an error in this case. So these methods should only be used when code
expects `Some` results, and if your code should handle `None` as a
"non-error", it can simply use the [std] method.

## Wrapper types

In some cases, it is necessary to use a "wrapper type" pattern
rather than an extension trait, primarily to track extra data
used in error contexts. For example the [crate::fs] wrapper types
[ReadDir](crate::fs::ReadDir), [DirEntry](crate::fs::DirEntry), and
[Metadata](crate::fs::Metadata) each own a [PathBuf](std::path::PathBuf)
in addition to the underlying [std::fs] type in order to provide paths
in error contexts, in order to provide helpful error context for common
directory and fs traversal uses.

Wrapper types _override_ some underlying [std] type methods, rather
than using the `…_anyhow` naming convention. They also provide
a [std::ops::Deref] impl for the underlying [std] type, so all
non-overridden methods can be called naturally and overridden methods
can be called by explicitly using `wrapper.deref().target_method(…)`.

Types using this wrapper-type pattern consistently provide [From] /
[Into] implementations where possible for the underlying [std] types plus
any error context data.  For example, for [crate::fs::Metadata] provides
[From] / [Into] impls for `(std::fs::Metadata, std::fs::PathBuf)`,
the latter providing error context. There is an exception to this
for [Child](crate::process::Child) because it mutates the underlying
[std::process::Child] upon construction.

Finally, the [Output](crate::process::Output) type is an exception to
this wrapper pattern because it provides no methods and exposes all
contents as `pub` fields.

## API Coverage

This crate only wraps a small subset of [std] based on what the author
needs in other projects. If you'd like to see more [std] APIs wrapped,
patches are welcome. ;-)

The `0.1.x` version series will add APIs as they are useful and may
change error context strings. The semantics of wrapped functions should
not vary much, but might.