Skip to content

Commit 502bb93

Browse files
committed
feat(error): Allow reproducing clap's errors
Fixes #4362
1 parent 5b763e9 commit 502bb93

File tree

2 files changed

+104
-23
lines changed

2 files changed

+104
-23
lines changed

src/builder/value_parser.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,54 @@ where
599599
}
600600

601601
/// Parse/validate argument values
602+
///
603+
/// As alternatives to implementing `TypedValueParser`,
604+
/// - Use `Fn(&str) -> Result<T, E>` which implements `TypedValueParser`
605+
/// - [`TypedValueParser::map`] to adapt an existing `TypedValueParser`
606+
///
607+
/// See `ValueParserFactory` for register `TypedValueParser::Value` with
608+
/// [`value_parser!`][crate::value_parser].
609+
///
610+
/// # Example
611+
///
612+
#[cfg_attr(not(feature = "error-context"), doc = " ```ignore")]
613+
#[cfg_attr(feature = "error-context", doc = " ```")]
614+
/// # use clap::error::ErrorKind;
615+
/// # use clap::error::ContextKind;
616+
/// # use clap::error::ContextValue;
617+
/// #[derive(Clone)]
618+
/// struct Custom(u32);
619+
///
620+
/// #[derive(Clone)]
621+
/// struct CustomValueParser;
622+
///
623+
/// impl clap::builder::TypedValueParser for CustomValueParser {
624+
/// type Value = Custom;
625+
///
626+
/// fn parse_ref(
627+
/// &self,
628+
/// cmd: &clap::Command,
629+
/// arg: Option<&clap::Arg>,
630+
/// value: &std::ffi::OsStr,
631+
/// ) -> Result<Self::Value, clap::Error> {
632+
/// let inner = clap::value_parser!(u32);
633+
/// let val = inner.parse_ref(cmd, arg, value)?;
634+
///
635+
/// const INVALID_VALUE: u32 = 10;
636+
/// if val == INVALID_VALUE {
637+
/// let mut err = clap::Error::new(ErrorKind::ValueValidation)
638+
/// .with_cmd(cmd);
639+
/// if let Some(arg) = arg {
640+
/// err.insert(ContextKind::InvalidArg, ContextValue::String(arg.to_string()));
641+
/// }
642+
/// err.insert(ContextKind::InvalidValue, ContextValue::String(INVALID_VALUE.to_string()));
643+
/// return Err(err);
644+
/// }
645+
///
646+
/// Ok(Custom(val))
647+
/// }
648+
/// }
649+
/// ```
602650
pub trait TypedValueParser: Clone + Send + Sync + 'static {
603651
/// Argument's value type
604652
type Value: Send + Sync + Clone;

src/error/mod.rs

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,55 @@ impl<F: ErrorFormatter> Error<F> {
9898
self.with_cmd(cmd)
9999
}
100100

101+
/// Create an error with a pre-defined message
102+
///
103+
/// See also
104+
/// - [`Error::insert`]
105+
/// - [`Error::with_cmd`]
106+
///
107+
/// # Example
108+
///
109+
#[cfg_attr(not(feature = "error-context"), doc = " ```ignore")]
110+
#[cfg_attr(feature = "error-context", doc = " ```")]
111+
/// # use clap::error::ErrorKind;
112+
/// # use clap::error::ContextKind;
113+
/// # use clap::error::ContextValue;
114+
///
115+
/// let cmd = clap::Command::new("prog");
116+
///
117+
/// let mut err = clap::Error::new(ErrorKind::ValueValidation)
118+
/// .with_cmd(&cmd);
119+
/// err.insert(ContextKind::InvalidArg, ContextValue::String("--foo".to_owned()));
120+
/// err.insert(ContextKind::InvalidValue, ContextValue::String("bar".to_owned()));
121+
///
122+
/// err.print();
123+
/// ```
124+
pub fn new(kind: ErrorKind) -> Self {
125+
Self {
126+
inner: Box::new(ErrorInner {
127+
kind,
128+
#[cfg(feature = "error-context")]
129+
context: FlatMap::new(),
130+
message: None,
131+
source: None,
132+
help_flag: None,
133+
color_when: ColorChoice::Never,
134+
color_help_when: ColorChoice::Never,
135+
backtrace: Backtrace::new(),
136+
}),
137+
phantom: Default::default(),
138+
}
139+
}
140+
141+
/// Apply [`Command`]'s formatting to the error
142+
///
143+
/// Generally, this is used with [`Error::new`]
144+
pub fn with_cmd(self, cmd: &Command) -> Self {
145+
self.set_color(cmd.get_color())
146+
.set_colored_help(cmd.color_help())
147+
.set_help_flag(format::get_help_flag(cmd))
148+
}
149+
101150
/// Apply an alternative formatter to the error
102151
///
103152
/// # Example
@@ -138,6 +187,13 @@ impl<F: ErrorFormatter> Error<F> {
138187
self.inner.context.get(&kind)
139188
}
140189

190+
/// Insert a piece of context
191+
#[inline(never)]
192+
#[cfg(feature = "error-context")]
193+
pub fn insert(&mut self, kind: ContextKind, value: ContextValue) -> Option<ContextValue> {
194+
self.inner.context.insert(kind, value)
195+
}
196+
141197
/// Should the message be written to `stdout` or not?
142198
#[inline]
143199
pub fn use_stderr(&self) -> bool {
@@ -216,34 +272,11 @@ impl<F: ErrorFormatter> Error<F> {
216272
self.formatted().into_owned()
217273
}
218274

219-
fn new(kind: ErrorKind) -> Self {
220-
Self {
221-
inner: Box::new(ErrorInner {
222-
kind,
223-
#[cfg(feature = "error-context")]
224-
context: FlatMap::new(),
225-
message: None,
226-
source: None,
227-
help_flag: None,
228-
color_when: ColorChoice::Never,
229-
color_help_when: ColorChoice::Never,
230-
backtrace: Backtrace::new(),
231-
}),
232-
phantom: Default::default(),
233-
}
234-
}
235-
236275
#[inline(never)]
237276
fn for_app(kind: ErrorKind, cmd: &Command, styled: StyledStr) -> Self {
238277
Self::new(kind).set_message(styled).with_cmd(cmd)
239278
}
240279

241-
pub(crate) fn with_cmd(self, cmd: &Command) -> Self {
242-
self.set_color(cmd.get_color())
243-
.set_colored_help(cmd.color_help())
244-
.set_help_flag(format::get_help_flag(cmd))
245-
}
246-
247280
pub(crate) fn set_message(mut self, message: impl Into<Message>) -> Self {
248281
self.inner.message = Some(message.into());
249282
self

0 commit comments

Comments
 (0)