Expand description
This library provides Rust CLI applications with console reporting and
error-handling utilities. Console output is modeled after
Cargo, and the Error type is
similar to anyhow’s
Error, but with
optional help messages.
Minimum supported Rust version: 1.61.1
§Features
- Ergonomic error-handling.
- A set of typical CLI application errors (with exit codes).
- Errors and app status reporting.
§Cargo Feature Flags
All features are enabled by default, but they can be imported individually using Cargo feature flags:
error: Enables error-handling withError,ResultandErrorWrap.cli-error: Enables set ofCliErrors with their associatedexit_code.report: Enables reporting errors and statuses to the console with thereportmodule.
§Example Cargo.toml
...
[dependencies]
narrate = { version = "0.4.2", default-features = false, features = ["report"] }
...§Error Handling
Use Result<T> as a return type for any fallible function. Within the
function, use ? to propagate any error that implements the
std::error::Error trait. Same as
anyhow::Result<T>.
use narrate::Result;
fn get_user() -> Result<User> {
let json = std::fs::read_to_string("user.json")?;
let user: User = serde_json::from_str(&json)?;
Ok(user)
}§Returning from main()
Result<T> can be used to return from main(). If any errors occur it
prints the Debug implementation for Error.
use narrate::{bail, Result};
fn main() -> Result<()> {
inner_fn()?;
Ok(())
}
fn inner_fn() -> Result<()> {
bail!("internal error")
}Console output:
Error: internal error§Error Wrap
Wrap an error with more context by importing ErrorWrap. Similar to
anyhow::Context.
Just add .wrap(context) after any function call that returns a Result.
Context can be anything that implements Debug,
Display, Sync and Send – including &str,
String and errors.
use narrate::{ErrorWrap, Result, CliError};
fn run() -> Result<()> {
...
// wrap with contextual &str
acquire().wrap("unable to acquire data")?;
// or wrap with another error
config.load().wrap(CliError::Config)?;
...
}
fn acquire() -> Result<(), io::Error> {
...
}Console output:
Error: unable to acquire data
Cause: oh no!§Lazy evaluation
If your context requires some work to create/format, you should use
wrap_with instead.
use narrate::{ErrorWrap, Result, CliError};
fn run() -> Result<()> {
...
// wrap with a formatted string
data.store(path).wrap_with(|| format!("can't save to: {path}"))?;
// wrap with a computed error
data.store(path)
.wrap_with(|| CliError::WriteFile(PathBuf::from(path)))?;
...
}§Help Message Wrap
Add separate help text to an error. By importing ErrorWrap you also get
the add_help method and its lazy version add_help_with.
use narrate::{ErrorWrap, Result};
fn run() -> Result<()> {
Project::new(path).add_help("try using `project init`")?;
...
}Console output:
Error: directory already exists: '/home/dev/cool-project'
try using `project init`§Combination
Mix and match the ErrorWrap methods throughout your application to make
sure the user gets all the information they need.
use narrate::{ErrorWrap, Result};
fn run() -> Result<()> {
...
new_project(&path).wrap("cannot create project")?;
...
}
fn new_project(path: &Path) -> Result<()> {
...
create_dir(path)
.wrap_with(|| format!(
"unable to create directory: '{}'",
path.display()
))
.add_help(
"try using `project init` inside your existing directory"
)?;
...
}Console output:
Error: cannot create project
Cause: unable to create directory: '/home/dev/cool-project'
Cause: Is a directory (os error 20)
try using `project init` inside your existing directory§Convenience Macros
Use the error_from macro to create an ad-hoc Error from a string or
another error. Similar to anyhow!.
let val = map.get(key).ok_or(error_from!("unknown key"))?;Use bail to return early with an error. Equivalent to return Err(error_from!(...)).
if !map.contains_key(key) {
bail!("unknown key");
}§CLI Errors
Use CliError for a set of common errors that can occur in a command-line
application. These can be used to avoid adding repetitive context for IO
errors.
use narrate::{bail, ErrorWrap, Result, CliError};
fn run() -> Result<()> {
...
match args.operation {
Op::Get => fetch_data(res_name).wrap_with(|| CliError::ResourceNotFound(res_name))?,
Op::Set(data) => set_data(res_name, data).wrap(CliError::InputData)?,
_ => bail!(CliError::Protocol),
}
...
}§Exit Codes
As this selection of errors can often be fatal for an application, this
library provides access to a set of standard program exit codes via the
ExitCode trait. These adhere to
sysexits.h.
Both anyhow::Error and narrate::Error implement this trait,
thus can provide exit codes. If no CliError is found as an underlying
error, the code will be 70 (for internal software error).
Import the ExitCode trait to use the exit_code function, and use
std::process::exit to exit the program with the appropriate code.
use narrate::ExitCode;
if let Err(err) = run() {
std::process::exit(err.exit_code());
}Re-exports§
Modules§
- report
- Functions for printing status and error messages to stderr.
Macros§
- bail
- Return early with an error.
- error_
from - Construct an ad-hoc error from a string or existing non-
narrateerror value.
Structs§
- Chain
- Iterator of a chain of source errors.
- Error
- Wrapper around a dynamic error type with an optional help message.
Enums§
Traits§
- Error
Wrap - Provides
wrapandadd_helpmethods forResult. - Exit
Code - Provide
exit_codemethod for CliError. Intended to be passed tostd::process::exit.
Type Aliases§
- Result
Result<T, Error>