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