1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/// An option as it was written on the command line, without its leading dashes.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Opt<'a> {
/// A short option, like `-z` (stored as `'z'`).
Short(char),
/// A long option, like `--bogus` (stored as `"bogus"`).
Long(&'a str),
}
impl core::fmt::Display for Opt<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Opt::Short(short) => write!(f, "-{short}"),
Opt::Long(long) => write!(f, "--{long}"),
}
}
}
/// Errors that can occur when parsing CLI arguments with [`GetArgs`](crate::GetArgs).
///
/// The lifetime `'a` is the lifetime of the parsed arguments: variants describing what the
/// user typed borrow it from the command line instead of copying it, so no allocation is needed.
///
/// Names of the struct's own arguments (`&'static str`) are written as in the generated help:
/// `<INPUT>` for a positional argument, `--output <OUTPUT>` for an option taking a value
/// (`-o <OUTPUT>` when it has no long name).
///
/// The enum is `#[non_exhaustive]`: new variants can be added in a minor release, so a `match` on
/// it needs a wildcard arm.
#[must_use]
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error<'a> {
/// A required positional argument or named option was not provided on the command line.
///
/// The string is the argument name, e.g. `"<INPUT>"` or `"--output <OUTPUT>"`.
MissingArgument(&'static str),
/// An option that the struct does not define was given, e.g. `--bogus` or `-z`.
UnknownOption {
/// The unknown option, as written by the user.
option: Opt<'a>,
/// The closest long option name (without `--`) when an unknown long option looks like a
/// typo of a known one, e.g. `Some("verbose")` for `--verbos`.
suggestion: Option<&'static str>,
},
/// A positional argument was given but the struct expects no more of them.
///
/// The string is the extra argument, as written by the user.
UnexpectedArgument(&'a str),
/// A value was attached to an option that does not take one, e.g. `--verbose=yes` for a flag.
///
/// The option is the one written by the user.
UnexpectedValue(Opt<'a>),
/// An option taking a value was given without one, e.g. `--output` at the end of the
/// command line.
///
/// The string is the option name, e.g. `"--output <OUTPUT>"`.
MissingOptionValue(&'static str),
/// Two arguments that cannot be used together were both given, because of `conflicts_with`
/// or because they belong to the same group.
ArgumentConflict {
/// The argument declaring the conflict (or the first member of the group), e.g.
/// `"--quiet"`.
name: &'static str,
/// The other argument, e.g. `"--verbose"`.
other: &'static str,
},
/// An argument was given without another argument it `requires`.
MissingRequiredArgument {
/// The missing argument, e.g. `"--output <OUTPUT>"`.
name: &'static str,
/// The given argument that requires it, e.g. `"--compress"`.
required_by: &'static str,
},
/// None of a set of arguments was given although one of them is required, because of
/// `required_unless_present` or a `required` group.
///
/// The slice lists the arguments, e.g. `["--config <CONFIG>", "--stdin"]`.
MissingOneOf(&'static [&'static str]),
/// A required subcommand was not given.
///
/// The slice lists the visible subcommands, e.g. `["add", "remove"]`. It is only returned for
/// a `#[command(subcommand)]` field that is not an `Option`.
MissingSubcommand(&'static [&'static str]),
/// A positional argument was given where a subcommand was expected, and it is not the name
/// or an alias of any subcommand.
UnknownSubcommand {
/// The unknown subcommand, as written by the user.
name: &'a str,
/// The closest visible subcommand name or alias when the unknown one looks like a typo of
/// it, e.g. `Some("remove")` for `remov`.
suggestion: Option<&'static str>,
},
/// The underlying `getargs` parser reported an error that this crate does not recognise.
///
/// `getargs` may add error kinds in a compatible release; they are reported as this variant
/// instead of panicking. It is never returned with the `getargs` 0.5 releases known to this
/// crate.
InvalidArguments,
/// A value could not be parsed into the target field type, e.g. `abc` for a `u32` field.
///
/// This is also returned when a `default_value` string does not parse.
InvalidValue {
/// The argument name, e.g. `"--port <PORT>"` or `"<COUNT>"`.
name: &'static str,
/// The value that failed to parse.
value: &'a str,
},
/// A value is not one of the argument's `possible_values`, e.g. `turbo` for
/// `possible_values = ["fast", "slow"]`.
///
/// This is also returned when a `default_value` string is not one of them.
InvalidPossibleValue {
/// The argument name, e.g. `"--mode <MODE>"`.
name: &'static str,
/// The value that was given.
value: &'a str,
/// The accepted values, in declaration order, e.g. `["fast", "slow"]`.
possible_values: &'static [&'static str],
},
/// A value parsed into the field type but is outside the argument's `value_parser` range,
/// e.g. `0` for `value_parser = 1..=64`.
///
/// This is also returned when a `default_value` string is outside the range.
ValueOutOfRange {
/// The argument name, e.g. `"--jobs <JOBS>"`.
name: &'static str,
/// The value that was given.
value: &'a str,
/// The range as written in the attribute, e.g. `"1..=64"`.
range: &'static str,
},
/// More values were given to a repeated argument than its collection can hold, or than its
/// `num_args` range accepts, e.g. a third `-I` for a `Values<&str, 2>` field.
///
/// This is also returned when an environment variable or a default splits into too many
/// values.
TooManyValues {
/// The argument name, e.g. `"--include <INCLUDE>"` or `"<FILES>..."`.
name: &'static str,
/// The first value that did not fit.
value: &'a str,
/// The maximum number of values.
max: usize,
},
/// A positional argument collecting several values received fewer than the minimum of its
/// `num_args` range, but at least one. [`Error::MissingArgument`] is returned when it received
/// none.
TooFewValues {
/// The argument name, e.g. `"<FILES>..."`.
name: &'static str,
/// The minimum number of values.
min: usize,
/// The number of values given.
actual: usize,
},
/// An argument that is not valid UTF-8 was given where text is required, when parsing raw
/// bytes with `from_bytes`, `parse_bytes` or `from_args_os`.
///
/// It is returned for a value of a `&str` field or of a field parsed from a string, and for an
/// argument that matches no field, such as an option name, a subcommand name or an extra
/// positional argument, since those are reported as text by the other variants.
InvalidUtf8 {
/// The argument name, e.g. `"--name <NAME>"`, or `None` for an argument that matches no
/// field.
name: Option<&'static str>,
/// The raw bytes of the argument, displayed with [`Lossy`](crate::Lossy).
value: &'a [u8],
},
/// Help was requested with `-h` or `--help`.
///
/// This is not a parse failure: the string is the generated help text, the struct's `HELP`
/// constant for `-h` and its `LONG_HELP` constant for `--help`. After a subcommand, it is the
/// help of that subcommand instead. Applications should print it to standard output and exit
/// successfully.
/// It is never returned when help is disabled with `#[command(disable_help_flag)]`.
Help(&'static str),
/// The version was requested with `-V` or `--version`.
///
/// This is not a parse failure: the string is the version line, the command name followed by
/// the version (the struct's `VERSION_TEXT` constant). Applications should print it to standard
/// output and exit successfully. It is only returned when `#[command(version)]` is set.
Version(&'static str),
}
impl Error<'_> {
/// Returns `true` if this is [`Error::Help`], i.e. the user asked for help rather than
/// passing invalid arguments.
#[must_use]
pub fn is_help(&self) -> bool {
matches!(self, Error::Help(_))
}
/// Returns `true` if this is [`Error::Version`], i.e. the user asked for the version rather
/// than passing invalid arguments.
#[must_use]
pub fn is_version(&self) -> bool {
matches!(self, Error::Version(_))
}
}
impl core::fmt::Display for Error<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::MissingArgument(name) => {
write!(
f,
"the following required argument was not provided: {name}"
)
}
Error::UnknownOption { option, suggestion } => {
write!(f, "unexpected argument '{option}' found")?;
match suggestion {
Some(suggestion) => {
write!(f, "\n\n tip: a similar argument exists: '--{suggestion}'")
}
None => Ok(()),
}
}
Error::UnexpectedArgument(argument) => {
write!(f, "unexpected argument '{argument}' found")
}
Error::UnexpectedValue(option) => {
write!(
f,
"unexpected value for '{option}': it does not take a value"
)
}
Error::ArgumentConflict { name, other } => {
write!(f, "the argument '{name}' cannot be used with '{other}'")
}
Error::MissingRequiredArgument { name, required_by } => {
write!(
f,
"the following required argument was not provided: {name} (required by '{required_by}')"
)
}
Error::MissingOneOf(names) => {
f.write_str("the following required arguments were not provided: <")?;
for (index, name) in names.iter().enumerate() {
if index > 0 {
f.write_str("|")?;
}
f.write_str(name)?;
}
f.write_str(">")
}
Error::MissingSubcommand(names) => {
f.write_str("a subcommand is required but one was not provided\n [subcommands: ")?;
for (index, name) in names.iter().enumerate() {
if index > 0 {
f.write_str(", ")?;
}
f.write_str(name)?;
}
f.write_str("]")
}
Error::UnknownSubcommand { name, suggestion } => {
write!(f, "unrecognized subcommand '{name}'")?;
match suggestion {
Some(suggestion) => {
write!(f, "\n\n tip: a similar subcommand exists: '{suggestion}'")
}
None => Ok(()),
}
}
Error::InvalidArguments => f.write_str("the arguments could not be parsed"),
Error::MissingOptionValue(name) => {
write!(f, "a value is required for '{name}' but none was supplied")
}
Error::InvalidValue { name, value } => {
write!(f, "invalid value '{value}' for '{name}'")
}
Error::InvalidPossibleValue {
name,
value,
possible_values,
} => {
write!(
f,
"invalid value '{value}' for '{name}'\n [possible values: "
)?;
for (index, possible) in possible_values.iter().enumerate() {
if index > 0 {
f.write_str(", ")?;
}
if possible.is_empty() || possible.contains(char::is_whitespace) {
write!(f, "{possible:?}")?;
} else {
f.write_str(possible)?;
}
}
f.write_str("]")
}
Error::ValueOutOfRange { name, value, range } => {
write!(
f,
"invalid value '{value}' for '{name}': {value} is not in {range}"
)
}
Error::TooManyValues { name, value, .. } => {
write!(
f,
"unexpected value '{value}' for '{name}' found; no more were expected"
)
}
Error::TooFewValues { name, min, actual } => {
let verb = if *actual == 1 { "was" } else { "were" };
write!(
f,
"{min} values required by '{name}'; only {actual} {verb} provided"
)
}
Error::InvalidUtf8 {
name: Some(name),
value,
} => {
write!(
f,
"invalid UTF-8 in value '{}' for '{name}'",
crate::Lossy(value)
)
}
Error::InvalidUtf8 { name: None, value } => {
write!(f, "invalid UTF-8 in argument '{}'", crate::Lossy(value))
}
Error::Help(help) => f.write_str(help),
Error::Version(version) => f.write_str(version),
}
}
}
impl core::error::Error for Error<'_> {}