Crates.io | anyhow-std |
lib.rs | anyhow-std |
version | 0.1.4 |
source | src |
created_at | 2023-04-08 15:29:10.497545 |
updated_at | 2023-06-03 20:33:10.200048 |
description | Wrap std APIs with anyhow error context. |
homepage | https://github.com/nathan-at-least/anyhow-std |
repository | https://github.com/nathan-at-least/anyhow-std.git |
max_upload_size | |
id | 833640 |
size | 43,542 |
anyhow-std
This crate wraps certain [std] functionality to produce [anyhow::Result]s providing better error messages w/ context.
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();
A common pattern is used to extend [std] types:
<std type>Anyhow
, for example: [OsStrAnyhow]impl OsStrAnyhow for OsStr { … }
<method name>_anyhow
. These methods always return [anyhow::Result] types, for example: [OsStrAnyhow::to_str_anyhow
].…_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
MethodsThese 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.
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.
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.