1use std::fmt;
10
11use winnow::error::{FromExternalError, ModalError, ParserError};
12
13use crate::help::Style;
14use crate::stream::Argv;
15
16pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
18
19#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
21#[non_exhaustive]
22pub enum ErrorKind {
23 Backtrack,
25 UnknownFlag,
27 UnexpectedArg,
29 MissingValue,
31 UnexpectedValue,
33 MissingRequired,
35 MissingArgument,
37 MissingSubcommand,
39 Conflict,
41 MissingOneOf,
43 RequiresDoubleDash,
45 HelpRequested,
48 VersionRequested,
50 InvalidValue,
52 InvalidChoice,
54}
55
56#[derive(Debug)]
58struct Request {
59 command: &'static crate::help::Command,
60 long: bool,
61 bare: bool,
63 path: Vec<&'static str>,
65}
66
67#[derive(Debug, Default)]
68struct Detail {
69 request: Option<Request>,
70 token: String,
72 value: Option<String>,
73 cause: Option<BoxError>,
74}
75
76#[derive(Debug)]
78pub struct Error {
79 kind: ErrorKind,
80 cut: bool,
81 offset: usize,
82 detail: Option<Box<Detail>>,
83}
84
85impl Error {
86 fn with_token(kind: ErrorKind, offset: usize, token: String) -> Self {
87 Self {
88 kind,
89 cut: true,
90 offset,
91 detail: Some(Box::new(Detail {
92 token,
93 ..Detail::default()
94 })),
95 }
96 }
97
98 pub fn unknown_flag(offset: usize, token: impl Into<String>) -> Self {
100 Self::with_token(ErrorKind::UnknownFlag, offset, token.into())
101 }
102
103 pub fn unexpected_arg(offset: usize, token: impl Into<String>) -> Self {
105 Self::with_token(ErrorKind::UnexpectedArg, offset, token.into())
106 }
107
108 pub fn missing_value(offset: usize, flag: impl Into<String>) -> Self {
110 Self::with_token(ErrorKind::MissingValue, offset, flag.into())
111 }
112
113 pub fn unexpected_value(offset: usize, flag: impl Into<String>, value: &[u8]) -> Self {
115 let mut error = Self::with_token(ErrorKind::UnexpectedValue, offset, flag.into());
116 error.detail_mut().value = Some(String::from_utf8_lossy(value).into_owned());
117 error
118 }
119
120 pub fn missing_required(offset: usize, flag: impl Into<String>) -> Self {
122 Self::with_token(ErrorKind::MissingRequired, offset, flag.into())
123 }
124
125 pub fn missing_argument(offset: usize, name: impl Into<String>) -> Self {
127 Self::with_token(ErrorKind::MissingArgument, offset, name.into())
128 }
129
130 pub fn required_by(offset: usize, target: impl Into<String>, name: &str) -> Self {
132 let mut error = Self::with_token(ErrorKind::MissingRequired, offset, target.into());
133 error.detail_mut().value = Some(name.to_owned());
134 error
135 }
136
137 pub fn conflict(offset: usize, name: impl Into<String>, other: &str) -> Self {
139 let mut error = Self::with_token(ErrorKind::Conflict, offset, name.into());
140 error.detail_mut().value = Some(other.to_owned());
141 error
142 }
143
144 pub fn missing_one_of(offset: usize, group: impl Into<String>, members: &[&str]) -> Self {
146 let mut error = Self::with_token(ErrorKind::MissingOneOf, offset, group.into());
147 error.detail_mut().value = Some(members.join(", "));
148 error
149 }
150
151 pub fn requires_double_dash(offset: usize, name: impl Into<String>) -> Self {
153 Self::with_token(ErrorKind::RequiresDoubleDash, offset, name.into())
154 }
155
156 fn request(
157 kind: ErrorKind,
158 command: &'static crate::help::Command,
159 long: bool,
160 bare: bool,
161 ) -> Self {
162 Self {
163 kind,
164 cut: true,
165 offset: 0,
166 detail: Some(Box::new(Detail {
167 request: Some(Request {
168 command,
169 long,
170 bare,
171 path: Vec::new(),
172 }),
173 ..Detail::default()
174 })),
175 }
176 }
177
178 pub fn help(command: &'static crate::help::Command, long: bool) -> Self {
180 Self::request(ErrorKind::HelpRequested, command, long, false)
181 }
182
183 pub fn bare_help(command: &'static crate::help::Command) -> Self {
185 Self::request(ErrorKind::HelpRequested, command, false, true)
186 }
187
188 pub fn version(command: &'static crate::help::Command) -> Self {
190 Self::request(ErrorKind::VersionRequested, command, false, false)
191 }
192
193 pub fn within(mut self, name: &'static str) -> Self {
195 if let Some(request) = self.detail.as_deref_mut().and_then(|d| d.request.as_mut()) {
196 request.path.insert(0, name);
197 }
198 self
199 }
200
201 pub fn render_help(&self, program: &str) -> Option<String> {
204 self.render_help_styled(program, Style::PLAIN)
205 }
206
207 pub fn version_text(&self, program: &str) -> Option<String> {
209 let request = self.detail.as_deref()?.request.as_ref()?;
210 let version = request.command.version?;
211 (self.kind == ErrorKind::VersionRequested).then(|| format!("{program} {version}"))
212 }
213
214 pub fn is_bare_help(&self) -> bool {
216 self.detail
217 .as_deref()
218 .and_then(|d| d.request.as_ref())
219 .is_some_and(|r| r.bare)
220 }
221
222 pub fn missing_subcommand(offset: usize) -> Self {
224 Self {
225 kind: ErrorKind::MissingSubcommand,
226 cut: true,
227 offset,
228 detail: None,
229 }
230 }
231
232 pub fn invalid_value(
234 offset: usize,
235 flag: impl Into<String>,
236 value: &[u8],
237 cause: impl Into<BoxError>,
238 ) -> Self {
239 let cause = cause.into();
240 let kind = if cause.is::<crate::value::ChoiceError>() {
241 ErrorKind::InvalidChoice
242 } else {
243 ErrorKind::InvalidValue
244 };
245 let mut error = Self::with_token(kind, offset, flag.into());
246 let detail = error.detail_mut();
247 detail.value = Some(String::from_utf8_lossy(value).into_owned());
248 detail.cause = Some(cause);
249 error
250 }
251
252 fn detail_mut(&mut self) -> &mut Detail {
253 self.detail.get_or_insert_with(Default::default)
254 }
255
256 pub fn kind(&self) -> ErrorKind {
258 self.kind
259 }
260
261 pub fn offset(&self) -> usize {
263 self.offset
264 }
265
266 pub fn token(&self) -> Option<&str> {
268 self.detail.as_deref().map(|d| d.token.as_str())
269 }
270
271 pub fn value(&self) -> Option<&str> {
273 self.detail.as_deref()?.value.as_deref()
274 }
275
276 pub fn is_cut(&self) -> bool {
278 self.cut
279 }
280}
281
282impl PartialEq for Error {
283 fn eq(&self, other: &Self) -> bool {
285 self.kind == other.kind
286 && self.offset == other.offset
287 && self.token() == other.token()
288 && self.value() == other.value()
289 }
290}
291
292impl fmt::Display for Error {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 f.write_str(&self.message(Style::PLAIN))
295 }
296}
297
298impl Error {
299 pub fn message(&self, style: Style) -> String {
302 let bad = |text: &str| Style::paint(style.invalid(), text);
303 let good = |text: &str| Style::paint(style.valid(), text);
304 let token = self.token().unwrap_or_default();
305 let value = self.value().unwrap_or_default();
306 match self.kind {
307 ErrorKind::Backtrack => "invalid command line".to_owned(),
308 ErrorKind::UnknownFlag => format!("unknown flag `{}`", bad(token)),
309 ErrorKind::UnexpectedArg => format!("unexpected argument `{}`", bad(token)),
310 ErrorKind::MissingValue => format!("`{}` needs a value", good(token)),
311 ErrorKind::UnexpectedValue => format!(
312 "`{}` does not take a value, got `{}`",
313 bad(token),
314 bad(value)
315 ),
316 ErrorKind::MissingRequired => match self.value() {
317 Some(by) => format!("`{}` is required by `{}`", good(token), bad(by)),
318 None => format!("`{}` is required", good(token)),
319 },
320 ErrorKind::Conflict => format!("`{}` cannot be used with `{}`", bad(token), bad(value)),
321 ErrorKind::HelpRequested => "help requested".to_owned(),
322 ErrorKind::VersionRequested => "version requested".to_owned(),
323 ErrorKind::RequiresDoubleDash => {
324 format!("`{}` can only be set after a `--` separator", bad(token))
325 }
326 ErrorKind::MissingOneOf => format!("one of {} is required ({token})", good(value)),
327 ErrorKind::MissingArgument => format!("missing argument `{}`", good(token)),
328 ErrorKind::MissingSubcommand => "a subcommand is required".to_owned(),
329 ErrorKind::InvalidValue | ErrorKind::InvalidChoice => {
330 let mut text = format!("invalid value `{}` for `{token}`", bad(value));
331 if let Some(cause) = self.detail.as_deref().and_then(|d| d.cause.as_ref()) {
332 text.push_str(": ");
333 text.push_str(&cause.to_string());
334 }
335 text
336 }
337 }
338 }
339
340 pub fn render(&self, style: Style) -> String {
343 format!(
344 "{} {}\n\nFor more information, try '{}'.",
345 Style::paint(style.error(), "error:"),
346 self.message(style),
347 Style::paint(style.flag(), "--help"),
348 )
349 }
350
351 pub fn render_help_styled(&self, program: &str, style: Style) -> Option<String> {
353 let request = self.detail.as_deref()?.request.as_ref()?;
354 if self.kind != ErrorKind::HelpRequested {
355 return None;
356 }
357 let mut path = vec![program];
358 path.extend(&request.path);
359 let width = crate::help::width();
360 Some(crate::help::render_styled(
361 request.command,
362 &path,
363 request.long,
364 width,
365 style,
366 ))
367 }
368}
369
370impl std::error::Error for Error {
371 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
372 let cause = self.detail.as_deref()?.cause.as_ref()?;
373 Some(&**cause as &(dyn std::error::Error + 'static))
374 }
375}
376
377impl<'i> ParserError<Argv<'i>> for Error {
378 type Inner = Self;
379
380 #[inline]
381 fn from_input(input: &Argv<'i>) -> Self {
382 Self {
383 kind: ErrorKind::Backtrack,
384 cut: false,
385 offset: input.offset(),
386 detail: None,
387 }
388 }
389
390 #[inline]
393 fn or(self, other: Self) -> Self {
394 if other.kind == ErrorKind::Backtrack && self.kind != ErrorKind::Backtrack {
395 self
396 } else {
397 other
398 }
399 }
400
401 #[inline(always)]
402 fn is_backtrack(&self) -> bool {
403 !self.cut
404 }
405
406 #[inline(always)]
407 fn into_inner(self) -> Result<Self::Inner, Self> {
408 Ok(self)
409 }
410}
411
412impl ModalError for Error {
413 #[inline(always)]
414 fn cut(mut self) -> Self {
415 self.cut = true;
416 self
417 }
418
419 #[inline(always)]
420 fn backtrack(mut self) -> Self {
421 self.cut = false;
422 self
423 }
424}
425
426impl<'i, E> FromExternalError<Argv<'i>, E> for Error
430where
431 E: std::error::Error + Send + Sync + 'static,
432{
433 fn from_external_error(input: &Argv<'i>, e: E) -> Self {
434 let mut error = Self::from_input(input);
435 error.kind = ErrorKind::InvalidValue;
436 error.detail_mut().cause = Some(Box::new(e));
437 error
438 }
439}