Skip to content

Instantly share code, notes, and snippets.

@benkay86
Created January 29, 2021 21:04
Show Gist options
  • Save benkay86/6f4fbe31c219fb0a99bba13735c52206 to your computer and use it in GitHub Desktop.
Save benkay86/6f4fbe31c219fb0a99bba13735c52206 to your computer and use it in GitHub Desktop.
Replacement for structopt::clap::Error to override from_args() and avoid calls to std::process::exit().
//! Custom error type to replace [`structopt::clap::Error`].
//! By default [`structopt::StructOpt::from_args()`] calls `exit().``
//! By default [`std::process::exit()`] does not unwind the stack.
//! This is bad: https://www.youtube.com/watch?v=zQC8T71Y8e4
//! Use [`OptsError`] like this to override `from_args()` and avoid calls to
//! `std::process::exit()`:
//!
//! ```ignore
//! #[derive(StructOpt)]
//! #[structopt(...)]
//! struct Opts {
//! // ... your command line arguments ...
//! }
//!
//! // Override from_args() to use [`OptsError`].
//! impl Opts {
//! pub fn from_args() -> Result<Opts, OptsError> {
//! match Opts::from_iter_safe(std::env::args()) {
//! Ok(opts) => Ok(opts),
//! Err(e) => Err(OptsError { error: e })
//! }
//! }
//! }
//! ```
/// Custom error type to return when command line parsing has gone wrong.
///
/// The default behavior of [structopt]'s `from_args()` when it encounters an
/// error is to print out an error message with some usage hints using the
/// [`std::fmt::Display`] trait of [`structopt::clap::Error`] and then call
/// [`std::process::exit()`]. The problem with this approach is that `exit()`
/// doesn't unwind the stack. It is safer to instead use `from_iter_safe()` and
/// then propagate the `Error` to the called by returning an `Result`.
///
/// Why not just use [`structopt::clap::Error`]? In the most common use case,
/// the error propagate all the way up to `main()` and the Rust compiler will
/// emit code to do something like `eprinteln!("Error: {:?}", error)` just
/// before the program terminates. The pretty-formatted help, version, or
/// error/usage message is generated by the [`std::fmt::Display`] trait for
/// `clap::Error`, whereas its [`std::fmt::Display`] trait would be quite
/// indecipherable to the end user!
///
/// This custom error type stores an inner [`structopt::clap::Error`], which is
/// accessible via its public member `OptsError::error`. Its `Display` and
/// `Debug` traits both use VT100 ANSI escape codes to erase the "Error: "
/// message printed by Rust, since it is misleading when printing out
/// help/version information and redundant when printing out out pretty-
/// formatted error/usage information. It then renders `clap::Error` using its
/// `Display` trait regardless of whether `OptsError` is being rendered via
/// `Display` or `Debug`.
///
/// TLDR; used to override structopt's `from_args()` to propagate errors through
/// `main()` instead of terminating early.
pub struct OptsError {
/// Underlying command line parsing error that caused this error.
pub error: structopt::clap::Error
}
impl std::fmt::Display for OptsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Use VT100 ANSI escape codes to erase current line and move cursor to the
// beginning of the line (on the left).
write!(f, "\x1B[2K\x1B[1000D{}", self.error)
}
}
impl std::fmt::Debug for OptsError {
// `Debug` trait changed from `clap::Error` to be the same as `Display`.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<OptsError as std::fmt::Display>::fmt(self, f)
}
}
impl std::error::Error for OptsError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.error.source()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment