Skip to content

Commit

Permalink
feat(error): Allow reproducing clap's errors
Browse files Browse the repository at this point in the history
Fixes #4362
  • Loading branch information
epage committed Oct 10, 2022
1 parent 5b763e9 commit 502bb93
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 23 deletions.
48 changes: 48 additions & 0 deletions src/builder/value_parser.rs
Expand Up @@ -599,6 +599,54 @@ where
}

/// Parse/validate argument values
///
/// As alternatives to implementing `TypedValueParser`,
/// - Use `Fn(&str) -> Result<T, E>` which implements `TypedValueParser`
/// - [`TypedValueParser::map`] to adapt an existing `TypedValueParser`
///
/// See `ValueParserFactory` for register `TypedValueParser::Value` with
/// [`value_parser!`][crate::value_parser].
///
/// # Example
///
#[cfg_attr(not(feature = "error-context"), doc = " ```ignore")]
#[cfg_attr(feature = "error-context", doc = " ```")]
/// # use clap::error::ErrorKind;
/// # use clap::error::ContextKind;
/// # use clap::error::ContextValue;
/// #[derive(Clone)]
/// struct Custom(u32);
///
/// #[derive(Clone)]
/// struct CustomValueParser;
///
/// impl clap::builder::TypedValueParser for CustomValueParser {
/// type Value = Custom;
///
/// fn parse_ref(
/// &self,
/// cmd: &clap::Command,
/// arg: Option<&clap::Arg>,
/// value: &std::ffi::OsStr,
/// ) -> Result<Self::Value, clap::Error> {
/// let inner = clap::value_parser!(u32);
/// let val = inner.parse_ref(cmd, arg, value)?;
///
/// const INVALID_VALUE: u32 = 10;
/// if val == INVALID_VALUE {
/// let mut err = clap::Error::new(ErrorKind::ValueValidation)
/// .with_cmd(cmd);
/// if let Some(arg) = arg {
/// err.insert(ContextKind::InvalidArg, ContextValue::String(arg.to_string()));
/// }
/// err.insert(ContextKind::InvalidValue, ContextValue::String(INVALID_VALUE.to_string()));
/// return Err(err);
/// }
///
/// Ok(Custom(val))
/// }
/// }
/// ```
pub trait TypedValueParser: Clone + Send + Sync + 'static {
/// Argument's value type
type Value: Send + Sync + Clone;
Expand Down
79 changes: 56 additions & 23 deletions src/error/mod.rs
Expand Up @@ -98,6 +98,55 @@ impl<F: ErrorFormatter> Error<F> {
self.with_cmd(cmd)
}

/// Create an error with a pre-defined message
///
/// See also
/// - [`Error::insert`]
/// - [`Error::with_cmd`]
///
/// # Example
///
#[cfg_attr(not(feature = "error-context"), doc = " ```ignore")]
#[cfg_attr(feature = "error-context", doc = " ```")]
/// # 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,
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_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
Expand Down Expand Up @@ -138,6 +187,13 @@ impl<F: ErrorFormatter> Error<F> {
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<ContextValue> {
self.inner.context.insert(kind, value)
}

/// Should the message be written to `stdout` or not?
#[inline]
pub fn use_stderr(&self) -> bool {
Expand Down Expand Up @@ -216,34 +272,11 @@ impl<F: ErrorFormatter> Error<F> {
self.formatted().into_owned()
}

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,
color_when: ColorChoice::Never,
color_help_when: ColorChoice::Never,
backtrace: Backtrace::new(),
}),
phantom: Default::default(),
}
}

#[inline(never)]
fn for_app(kind: ErrorKind, cmd: &Command, styled: StyledStr) -> Self {
Self::new(kind).set_message(styled).with_cmd(cmd)
}

pub(crate) fn with_cmd(self, cmd: &Command) -> Self {
self.set_color(cmd.get_color())
.set_colored_help(cmd.color_help())
.set_help_flag(format::get_help_flag(cmd))
}

pub(crate) fn set_message(mut self, message: impl Into<Message>) -> Self {
self.inner.message = Some(message.into());
self
Expand Down

0 comments on commit 502bb93

Please sign in to comment.