mirror of
https://github.com/clap-rs/clap
synced 2024-11-10 06:44:16 +00:00
feat: Add backtraces to errors
This is gated behind the `debug` feature flag so only explicit debugging cases pay the build time and runtime costs. The builder API's stack traces are generally not too interesting. Where this really helps is with `clap_derive`. We currently panic on unexpected conditions which at least gives us a backtrace. We'd like to turn these into errors but to do so would lose those debuggin backtraces, which is where this comes in. This is a part of #2255
This commit is contained in:
parent
00f7fe5472
commit
7b5a4c9c2d
4 changed files with 91 additions and 24 deletions
|
@ -72,6 +72,7 @@ termcolor = { version = "1.1", optional = true }
|
|||
terminal_size = { version = "0.1.12", optional = true }
|
||||
lazy_static = { version = "1", optional = true }
|
||||
regex = { version = "1.0", optional = true }
|
||||
backtrace = { version = "0.3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
regex = "1.0"
|
||||
|
@ -89,7 +90,7 @@ default = [
|
|||
"suggestions",
|
||||
"unicode_help",
|
||||
]
|
||||
debug = ["clap_derive/debug"] # Enables debug messages
|
||||
debug = ["clap_derive/debug", "backtrace"] # Enables debug messages
|
||||
|
||||
# Used in default
|
||||
std = ["indexmap/std"] # support for no_std in a backwards-compatible way
|
||||
|
|
|
@ -432,11 +432,23 @@ pub struct Error {
|
|||
/// Useful when you want to render an error of your own.
|
||||
pub info: Vec<String>,
|
||||
pub(crate) source: Option<Box<dyn error::Error + Send + Sync>>,
|
||||
backtrace: Backtrace,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(&self.message, f)
|
||||
#[cfg(feature = "debug")]
|
||||
{
|
||||
writeln!(f, "{}", self.message)?;
|
||||
writeln!(f)?;
|
||||
writeln!(f, "Backtrace:")?;
|
||||
writeln!(f, "{}", self.backtrace)?;
|
||||
}
|
||||
#[cfg(not(feature = "debug"))]
|
||||
{
|
||||
Display::fmt(&self.message, f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -519,6 +531,16 @@ impl Error {
|
|||
self.message.print()
|
||||
}
|
||||
|
||||
pub(crate) fn new(message: Colorizer, kind: ErrorKind) -> Self {
|
||||
Self {
|
||||
message,
|
||||
kind,
|
||||
info: vec![],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn argument_conflict(
|
||||
app: &App,
|
||||
arg: &Arg,
|
||||
|
@ -556,6 +578,7 @@ impl Error {
|
|||
kind: ErrorKind::ArgumentConflict,
|
||||
info,
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -574,6 +597,7 @@ impl Error {
|
|||
kind: ErrorKind::EmptyValue,
|
||||
info: vec![arg],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -592,6 +616,7 @@ impl Error {
|
|||
kind: ErrorKind::NoEquals,
|
||||
info: vec![arg],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -655,6 +680,7 @@ impl Error {
|
|||
kind: ErrorKind::InvalidValue,
|
||||
info,
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -686,6 +712,7 @@ impl Error {
|
|||
kind: ErrorKind::InvalidSubcommand,
|
||||
info: vec![subcmd],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -704,6 +731,7 @@ impl Error {
|
|||
kind: ErrorKind::UnrecognizedSubcommand,
|
||||
info: vec![subcmd],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -734,6 +762,7 @@ impl Error {
|
|||
kind: ErrorKind::MissingRequiredArgument,
|
||||
info,
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -751,6 +780,7 @@ impl Error {
|
|||
kind: ErrorKind::MissingSubcommand,
|
||||
info: vec![],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -769,6 +799,7 @@ impl Error {
|
|||
kind: ErrorKind::InvalidUtf8,
|
||||
info: vec![],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -801,6 +832,7 @@ impl Error {
|
|||
max_occurs.to_string(),
|
||||
],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -820,6 +852,7 @@ impl Error {
|
|||
kind: ErrorKind::TooManyValues,
|
||||
info: vec![arg, val],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -848,6 +881,7 @@ impl Error {
|
|||
kind: ErrorKind::TooFewValues,
|
||||
info: vec![arg.to_string(), curr_vals.to_string(), min_vals.to_string()],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -862,6 +896,7 @@ impl Error {
|
|||
kind,
|
||||
info,
|
||||
source,
|
||||
backtrace,
|
||||
} = Self::value_validation_with_color(arg, val, err, app.color());
|
||||
try_help(app, &mut message);
|
||||
Self {
|
||||
|
@ -869,6 +904,7 @@ impl Error {
|
|||
kind,
|
||||
info,
|
||||
source,
|
||||
backtrace,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -901,6 +937,7 @@ impl Error {
|
|||
kind: ErrorKind::ValueValidation,
|
||||
info: vec![arg, val, err.to_string()],
|
||||
source: Some(err),
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -929,6 +966,7 @@ impl Error {
|
|||
kind: ErrorKind::WrongNumberOfValues,
|
||||
info: vec![arg.to_string(), curr_vals.to_string(), num_vals.to_string()],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -947,6 +985,7 @@ impl Error {
|
|||
kind: ErrorKind::UnexpectedMultipleUsage,
|
||||
info: vec![arg],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -996,6 +1035,7 @@ impl Error {
|
|||
kind: ErrorKind::UnknownArgument,
|
||||
info: vec![arg],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1018,6 +1058,7 @@ impl Error {
|
|||
kind: ErrorKind::UnknownArgument,
|
||||
info: vec![arg],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1033,6 +1074,7 @@ impl Error {
|
|||
kind: ErrorKind::ArgumentNotFound,
|
||||
info: vec![arg],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1050,6 +1092,7 @@ impl Error {
|
|||
kind,
|
||||
info: vec![],
|
||||
source: None,
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1073,6 +1116,43 @@ impl error::Error for Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
#[derive(Debug)]
|
||||
struct Backtrace(backtrace::Backtrace);
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl Backtrace {
|
||||
fn new() -> Self {
|
||||
Self(backtrace::Backtrace::new())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl Display for Backtrace {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
// `backtrace::Backtrace` uses `Debug` instead of `Display`
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "debug"))]
|
||||
#[derive(Debug)]
|
||||
struct Backtrace;
|
||||
|
||||
#[cfg(not(feature = "debug"))]
|
||||
impl Backtrace {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "debug"))]
|
||||
impl Display for Backtrace {
|
||||
fn fmt(&self, _: &mut Formatter) -> fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
/// Check `clap::Error` impls Send and Sync.
|
||||
|
|
|
@ -759,12 +759,10 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
} else if self.is_set(AS::SubcommandRequiredElseHelp) {
|
||||
debug!("Parser::get_matches_with: SubcommandRequiredElseHelp=true");
|
||||
let message = self.write_help_err()?;
|
||||
return Err(ClapError {
|
||||
return Err(ClapError::new(
|
||||
message,
|
||||
kind: ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
|
||||
info: vec![],
|
||||
source: None,
|
||||
});
|
||||
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
|
||||
));
|
||||
}
|
||||
|
||||
self.remove_overrides(matcher);
|
||||
|
@ -1903,12 +1901,7 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
|
||||
match Help::new(HelpWriter::Buffer(&mut c), self, use_long).write_help() {
|
||||
Err(e) => e.into(),
|
||||
_ => ClapError {
|
||||
message: c,
|
||||
kind: ErrorKind::DisplayHelp,
|
||||
info: vec![],
|
||||
source: None,
|
||||
},
|
||||
_ => ClapError::new(c, ErrorKind::DisplayHelp),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1918,12 +1911,7 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
let msg = self.app._render_version(use_long);
|
||||
let mut c = Colorizer::new(false, self.color_help());
|
||||
c.none(msg);
|
||||
ClapError {
|
||||
message: c,
|
||||
kind: ErrorKind::DisplayVersion,
|
||||
info: vec![],
|
||||
source: None,
|
||||
}
|
||||
ClapError::new(c, ErrorKind::DisplayVersion)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,12 +63,10 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
|
|||
&& self.p.is_set(AS::ArgRequiredElseHelp)
|
||||
{
|
||||
let message = self.p.write_help_err()?;
|
||||
return Err(Error {
|
||||
return Err(Error::new(
|
||||
message,
|
||||
kind: ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
|
||||
info: vec![],
|
||||
source: None,
|
||||
});
|
||||
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
|
||||
));
|
||||
}
|
||||
self.validate_conflicts(matcher)?;
|
||||
if !(self.p.is_set(AS::SubcommandsNegateReqs) && is_subcmd || reqs_validated) {
|
||||
|
|
Loading…
Reference in a new issue