5 releases
0.1.4 | Jun 3, 2023 |
---|---|
0.1.3 | Apr 27, 2023 |
0.1.2 | Apr 15, 2023 |
0.1.1 | Apr 8, 2023 |
0.1.0 | Apr 8, 2023 |
#17 in #wrap
92 downloads per month
Used in 4 crates
(2 directly)
44KB
1K
SLoC
anyhow-std
This crate wraps certain [std] functionality to produce anyhow::Results 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 examplePath::read_to_string
does not exist, but would be a straightforward wrapper forstd::fs::read_to_string
, so this crate providesPathAnyhow::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, DirEntry, and Metadata each own a 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 because it mutates the underlying
std::process::Child upon construction.
Finally, the 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.
Dependencies
~0.4–1MB
~20K SLoC