1//! Error reporting
2
3#![cfg_attr(not(feature = "error-context"), allow(dead_code))]
4#![cfg_attr(not(feature = "error-context"), allow(unused_imports))]
5#![cfg_attr(not(feature = "error-context"), allow(unused_variables))]
6#![cfg_attr(not(feature = "error-context"), allow(unused_mut))]
7#![cfg_attr(not(feature = "error-context"), allow(clippy::let_and_return))]
8
9// Std
10use std::{
11 borrow::Cow,
12 convert::From,
13 error,
14 fmt::{self, Debug, Display, Formatter},
15 io::{self},
16 result::Result as StdResult,
17};
18
19// Internal
20use crate::builder::StyledStr;
21use crate::builder::Styles;
22use crate::output::fmt::Colorizer;
23use crate::output::fmt::Stream;
24use crate::parser::features::suggestions;
25use crate::util::FlatMap;
26use crate::util::{color::ColorChoice, safe_exit, SUCCESS_CODE, USAGE_CODE};
27use crate::Command;
28
29#[cfg(feature = "error-context")]
30mod context;
31mod format;
32mod kind;
33
34pub use format::ErrorFormatter;
35pub use format::KindFormatter;
36pub use kind::ErrorKind;
37
38#[cfg(feature = "error-context")]
39pub use context::ContextKind;
40#[cfg(feature = "error-context")]
41pub use context::ContextValue;
42#[cfg(feature = "error-context")]
43pub use format::RichFormatter;
44
45#[cfg(not(feature = "error-context"))]
46pub use KindFormatter as DefaultFormatter;
47#[cfg(feature = "error-context")]
48pub use RichFormatter as DefaultFormatter;
49
50/// Short hand for [`Result`] type
51///
52/// [`Result`]: std::result::Result
53pub type Result<T, E = Error> = StdResult<T, E>;
54
55/// Command Line Argument Parser Error
56///
57/// See [`Command::error`] to create an error.
58///
59/// [`Command::error`]: crate::Command::error
60pub struct Error<F: ErrorFormatter = DefaultFormatter> {
61 inner: Box<ErrorInner>,
62 phantom: std::marker::PhantomData<F>,
63}
64
65#[derive(Debug)]
66struct ErrorInner {
67 kind: ErrorKind,
68 #[cfg(feature = "error-context")]
69 context: FlatMap<ContextKind, ContextValue>,
70 message: Option<Message>,
71 source: Option<Box<dyn error::Error + Send + Sync>>,
72 help_flag: Option<&'static str>,
73 styles: Styles,
74 color_when: ColorChoice,
75 color_help_when: ColorChoice,
76 backtrace: Option<Backtrace>,
77}
78
79impl<F: ErrorFormatter> Error<F> {
80 /// Create an unformatted error
81 ///
82 /// This is for you need to pass the error up to
83 /// a place that has access to the `Command` at which point you can call [`Error::format`].
84 ///
85 /// Prefer [`Command::error`] for generating errors.
86 ///
87 /// [`Command::error`]: crate::Command::error
88 pub fn raw(kind: ErrorKind, message: impl std::fmt::Display) -> Self {
89 Self::new(kind).set_message(message.to_string())
90 }
91
92 /// Format the existing message with the Command's context
93 #[must_use]
94 pub fn format(mut self, cmd: &mut Command) -> Self {
95 cmd._build_self(false);
96 let usage = cmd.render_usage_();
97 if let Some(message) = self.inner.message.as_mut() {
98 message.format(cmd, usage);
99 }
100 self.with_cmd(cmd)
101 }
102
103 /// Create an error with a pre-defined message
104 ///
105 /// See also
106 /// - [`Error::insert`]
107 /// - [`Error::with_cmd`]
108 ///
109 /// # Example
110 ///
111 /// ```rust
112 /// # #[cfg(feature = "error-context")] {
113 /// # use clap_builder as clap;
114 /// # use clap::error::ErrorKind;
115 /// # use clap::error::ContextKind;
116 /// # use clap::error::ContextValue;
117 ///
118 /// let cmd = clap::Command::new("prog");
119 ///
120 /// let mut err = clap::Error::new(ErrorKind::ValueValidation)
121 /// .with_cmd(&cmd);
122 /// err.insert(ContextKind::InvalidArg, ContextValue::String("--foo".to_owned()));
123 /// err.insert(ContextKind::InvalidValue, ContextValue::String("bar".to_owned()));
124 ///
125 /// err.print();
126 /// # }
127 /// ```
128 pub fn new(kind: ErrorKind) -> Self {
129 Self {
130 inner: Box::new(ErrorInner {
131 kind,
132 #[cfg(feature = "error-context")]
133 context: FlatMap::new(),
134 message: None,
135 source: None,
136 help_flag: None,
137 styles: Styles::plain(),
138 color_when: ColorChoice::Never,
139 color_help_when: ColorChoice::Never,
140 backtrace: Backtrace::new(),
141 }),
142 phantom: Default::default(),
143 }
144 }
145
146 /// Apply [`Command`]'s formatting to the error
147 ///
148 /// Generally, this is used with [`Error::new`]
149 pub fn with_cmd(self, cmd: &Command) -> Self {
150 self.set_styles(cmd.get_styles().clone())
151 .set_color(cmd.get_color())
152 .set_colored_help(cmd.color_help())
153 .set_help_flag(format::get_help_flag(cmd))
154 }
155
156 /// Apply an alternative formatter to the error
157 ///
158 /// # Example
159 ///
160 /// ```rust
161 /// # use clap_builder as clap;
162 /// # use clap::Command;
163 /// # use clap::Arg;
164 /// # use clap::error::KindFormatter;
165 /// let cmd = Command::new("foo")
166 /// .arg(Arg::new("input").required(true));
167 /// let matches = cmd
168 /// .try_get_matches_from(["foo", "input.txt"])
169 /// .map_err(|e| e.apply::<KindFormatter>())
170 /// .unwrap_or_else(|e| e.exit());
171 /// ```
172 pub fn apply<EF: ErrorFormatter>(self) -> Error<EF> {
173 Error {
174 inner: self.inner,
175 phantom: Default::default(),
176 }
177 }
178
179 /// Type of error for programmatic processing
180 pub fn kind(&self) -> ErrorKind {
181 self.inner.kind
182 }
183
184 /// Additional information to further qualify the error
185 #[cfg(feature = "error-context")]
186 pub fn context(&self) -> impl Iterator<Item = (ContextKind, &ContextValue)> {
187 self.inner.context.iter().map(|(k, v)| (*k, v))
188 }
189
190 /// Lookup a piece of context
191 #[inline(never)]
192 #[cfg(feature = "error-context")]
193 pub fn get(&self, kind: ContextKind) -> Option<&ContextValue> {
194 self.inner.context.get(&kind)
195 }
196
197 /// Insert a piece of context
198 #[inline(never)]
199 #[cfg(feature = "error-context")]
200 pub fn insert(&mut self, kind: ContextKind, value: ContextValue) -> Option<ContextValue> {
201 self.inner.context.insert(kind, value)
202 }
203
204 /// Should the message be written to `stdout` or not?
205 #[inline]
206 pub fn use_stderr(&self) -> bool {
207 self.stream() == Stream::Stderr
208 }
209
210 pub(crate) fn stream(&self) -> Stream {
211 match self.kind() {
212 ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => Stream::Stdout,
213 _ => Stream::Stderr,
214 }
215 }
216
217 /// Returns the exit code that `.exit` will exit the process with.
218 ///
219 /// When the error's kind would print to `stderr` this returns `2`,
220 /// else it returns `0`.
221 pub fn exit_code(&self) -> i32 {
222 if self.use_stderr() {
223 USAGE_CODE
224 } else {
225 SUCCESS_CODE
226 }
227 }
228
229 /// Prints the error and exits.
230 ///
231 /// Depending on the error kind, this either prints to `stderr` and exits with a status of `2`
232 /// or prints to `stdout` and exits with a status of `0`.
233 pub fn exit(&self) -> ! {
234 // Swallow broken pipe errors
235 let _ = self.print();
236 safe_exit(self.exit_code())
237 }
238
239 /// Prints formatted and colored error to `stdout` or `stderr` according to its error kind
240 ///
241 /// # Example
242 /// ```no_run
243 /// # use clap_builder as clap;
244 /// use clap::Command;
245 ///
246 /// match Command::new("Command").try_get_matches() {
247 /// Ok(matches) => {
248 /// // do_something
249 /// },
250 /// Err(err) => {
251 /// err.print().expect("Error writing Error");
252 /// // do_something
253 /// },
254 /// };
255 /// ```
256 pub fn print(&self) -> io::Result<()> {
257 let style = self.formatted();
258 let color_when = if matches!(
259 self.kind(),
260 ErrorKind::DisplayHelp | ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
261 ) {
262 self.inner.color_help_when
263 } else {
264 self.inner.color_when
265 };
266 let c = Colorizer::new(self.stream(), color_when).with_content(style.into_owned());
267 c.print()
268 }
269
270 /// Render the error message to a [`StyledStr`].
271 ///
272 /// # Example
273 /// ```no_run
274 /// # use clap_builder as clap;
275 /// use clap::Command;
276 ///
277 /// match Command::new("Command").try_get_matches() {
278 /// Ok(matches) => {
279 /// // do_something
280 /// },
281 /// Err(err) => {
282 /// let err = err.render();
283 /// println!("{err}");
284 /// // do_something
285 /// },
286 /// };
287 /// ```
288 pub fn render(&self) -> StyledStr {
289 self.formatted().into_owned()
290 }
291
292 #[inline(never)]
293 fn for_app(kind: ErrorKind, cmd: &Command, styled: StyledStr) -> Self {
294 Self::new(kind).set_message(styled).with_cmd(cmd)
295 }
296
297 pub(crate) fn set_message(mut self, message: impl Into<Message>) -> Self {
298 self.inner.message = Some(message.into());
299 self
300 }
301
302 pub(crate) fn set_source(mut self, source: Box<dyn error::Error + Send + Sync>) -> Self {
303 self.inner.source = Some(source);
304 self
305 }
306
307 pub(crate) fn set_styles(mut self, styles: Styles) -> Self {
308 self.inner.styles = styles;
309 self
310 }
311
312 pub(crate) fn set_color(mut self, color_when: ColorChoice) -> Self {
313 self.inner.color_when = color_when;
314 self
315 }
316
317 pub(crate) fn set_colored_help(mut self, color_help_when: ColorChoice) -> Self {
318 self.inner.color_help_when = color_help_when;
319 self
320 }
321
322 pub(crate) fn set_help_flag(mut self, help_flag: Option<&'static str>) -> Self {
323 self.inner.help_flag = help_flag;
324 self
325 }
326
327 /// Does not verify if `ContextKind` is already present
328 #[inline(never)]
329 #[cfg(feature = "error-context")]
330 pub(crate) fn insert_context_unchecked(
331 mut self,
332 kind: ContextKind,
333 value: ContextValue,
334 ) -> Self {
335 self.inner.context.insert_unchecked(kind, value);
336 self
337 }
338
339 /// Does not verify if `ContextKind` is already present
340 #[inline(never)]
341 #[cfg(feature = "error-context")]
342 pub(crate) fn extend_context_unchecked<const N: usize>(
343 mut self,
344 context: [(ContextKind, ContextValue); N],
345 ) -> Self {
346 self.inner.context.extend_unchecked(context);
347 self
348 }
349
350 pub(crate) fn display_help(cmd: &Command, styled: StyledStr) -> Self {
351 Self::for_app(ErrorKind::DisplayHelp, cmd, styled)
352 }
353
354 pub(crate) fn display_help_error(cmd: &Command, styled: StyledStr) -> Self {
355 Self::for_app(
356 ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
357 cmd,
358 styled,
359 )
360 }
361
362 pub(crate) fn display_version(cmd: &Command, styled: StyledStr) -> Self {
363 Self::for_app(ErrorKind::DisplayVersion, cmd, styled)
364 }
365
366 pub(crate) fn argument_conflict(
367 cmd: &Command,
368 arg: String,
369 mut others: Vec<String>,
370 usage: Option<StyledStr>,
371 ) -> Self {
372 let mut err = Self::new(ErrorKind::ArgumentConflict).with_cmd(cmd);
373
374 #[cfg(feature = "error-context")]
375 {
376 let others = match others.len() {
377 0 => ContextValue::None,
378 1 => ContextValue::String(others.pop().unwrap()),
379 _ => ContextValue::Strings(others),
380 };
381 err = err.extend_context_unchecked([
382 (ContextKind::InvalidArg, ContextValue::String(arg)),
383 (ContextKind::PriorArg, others),
384 ]);
385 if let Some(usage) = usage {
386 err = err
387 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
388 }
389 }
390
391 err
392 }
393
394 pub(crate) fn subcommand_conflict(
395 cmd: &Command,
396 sub: String,
397 mut others: Vec<String>,
398 usage: Option<StyledStr>,
399 ) -> Self {
400 let mut err = Self::new(ErrorKind::ArgumentConflict).with_cmd(cmd);
401
402 #[cfg(feature = "error-context")]
403 {
404 let others = match others.len() {
405 0 => ContextValue::None,
406 1 => ContextValue::String(others.pop().unwrap()),
407 _ => ContextValue::Strings(others),
408 };
409 err = err.extend_context_unchecked([
410 (ContextKind::InvalidSubcommand, ContextValue::String(sub)),
411 (ContextKind::PriorArg, others),
412 ]);
413 if let Some(usage) = usage {
414 err = err
415 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
416 }
417 }
418
419 err
420 }
421
422 pub(crate) fn empty_value(cmd: &Command, good_vals: &[String], arg: String) -> Self {
423 Self::invalid_value(cmd, "".to_owned(), good_vals, arg)
424 }
425
426 pub(crate) fn no_equals(cmd: &Command, arg: String, usage: Option<StyledStr>) -> Self {
427 let mut err = Self::new(ErrorKind::NoEquals).with_cmd(cmd);
428
429 #[cfg(feature = "error-context")]
430 {
431 err = err
432 .extend_context_unchecked([(ContextKind::InvalidArg, ContextValue::String(arg))]);
433 if let Some(usage) = usage {
434 err = err
435 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
436 }
437 }
438
439 err
440 }
441
442 pub(crate) fn invalid_value(
443 cmd: &Command,
444 bad_val: String,
445 good_vals: &[String],
446 arg: String,
447 ) -> Self {
448 let suggestion = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop();
449 let mut err = Self::new(ErrorKind::InvalidValue).with_cmd(cmd);
450
451 #[cfg(feature = "error-context")]
452 {
453 err = err.extend_context_unchecked([
454 (ContextKind::InvalidArg, ContextValue::String(arg)),
455 (ContextKind::InvalidValue, ContextValue::String(bad_val)),
456 (
457 ContextKind::ValidValue,
458 ContextValue::Strings(good_vals.iter().map(|s| (*s).to_owned()).collect()),
459 ),
460 ]);
461 if let Some(suggestion) = suggestion {
462 err = err.insert_context_unchecked(
463 ContextKind::SuggestedValue,
464 ContextValue::String(suggestion),
465 );
466 }
467 }
468
469 err
470 }
471
472 pub(crate) fn invalid_subcommand(
473 cmd: &Command,
474 subcmd: String,
475 did_you_mean: Vec<String>,
476 name: String,
477 suggested_trailing_arg: bool,
478 usage: Option<StyledStr>,
479 ) -> Self {
480 use std::fmt::Write as _;
481 let styles = cmd.get_styles();
482 let invalid = &styles.get_invalid();
483 let valid = &styles.get_valid();
484 let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd);
485
486 #[cfg(feature = "error-context")]
487 {
488 let mut suggestions = vec![];
489 if suggested_trailing_arg {
490 let mut styled_suggestion = StyledStr::new();
491 let _ = write!(
492 styled_suggestion,
493 "to pass '{}{subcmd}{}' as a value, use '{}{name} -- {subcmd}{}'",
494 invalid.render(),
495 invalid.render_reset(),
496 valid.render(),
497 valid.render_reset()
498 );
499 suggestions.push(styled_suggestion);
500 }
501
502 err = err.extend_context_unchecked([
503 (ContextKind::InvalidSubcommand, ContextValue::String(subcmd)),
504 (
505 ContextKind::SuggestedSubcommand,
506 ContextValue::Strings(did_you_mean),
507 ),
508 (
509 ContextKind::Suggested,
510 ContextValue::StyledStrs(suggestions),
511 ),
512 ]);
513 if let Some(usage) = usage {
514 err = err
515 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
516 }
517 }
518
519 err
520 }
521
522 pub(crate) fn unrecognized_subcommand(
523 cmd: &Command,
524 subcmd: String,
525 usage: Option<StyledStr>,
526 ) -> Self {
527 let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd);
528
529 #[cfg(feature = "error-context")]
530 {
531 err = err.extend_context_unchecked([(
532 ContextKind::InvalidSubcommand,
533 ContextValue::String(subcmd),
534 )]);
535 if let Some(usage) = usage {
536 err = err
537 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
538 }
539 }
540
541 err
542 }
543
544 pub(crate) fn missing_required_argument(
545 cmd: &Command,
546 required: Vec<String>,
547 usage: Option<StyledStr>,
548 ) -> Self {
549 let mut err = Self::new(ErrorKind::MissingRequiredArgument).with_cmd(cmd);
550
551 #[cfg(feature = "error-context")]
552 {
553 err = err.extend_context_unchecked([(
554 ContextKind::InvalidArg,
555 ContextValue::Strings(required),
556 )]);
557 if let Some(usage) = usage {
558 err = err
559 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
560 }
561 }
562
563 err
564 }
565
566 pub(crate) fn missing_subcommand(
567 cmd: &Command,
568 parent: String,
569 available: Vec<String>,
570 usage: Option<StyledStr>,
571 ) -> Self {
572 let mut err = Self::new(ErrorKind::MissingSubcommand).with_cmd(cmd);
573
574 #[cfg(feature = "error-context")]
575 {
576 err = err.extend_context_unchecked([
577 (ContextKind::InvalidSubcommand, ContextValue::String(parent)),
578 (
579 ContextKind::ValidSubcommand,
580 ContextValue::Strings(available),
581 ),
582 ]);
583 if let Some(usage) = usage {
584 err = err
585 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
586 }
587 }
588
589 err
590 }
591
592 pub(crate) fn invalid_utf8(cmd: &Command, usage: Option<StyledStr>) -> Self {
593 let mut err = Self::new(ErrorKind::InvalidUtf8).with_cmd(cmd);
594
595 #[cfg(feature = "error-context")]
596 {
597 if let Some(usage) = usage {
598 err = err
599 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
600 }
601 }
602
603 err
604 }
605
606 pub(crate) fn too_many_values(
607 cmd: &Command,
608 val: String,
609 arg: String,
610 usage: Option<StyledStr>,
611 ) -> Self {
612 let mut err = Self::new(ErrorKind::TooManyValues).with_cmd(cmd);
613
614 #[cfg(feature = "error-context")]
615 {
616 err = err.extend_context_unchecked([
617 (ContextKind::InvalidArg, ContextValue::String(arg)),
618 (ContextKind::InvalidValue, ContextValue::String(val)),
619 ]);
620 if let Some(usage) = usage {
621 err = err
622 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
623 }
624 }
625
626 err
627 }
628
629 pub(crate) fn too_few_values(
630 cmd: &Command,
631 arg: String,
632 min_vals: usize,
633 curr_vals: usize,
634 usage: Option<StyledStr>,
635 ) -> Self {
636 let mut err = Self::new(ErrorKind::TooFewValues).with_cmd(cmd);
637
638 #[cfg(feature = "error-context")]
639 {
640 err = err.extend_context_unchecked([
641 (ContextKind::InvalidArg, ContextValue::String(arg)),
642 (
643 ContextKind::MinValues,
644 ContextValue::Number(min_vals as isize),
645 ),
646 (
647 ContextKind::ActualNumValues,
648 ContextValue::Number(curr_vals as isize),
649 ),
650 ]);
651 if let Some(usage) = usage {
652 err = err
653 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
654 }
655 }
656
657 err
658 }
659
660 pub(crate) fn value_validation(
661 arg: String,
662 val: String,
663 err: Box<dyn error::Error + Send + Sync>,
664 ) -> Self {
665 let mut err = Self::new(ErrorKind::ValueValidation).set_source(err);
666
667 #[cfg(feature = "error-context")]
668 {
669 err = err.extend_context_unchecked([
670 (ContextKind::InvalidArg, ContextValue::String(arg)),
671 (ContextKind::InvalidValue, ContextValue::String(val)),
672 ]);
673 }
674
675 err
676 }
677
678 pub(crate) fn wrong_number_of_values(
679 cmd: &Command,
680 arg: String,
681 num_vals: usize,
682 curr_vals: usize,
683 usage: Option<StyledStr>,
684 ) -> Self {
685 let mut err = Self::new(ErrorKind::WrongNumberOfValues).with_cmd(cmd);
686
687 #[cfg(feature = "error-context")]
688 {
689 err = err.extend_context_unchecked([
690 (ContextKind::InvalidArg, ContextValue::String(arg)),
691 (
692 ContextKind::ExpectedNumValues,
693 ContextValue::Number(num_vals as isize),
694 ),
695 (
696 ContextKind::ActualNumValues,
697 ContextValue::Number(curr_vals as isize),
698 ),
699 ]);
700 if let Some(usage) = usage {
701 err = err
702 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
703 }
704 }
705
706 err
707 }
708
709 pub(crate) fn unknown_argument(
710 cmd: &Command,
711 arg: String,
712 did_you_mean: Option<(String, Option<String>)>,
713 suggested_trailing_arg: bool,
714 usage: Option<StyledStr>,
715 ) -> Self {
716 use std::fmt::Write as _;
717 let styles = cmd.get_styles();
718 let invalid = &styles.get_invalid();
719 let valid = &styles.get_valid();
720 let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd);
721
722 #[cfg(feature = "error-context")]
723 {
724 let mut suggestions = vec![];
725 if suggested_trailing_arg {
726 let mut styled_suggestion = StyledStr::new();
727 let _ = write!(
728 styled_suggestion,
729 "to pass '{}{arg}{}' as a value, use '{}-- {arg}{}'",
730 invalid.render(),
731 invalid.render_reset(),
732 valid.render(),
733 valid.render_reset()
734 );
735 suggestions.push(styled_suggestion);
736 }
737
738 err = err
739 .extend_context_unchecked([(ContextKind::InvalidArg, ContextValue::String(arg))]);
740 if let Some(usage) = usage {
741 err = err
742 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
743 }
744 match did_you_mean {
745 Some((flag, Some(sub))) => {
746 let mut styled_suggestion = StyledStr::new();
747 let _ = write!(
748 styled_suggestion,
749 "'{}{sub} {flag}{}' exists",
750 valid.render(),
751 valid.render_reset()
752 );
753 suggestions.push(styled_suggestion);
754 }
755 Some((flag, None)) => {
756 err = err.insert_context_unchecked(
757 ContextKind::SuggestedArg,
758 ContextValue::String(flag),
759 );
760 }
761 None => {}
762 }
763 if !suggestions.is_empty() {
764 err = err.insert_context_unchecked(
765 ContextKind::Suggested,
766 ContextValue::StyledStrs(suggestions),
767 );
768 }
769 }
770
771 err
772 }
773
774 pub(crate) fn unnecessary_double_dash(
775 cmd: &Command,
776 arg: String,
777 usage: Option<StyledStr>,
778 ) -> Self {
779 use std::fmt::Write as _;
780 let styles = cmd.get_styles();
781 let invalid = &styles.get_invalid();
782 let valid = &styles.get_valid();
783 let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd);
784
785 #[cfg(feature = "error-context")]
786 {
787 let mut styled_suggestion = StyledStr::new();
788 let _ = write!(
789 styled_suggestion,
790 "subcommand '{}{arg}{}' exists; to use it, remove the '{}--{}' before it",
791 valid.render(),
792 valid.render_reset(),
793 invalid.render(),
794 invalid.render_reset()
795 );
796
797 err = err.extend_context_unchecked([
798 (ContextKind::InvalidArg, ContextValue::String(arg)),
799 (
800 ContextKind::Suggested,
801 ContextValue::StyledStrs(vec![styled_suggestion]),
802 ),
803 ]);
804 if let Some(usage) = usage {
805 err = err
806 .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
807 }
808 }
809
810 err
811 }
812
813 fn formatted(&self) -> Cow<'_, StyledStr> {
814 if let Some(message) = self.inner.message.as_ref() {
815 message.formatted(&self.inner.styles)
816 } else {
817 let styled = F::format_error(self);
818 Cow::Owned(styled)
819 }
820 }
821}
822
823impl<F: ErrorFormatter> From<io::Error> for Error<F> {
824 fn from(e: io::Error) -> Self {
825 Error::raw(kind:ErrorKind::Io, message:e)
826 }
827}
828
829impl<F: ErrorFormatter> From<fmt::Error> for Error<F> {
830 fn from(e: fmt::Error) -> Self {
831 Error::raw(kind:ErrorKind::Format, message:e)
832 }
833}
834
835impl<F: ErrorFormatter> std::fmt::Debug for Error<F> {
836 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
837 self.inner.fmt(f)
838 }
839}
840
841impl<F: ErrorFormatter> error::Error for Error<F> {
842 #[allow(trivial_casts)]
843 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
844 self.inner.source.as_ref().map(|e: &Box| e.as_ref() as _)
845 }
846}
847
848impl<F: ErrorFormatter> Display for Error<F> {
849 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
850 // Assuming `self.message` already has a trailing newline, from `try_help` or similar
851 ok!(write!(f, "{}", self.formatted()));
852 if let Some(backtrace: &Backtrace) = self.inner.backtrace.as_ref() {
853 ok!(writeln!(f));
854 ok!(writeln!(f, "Backtrace:"));
855 ok!(writeln!(f, "{backtrace}"));
856 }
857 Ok(())
858 }
859}
860
861#[derive(Clone, Debug)]
862pub(crate) enum Message {
863 Raw(String),
864 Formatted(StyledStr),
865}
866
867impl Message {
868 fn format(&mut self, cmd: &Command, usage: Option<StyledStr>) {
869 match self {
870 Message::Raw(s) => {
871 let mut message = String::new();
872 std::mem::swap(s, &mut message);
873
874 let styled = format::format_error_message(
875 &message,
876 cmd.get_styles(),
877 Some(cmd),
878 usage.as_ref(),
879 );
880
881 *self = Self::Formatted(styled);
882 }
883 Message::Formatted(_) => {}
884 }
885 }
886
887 fn formatted(&self, styles: &Styles) -> Cow<StyledStr> {
888 match self {
889 Message::Raw(s) => {
890 let styled = format::format_error_message(s, styles, None, None);
891
892 Cow::Owned(styled)
893 }
894 Message::Formatted(s) => Cow::Borrowed(s),
895 }
896 }
897}
898
899impl From<String> for Message {
900 fn from(inner: String) -> Self {
901 Self::Raw(inner)
902 }
903}
904
905impl From<StyledStr> for Message {
906 fn from(inner: StyledStr) -> Self {
907 Self::Formatted(inner)
908 }
909}
910
911#[cfg(feature = "debug")]
912#[derive(Debug)]
913struct Backtrace(backtrace::Backtrace);
914
915#[cfg(feature = "debug")]
916impl Backtrace {
917 fn new() -> Option<Self> {
918 Some(Self(backtrace::Backtrace::new()))
919 }
920}
921
922#[cfg(feature = "debug")]
923impl Display for Backtrace {
924 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
925 // `backtrace::Backtrace` uses `Debug` instead of `Display`
926 write!(f, "{:?}", self.0)
927 }
928}
929
930#[cfg(not(feature = "debug"))]
931#[derive(Debug)]
932struct Backtrace;
933
934#[cfg(not(feature = "debug"))]
935impl Backtrace {
936 fn new() -> Option<Self> {
937 None
938 }
939}
940
941#[cfg(not(feature = "debug"))]
942impl Display for Backtrace {
943 fn fmt(&self, _: &mut Formatter) -> fmt::Result {
944 Ok(())
945 }
946}
947
948#[test]
949fn check_auto_traits() {
950 static_assertions::assert_impl_all!(Error: Send, Sync, Unpin);
951}
952