Skip to main content

parkour/
error.rs

1use std::fmt;
2use std::num::{ParseFloatError, ParseIntError};
3
4use crate::util::Flag;
5
6/// The error type when parsing command-line arguments. You can create an
7/// `Error` by creating an `ErrorInner` and converting it with `.into()`.
8///
9/// This error type supports an error source for attaching context to the error.
10#[derive(Debug)]
11pub struct Error {
12    inner: ErrorInner,
13    source: Option<Box<dyn std::error::Error + Sync + Send + 'static>>,
14}
15
16impl Error {
17    /// Attach context to the error.
18    ///
19    /// ### Usage
20    ///
21    /// ```
22    /// use parkour::{Error, util::Flag};
23    ///
24    /// Error::missing_value()
25    ///     .with_source(Error::in_subcommand("test"))
26    /// # ;
27    /// ```
28    ///
29    /// This could produce the following output:
30    /// ```text
31    /// missing value
32    ///     source: in subcommand `test`
33    /// ```
34    pub fn with_source(
35        self,
36        source: impl std::error::Error + Sync + Send + 'static,
37    ) -> Self {
38        Error { source: Some(Box::new(source)), ..self }
39    }
40
41    /// Create a `NoValue` error
42    pub fn no_value() -> Self {
43        ErrorInner::NoValue.into()
44    }
45
46    /// Returns `true` if this is a `NoValue` error
47    pub fn is_no_value(&self) -> bool {
48        self.inner == ErrorInner::NoValue
49    }
50
51    /// Create a `MissingValue` error
52    pub fn missing_value() -> Self {
53        ErrorInner::MissingValue.into()
54    }
55
56    /// Returns `true` if this is a `MissingValue` error
57    pub fn is_missing_value(&self) -> bool {
58        self.inner == ErrorInner::MissingValue
59    }
60
61    /// Create a `EarlyExit` error
62    pub fn early_exit() -> Self {
63        ErrorInner::EarlyExit.into()
64    }
65
66    /// Returns `true` if this is a `EarlyExit` error
67    pub fn is_early_exit(&self) -> bool {
68        self.inner == ErrorInner::EarlyExit
69    }
70
71    /// Create a `UnexpectedValue` error
72    pub fn unexpected_value(got: impl ToString, expected: impl ToString) -> Self {
73        ErrorInner::UnexpectedValue {
74            got: got.to_string(),
75            expected: expected.to_string(),
76        }
77        .into()
78    }
79
80    /// Create a `MissingArgument` error
81    pub fn missing_argument(arg: impl ToString) -> Self {
82        ErrorInner::MissingArgument { arg: arg.to_string() }.into()
83    }
84
85    /// Create a `InArgument` error
86    pub fn in_argument(flag: &Flag) -> Self {
87        ErrorInner::InArgument(flag.first_to_string()).into()
88    }
89
90    /// Create a `InSubcommand` error
91    pub fn in_subcommand(cmd: impl ToString) -> Self {
92        ErrorInner::InSubcommand(cmd.to_string()).into()
93    }
94}
95
96impl From<ErrorInner> for Error {
97    fn from(inner: ErrorInner) -> Self {
98        Error { inner, source: None }
99    }
100}
101
102/// The error type when parsing command-line arguments
103#[derive(Debug, PartialEq)]
104pub enum ErrorInner {
105    /// The argument you tried to parse wasn't present at the current position.
106    /// Has a similar purpose as `Option::None`
107    NoValue,
108
109    /// The argument you tried to parse wasn't present at the current position,
110    /// but was required
111    MissingValue,
112
113    /// The argument you tried to parse was only partly present
114    IncompleteValue(usize),
115
116    /// Used when an argument should abort argument parsing, like --help
117    EarlyExit,
118
119    /// Indicates that the error originated in the specified argument. This
120    /// should be used as the source for another error
121    InArgument(String),
122
123    /// Indicates that the error originated in the specified subcommand. This
124    /// should be used as the source for another error
125    InSubcommand(String),
126
127    /// The parsed value doesn't meet our expectations
128    UnexpectedValue {
129        /// The value we tried to parse
130        got: String,
131        /// The expectation that was violated. For example, this string can
132        /// contain a list of accepted values.
133        expected: String,
134    },
135
136    /// The parsed list contains more items than allowed
137    TooManyValues {
138        /// The maximum number of items
139        max: usize,
140        /// The number of items that was parsed
141        count: usize,
142    },
143
144    /// The parsed array has the wrong length
145    WrongNumberOfValues {
146        /// The length of the array
147        expected: usize,
148        /// The number of items that was parsed
149        got: usize,
150    },
151
152    /// A required argument was not provided
153    MissingArgument {
154        /// The name of the argument that is missing
155        arg: String,
156    },
157
158    /// An unknown argument was provided
159    UnexpectedArgument {
160        /// The (full) argument that wasn't expected
161        arg: String,
162    },
163
164    /// An argument was provided more often than allowed
165    TooManyArgOccurrences {
166        /// The name of the argument that was provided too many times
167        arg: String,
168        /// The maximum number of times the argument may be provided
169        max: Option<u32>,
170    },
171
172    /// Parsing an integer failed
173    ParseIntError(ParseIntError),
174
175    /// Parsing a floating-point number failed
176    ParseFloatError(ParseFloatError),
177}
178
179impl From<ParseIntError> for Error {
180    fn from(e: ParseIntError) -> Self {
181        ErrorInner::ParseIntError(e).into()
182    }
183}
184impl From<ParseFloatError> for Error {
185    fn from(e: ParseFloatError) -> Self {
186        ErrorInner::ParseFloatError(e).into()
187    }
188}
189
190impl std::error::Error for Error {
191    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
192        match &self.source {
193            Some(source) => Some(&**source as &(dyn std::error::Error + 'static)),
194            None => None,
195        }
196    }
197}
198
199impl fmt::Display for Error {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        match &self.inner {
202            ErrorInner::NoValue => write!(f, "no value"),
203            ErrorInner::MissingValue => write!(f, "missing value"),
204            ErrorInner::IncompleteValue(part) => {
205                write!(f, "missing part {} of value", part)
206            }
207            ErrorInner::EarlyExit => write!(f, "early exit"),
208            ErrorInner::InArgument(opt) => write!(f, "in `{}`", opt.escape_debug()),
209            ErrorInner::InSubcommand(cmd) => {
210                write!(f, "in subcommand {}", cmd.escape_debug())
211            }
212            ErrorInner::UnexpectedValue { expected, got } => {
213                write!(
214                    f,
215                    "unexpected value `{}`, expected {}",
216                    got.escape_debug(),
217                    expected.escape_debug()
218                )
219            }
220            ErrorInner::UnexpectedArgument { arg } => {
221                write!(f, "unexpected argument `{}`", arg.escape_debug())
222            }
223            ErrorInner::TooManyValues { max, count } => {
224                write!(f, "too many values, expected at most {}, got {}", max, count)
225            }
226            ErrorInner::WrongNumberOfValues { expected, got } => {
227                write!(f, "wrong number of values, expected {}, got {}", expected, got)
228            }
229            ErrorInner::MissingArgument { arg } => {
230                write!(f, "required {} was not provided", arg)
231            }
232            ErrorInner::TooManyArgOccurrences { arg, max } => {
233                if let Some(max) = max {
234                    write!(
235                        f,
236                        "{} was used too often, it can be used at most {} times",
237                        arg, max
238                    )
239                } else {
240                    write!(f, "{} was used too often", arg)
241                }
242            }
243
244            ErrorInner::ParseIntError(e) => write!(f, "{}", e),
245            ErrorInner::ParseFloatError(e) => write!(f, "{}", e),
246        }
247    }
248}