//! Error reporting #![cfg_attr(not(feature = "error-context"), allow(dead_code))] #![cfg_attr(not(feature = "error-context"), allow(unused_imports))] #![cfg_attr(not(feature = "error-context"), allow(unused_variables))] #![cfg_attr(not(feature = "error-context"), allow(unused_mut))] #![cfg_attr(not(feature = "error-context"), allow(clippy::let_and_return))] // Std use std::{ borrow::Cow, convert::From, error, fmt::{self, Debug, Display, Formatter}, io::{self}, result::Result as StdResult, }; // Internal use crate::builder::StyledStr; use crate::builder::Styles; use crate::output::fmt::Colorizer; use crate::output::fmt::Stream; use crate::parser::features::suggestions; use crate::util::FlatMap; use crate::util::{color::ColorChoice, safe_exit, SUCCESS_CODE, USAGE_CODE}; use crate::Command; #[cfg(feature = "error-context")] mod context; mod format; mod kind; pub use format::ErrorFormatter; pub use format::KindFormatter; pub use kind::ErrorKind; #[cfg(feature = "error-context")] pub use context::ContextKind; #[cfg(feature = "error-context")] pub use context::ContextValue; #[cfg(feature = "error-context")] pub use format::RichFormatter; #[cfg(not(feature = "error-context"))] pub use KindFormatter as DefaultFormatter; #[cfg(feature = "error-context")] pub use RichFormatter as DefaultFormatter; /// Short hand for [`Result`] type /// /// [`Result`]: std::result::Result pub type Result = StdResult; /// Command Line Argument Parser Error /// /// See [`Command::error`] to create an error. /// /// [`Command::error`]: crate::Command::error pub struct Error { inner: Box, phantom: std::marker::PhantomData, } #[derive(Debug)] struct ErrorInner { kind: ErrorKind, #[cfg(feature = "error-context")] context: FlatMap, message: Option, source: Option>, help_flag: Option<&'static str>, styles: Styles, color_when: ColorChoice, color_help_when: ColorChoice, backtrace: Option, } impl Error { /// Create an unformatted error /// /// This is for you need to pass the error up to /// a place that has access to the `Command` at which point you can call [`Error::format`]. /// /// Prefer [`Command::error`] for generating errors. /// /// [`Command::error`]: crate::Command::error pub fn raw(kind: ErrorKind, message: impl std::fmt::Display) -> Self { Self::new(kind).set_message(message.to_string()) } /// Format the existing message with the Command's context #[must_use] pub fn format(mut self, cmd: &mut Command) -> Self { cmd._build_self(false); let usage = cmd.render_usage_(); if let Some(message) = self.inner.message.as_mut() { message.format(cmd, usage); } self.with_cmd(cmd) } /// Create an error with a pre-defined message /// /// See also /// - [`Error::insert`] /// - [`Error::with_cmd`] /// /// # Example /// /// ```rust /// # #[cfg(feature = "error-context")] { /// # use clap_builder as clap; /// # use clap::error::ErrorKind; /// # use clap::error::ContextKind; /// # use clap::error::ContextValue; /// /// let cmd = clap::Command::new("prog"); /// /// let mut err = clap::Error::new(ErrorKind::ValueValidation) /// .with_cmd(&cmd); /// err.insert(ContextKind::InvalidArg, ContextValue::String("--foo".to_owned())); /// err.insert(ContextKind::InvalidValue, ContextValue::String("bar".to_owned())); /// /// err.print(); /// # } /// ``` pub fn new(kind: ErrorKind) -> Self { Self { inner: Box::new(ErrorInner { kind, #[cfg(feature = "error-context")] context: FlatMap::new(), message: None, source: None, help_flag: None, styles: Styles::plain(), color_when: ColorChoice::Never, color_help_when: ColorChoice::Never, backtrace: Backtrace::new(), }), phantom: Default::default(), } } /// Apply [`Command`]'s formatting to the error /// /// Generally, this is used with [`Error::new`] pub fn with_cmd(self, cmd: &Command) -> Self { self.set_styles(cmd.get_styles().clone()) .set_color(cmd.get_color()) .set_colored_help(cmd.color_help()) .set_help_flag(format::get_help_flag(cmd)) } /// Apply an alternative formatter to the error /// /// # Example /// /// ```rust /// # use clap_builder as clap; /// # use clap::Command; /// # use clap::Arg; /// # use clap::error::KindFormatter; /// let cmd = Command::new("foo") /// .arg(Arg::new("input").required(true)); /// let matches = cmd /// .try_get_matches_from(["foo", "input.txt"]) /// .map_err(|e| e.apply::()) /// .unwrap_or_else(|e| e.exit()); /// ``` pub fn apply(self) -> Error { Error { inner: self.inner, phantom: Default::default(), } } /// Type of error for programmatic processing pub fn kind(&self) -> ErrorKind { self.inner.kind } /// Additional information to further qualify the error #[cfg(feature = "error-context")] pub fn context(&self) -> impl Iterator { self.inner.context.iter().map(|(k, v)| (*k, v)) } /// Lookup a piece of context #[inline(never)] #[cfg(feature = "error-context")] pub fn get(&self, kind: ContextKind) -> Option<&ContextValue> { self.inner.context.get(&kind) } /// Insert a piece of context #[inline(never)] #[cfg(feature = "error-context")] pub fn insert(&mut self, kind: ContextKind, value: ContextValue) -> Option { self.inner.context.insert(kind, value) } /// Should the message be written to `stdout` or not? #[inline] pub fn use_stderr(&self) -> bool { self.stream() == Stream::Stderr } pub(crate) fn stream(&self) -> Stream { match self.kind() { ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => Stream::Stdout, _ => Stream::Stderr, } } /// Returns the exit code that `.exit` will exit the process with. /// /// When the error's kind would print to `stderr` this returns `2`, /// else it returns `0`. pub fn exit_code(&self) -> i32 { if self.use_stderr() { USAGE_CODE } else { SUCCESS_CODE } } /// Prints the error and exits. /// /// Depending on the error kind, this either prints to `stderr` and exits with a status of `2` /// or prints to `stdout` and exits with a status of `0`. pub fn exit(&self) -> ! { // Swallow broken pipe errors let _ = self.print(); safe_exit(self.exit_code()) } /// Prints formatted and colored error to `stdout` or `stderr` according to its error kind /// /// # Example /// ```no_run /// # use clap_builder as clap; /// use clap::Command; /// /// match Command::new("Command").try_get_matches() { /// Ok(matches) => { /// // do_something /// }, /// Err(err) => { /// err.print().expect("Error writing Error"); /// // do_something /// }, /// }; /// ``` pub fn print(&self) -> io::Result<()> { let style = self.formatted(); let color_when = if matches!( self.kind(), ErrorKind::DisplayHelp | ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand, ) { self.inner.color_help_when } else { self.inner.color_when }; let c = Colorizer::new(self.stream(), color_when).with_content(style.into_owned()); c.print() } /// Render the error message to a [`StyledStr`]. /// /// # Example /// ```no_run /// # use clap_builder as clap; /// use clap::Command; /// /// match Command::new("Command").try_get_matches() { /// Ok(matches) => { /// // do_something /// }, /// Err(err) => { /// let err = err.render(); /// println!("{err}"); /// // do_something /// }, /// }; /// ``` pub fn render(&self) -> StyledStr { self.formatted().into_owned() } #[inline(never)] fn for_app(kind: ErrorKind, cmd: &Command, styled: StyledStr) -> Self { Self::new(kind).set_message(styled).with_cmd(cmd) } pub(crate) fn set_message(mut self, message: impl Into) -> Self { self.inner.message = Some(message.into()); self } pub(crate) fn set_source(mut self, source: Box) -> Self { self.inner.source = Some(source); self } pub(crate) fn set_styles(mut self, styles: Styles) -> Self { self.inner.styles = styles; self } pub(crate) fn set_color(mut self, color_when: ColorChoice) -> Self { self.inner.color_when = color_when; self } pub(crate) fn set_colored_help(mut self, color_help_when: ColorChoice) -> Self { self.inner.color_help_when = color_help_when; self } pub(crate) fn set_help_flag(mut self, help_flag: Option<&'static str>) -> Self { self.inner.help_flag = help_flag; self } /// Does not verify if `ContextKind` is already present #[inline(never)] #[cfg(feature = "error-context")] pub(crate) fn insert_context_unchecked( mut self, kind: ContextKind, value: ContextValue, ) -> Self { self.inner.context.insert_unchecked(kind, value); self } /// Does not verify if `ContextKind` is already present #[inline(never)] #[cfg(feature = "error-context")] pub(crate) fn extend_context_unchecked( mut self, context: [(ContextKind, ContextValue); N], ) -> Self { self.inner.context.extend_unchecked(context); self } pub(crate) fn display_help(cmd: &Command, styled: StyledStr) -> Self { Self::for_app(ErrorKind::DisplayHelp, cmd, styled) } pub(crate) fn display_help_error(cmd: &Command, styled: StyledStr) -> Self { Self::for_app( ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand, cmd, styled, ) } pub(crate) fn display_version(cmd: &Command, styled: StyledStr) -> Self { Self::for_app(ErrorKind::DisplayVersion, cmd, styled) } pub(crate) fn argument_conflict( cmd: &Command, arg: String, mut others: Vec, usage: Option, ) -> Self { let mut err = Self::new(ErrorKind::ArgumentConflict).with_cmd(cmd); #[cfg(feature = "error-context")] { let others = match others.len() { 0 => ContextValue::None, 1 => ContextValue::String(others.pop().unwrap()), _ => ContextValue::Strings(others), }; err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::PriorArg, others), ]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); } } err } pub(crate) fn empty_value(cmd: &Command, good_vals: &[String], arg: String) -> Self { Self::invalid_value(cmd, "".to_owned(), good_vals, arg) } pub(crate) fn no_equals(cmd: &Command, arg: String, usage: Option) -> Self { let mut err = Self::new(ErrorKind::NoEquals).with_cmd(cmd); #[cfg(feature = "error-context")] { err = err .extend_context_unchecked([(ContextKind::InvalidArg, ContextValue::String(arg))]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); } } err } pub(crate) fn invalid_value( cmd: &Command, bad_val: String, good_vals: &[String], arg: String, ) -> Self { let suggestion = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop(); let mut err = Self::new(ErrorKind::InvalidValue).with_cmd(cmd); #[cfg(feature = "error-context")] { err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidValue, ContextValue::String(bad_val)), ( ContextKind::ValidValue, ContextValue::Strings(good_vals.iter().map(|s| (*s).to_owned()).collect()), ), ]); if let Some(suggestion) = suggestion { err = err.insert_context_unchecked( ContextKind::SuggestedValue, ContextValue::String(suggestion), ); } } err } pub(crate) fn invalid_subcommand( cmd: &Command, subcmd: String, did_you_mean: Vec, name: String, suggested_trailing_arg: bool, usage: Option, ) -> Self { use std::fmt::Write as _; let styles = cmd.get_styles(); let invalid = &styles.get_invalid(); let valid = &styles.get_valid(); let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd); #[cfg(feature = "error-context")] { let mut suggestions = vec![]; if suggested_trailing_arg { let mut styled_suggestion = StyledStr::new(); let _ = write!( styled_suggestion, "to pass '{}{subcmd}{}' as a value, use '{}{name} -- {subcmd}{}'", invalid.render(), invalid.render_reset(), valid.render(), valid.render_reset() ); suggestions.push(styled_suggestion); } err = err.extend_context_unchecked([ (ContextKind::InvalidSubcommand, ContextValue::String(subcmd)), ( ContextKind::SuggestedSubcommand, ContextValue::Strings(did_you_mean), ), ( ContextKind::Suggested, ContextValue::StyledStrs(suggestions), ), ]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); } } err } pub(crate) fn unrecognized_subcommand( cmd: &Command, subcmd: String, usage: Option, ) -> Self { let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd); #[cfg(feature = "error-context")] { err = err.extend_context_unchecked([( ContextKind::InvalidSubcommand, ContextValue::String(subcmd), )]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); } } err } pub(crate) fn missing_required_argument( cmd: &Command, required: Vec, usage: Option, ) -> Self { let mut err = Self::new(ErrorKind::MissingRequiredArgument).with_cmd(cmd); #[cfg(feature = "error-context")] { err = err.extend_context_unchecked([( ContextKind::InvalidArg, ContextValue::Strings(required), )]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); } } err } pub(crate) fn missing_subcommand( cmd: &Command, parent: String, available: Vec, usage: Option, ) -> Self { let mut err = Self::new(ErrorKind::MissingSubcommand).with_cmd(cmd); #[cfg(feature = "error-context")] { err = err.extend_context_unchecked([ (ContextKind::InvalidSubcommand, ContextValue::String(parent)), ( ContextKind::ValidSubcommand, ContextValue::Strings(available), ), ]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); } } err } pub(crate) fn invalid_utf8(cmd: &Command, usage: Option) -> Self { let mut err = Self::new(ErrorKind::InvalidUtf8).with_cmd(cmd); #[cfg(feature = "error-context")] { if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); } } err } pub(crate) fn too_many_values( cmd: &Command, val: String, arg: String, usage: Option, ) -> Self { let mut err = Self::new(ErrorKind::TooManyValues).with_cmd(cmd); #[cfg(feature = "error-context")] { err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidValue, ContextValue::String(val)), ]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); } } err } pub(crate) fn too_few_values( cmd: &Command, arg: String, min_vals: usize, curr_vals: usize, usage: Option, ) -> Self { let mut err = Self::new(ErrorKind::TooFewValues).with_cmd(cmd); #[cfg(feature = "error-context")] { err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), ( ContextKind::MinValues, ContextValue::Number(min_vals as isize), ), ( ContextKind::ActualNumValues, ContextValue::Number(curr_vals as isize), ), ]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); } } err } pub(crate) fn value_validation( arg: String, val: String, err: Box, ) -> Self { let mut err = Self::new(ErrorKind::ValueValidation).set_source(err); #[cfg(feature = "error-context")] { err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidValue, ContextValue::String(val)), ]); } err } pub(crate) fn wrong_number_of_values( cmd: &Command, arg: String, num_vals: usize, curr_vals: usize, usage: Option, ) -> Self { let mut err = Self::new(ErrorKind::WrongNumberOfValues).with_cmd(cmd); #[cfg(feature = "error-context")] { err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), ( ContextKind::ExpectedNumValues, ContextValue::Number(num_vals as isize), ), ( ContextKind::ActualNumValues, ContextValue::Number(curr_vals as isize), ), ]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); } } err } pub(crate) fn unknown_argument( cmd: &Command, arg: String, did_you_mean: Option<(String, Option)>, suggested_trailing_arg: bool, usage: Option, ) -> Self { use std::fmt::Write as _; let styles = cmd.get_styles(); let invalid = &styles.get_invalid(); let valid = &styles.get_valid(); let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd); #[cfg(feature = "error-context")] { let mut suggestions = vec![]; if suggested_trailing_arg { let mut styled_suggestion = StyledStr::new(); let _ = write!( styled_suggestion, "to pass '{}{arg}{}' as a value, use '{}-- {arg}{}'", invalid.render(), invalid.render_reset(), valid.render(), valid.render_reset() ); suggestions.push(styled_suggestion); } err = err .extend_context_unchecked([(ContextKind::InvalidArg, ContextValue::String(arg))]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); } match did_you_mean { Some((flag, Some(sub))) => { let mut styled_suggestion = StyledStr::new(); let _ = write!( styled_suggestion, "'{}{sub} {flag}{}' exists", valid.render(), valid.render_reset() ); suggestions.push(styled_suggestion); } Some((flag, None)) => { err = err.insert_context_unchecked( ContextKind::SuggestedArg, ContextValue::String(flag), ); } None => {} } if !suggestions.is_empty() { err = err.insert_context_unchecked( ContextKind::Suggested, ContextValue::StyledStrs(suggestions), ); } } err } pub(crate) fn unnecessary_double_dash( cmd: &Command, arg: String, usage: Option, ) -> Self { use std::fmt::Write as _; let styles = cmd.get_styles(); let invalid = &styles.get_invalid(); let valid = &styles.get_valid(); let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd); #[cfg(feature = "error-context")] { let mut styled_suggestion = StyledStr::new(); let _ = write!( styled_suggestion, "subcommand '{}{arg}{}' exists; to use it, remove the '{}--{}' before it", valid.render(), valid.render_reset(), invalid.render(), invalid.render_reset() ); err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), ( ContextKind::Suggested, ContextValue::StyledStrs(vec![styled_suggestion]), ), ]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); } } err } fn formatted(&self) -> Cow<'_, StyledStr> { if let Some(message) = self.inner.message.as_ref() { message.formatted(&self.inner.styles) } else { let styled = F::format_error(self); Cow::Owned(styled) } } } impl From for Error { fn from(e: io::Error) -> Self { Error::raw(ErrorKind::Io, e) } } impl From for Error { fn from(e: fmt::Error) -> Self { Error::raw(ErrorKind::Format, e) } } impl std::fmt::Debug for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { self.inner.fmt(f) } } impl error::Error for Error { #[allow(trivial_casts)] fn source(&self) -> Option<&(dyn error::Error + 'static)> { self.inner.source.as_ref().map(|e| e.as_ref() as _) } } impl Display for Error { fn fmt(&self, f: &mut Formatter) -> fmt::Result { // Assuming `self.message` already has a trailing newline, from `try_help` or similar ok!(write!(f, "{}", self.formatted())); if let Some(backtrace) = self.inner.backtrace.as_ref() { ok!(writeln!(f)); ok!(writeln!(f, "Backtrace:")); ok!(writeln!(f, "{backtrace}")); } Ok(()) } } #[derive(Clone, Debug)] pub(crate) enum Message { Raw(String), Formatted(StyledStr), } impl Message { fn format(&mut self, cmd: &Command, usage: Option) { match self { Message::Raw(s) => { let mut message = String::new(); std::mem::swap(s, &mut message); let styled = format::format_error_message( &message, cmd.get_styles(), Some(cmd), usage.as_ref(), ); *self = Self::Formatted(styled); } Message::Formatted(_) => {} } } fn formatted(&self, styles: &Styles) -> Cow { match self { Message::Raw(s) => { let styled = format::format_error_message(s, styles, None, None); Cow::Owned(styled) } Message::Formatted(s) => Cow::Borrowed(s), } } } impl From for Message { fn from(inner: String) -> Self { Self::Raw(inner) } } impl From for Message { fn from(inner: StyledStr) -> Self { Self::Formatted(inner) } } #[cfg(feature = "debug")] #[derive(Debug)] struct Backtrace(backtrace::Backtrace); #[cfg(feature = "debug")] impl Backtrace { fn new() -> Option { Some(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() -> Option { None } } #[cfg(not(feature = "debug"))] impl Display for Backtrace { fn fmt(&self, _: &mut Formatter) -> fmt::Result { Ok(()) } } #[test] fn check_auto_traits() { static_assertions::assert_impl_all!(Error: Send, Sync, Unpin); }