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
,Result
andErrorWrap
.cli-error
: Enables set ofCliError
s with their associatedexit_code
.report
: Enables reporting errors and statuses to the console with thereport
module.
Example Cargo.toml
...
[dependencies]
narrate = { version = "0.4.0", 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
- Functions for printing status and error messages to stderr.
Macros
- Return early with an error.
- Construct an ad-hoc error from a string or existing non-
narrate
error value.
Structs
- Iterator of a chain of source errors.
- Wrapper around a dynamic error type with an optional help message.
Enums
- Standard command line application error
- The 8 standard colors.
Traits
Type Definitions
Result<T, Error>