1use std::error::Error;
11use std::fmt;
12use std::fmt::Display;
13use std::fmt::Formatter;
14
15use crate::argument::ArgumentBound;
16use crate::argument::ArgumentErrorKind;
17use crate::argument::ArgumentPath;
18use crate::argument::ComparisonConstraint;
19use crate::argument::IndexRole;
20use crate::argument::LengthConstraint;
21use crate::argument::LengthMetric;
22use crate::argument::PatternExpectation;
23use crate::argument::RangeConstraint;
24
25#[must_use]
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct ArgumentError {
50 path: ArgumentPath,
52 kind: Box<ArgumentErrorKind>,
54}
55
56impl ArgumentError {
57 #[inline]
71 pub fn new(path: &str, kind: ArgumentErrorKind) -> Self {
72 Self {
73 path: ArgumentPath::new(path),
74 kind: Box::new(kind),
75 }
76 }
77
78 #[must_use = "the caller should inspect the argument path"]
84 #[inline]
85 pub fn path(&self) -> &ArgumentPath {
86 &self.path
87 }
88
89 #[must_use = "the caller should inspect the structured error kind"]
95 #[inline]
96 pub fn kind(&self) -> &ArgumentErrorKind {
97 self.kind.as_ref()
98 }
99
100 #[inline]
110 pub fn with_path_prefix(mut self, prefix: &str) -> Self {
111 self.path = self.path.with_prefix(prefix);
112 self
113 }
114
115 #[inline]
124 pub fn into_parts(self) -> (ArgumentPath, ArgumentErrorKind) {
125 let Self { path, kind } = self;
126 (path, *kind)
127 }
128}
129
130impl Display for ArgumentError {
131 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
133 let path = escape_for_display(self.path.as_ref(), Some('\''));
134 write!(formatter, "argument '{path}'")?;
135 match self.kind.as_ref() {
136 ArgumentErrorKind::Missing => formatter.write_str(" is missing"),
137 ArgumentErrorKind::Blank => formatter.write_str(" must not be blank"),
138 ArgumentErrorKind::Empty => formatter.write_str(" must not be empty"),
139 ArgumentErrorKind::Length {
140 actual,
141 constraint,
142 metric,
143 } => {
144 let label = length_metric_label(metric);
145 write!(formatter, " has {label} {actual}, expected ")?;
146 write_length_constraint(formatter, constraint)
147 }
148 ArgumentErrorKind::Comparison { actual, constraint } => {
149 write!(formatter, " has value {actual}, expected ")?;
150 write_comparison_constraint(formatter, constraint)
151 }
152 ArgumentErrorKind::Range { actual, constraint } => {
153 write!(formatter, " has value {actual}, expected range ")?;
154 write_range_constraint(formatter, constraint)
155 }
156 ArgumentErrorKind::InvalidLengthConstraint { constraint, metric } => {
157 let label = length_metric_label(metric);
158 write!(formatter, " has invalid {label} constraint ")?;
159 write_length_constraint(formatter, constraint)
160 }
161 ArgumentErrorKind::InvalidRangeConstraint { constraint } => {
162 formatter.write_str(" has invalid range constraint ")?;
163 write_range_constraint(formatter, constraint)
164 }
165 ArgumentErrorKind::NotANumber => formatter.write_str(" contains a NaN value"),
166 ArgumentErrorKind::NotFinite { actual } => {
167 write!(formatter, " has non-finite value {actual}")
168 }
169 ArgumentErrorKind::Index { index, size, role } => match role {
170 IndexRole::Element => write!(
171 formatter,
172 " has element index {index} outside the valid range 0..{size}",
173 ),
174 IndexRole::Position => write!(
175 formatter,
176 " has position index {index} outside the valid range 0..={size}",
177 ),
178 },
179 ArgumentErrorKind::IndexRange { start, end, size } => write!(
180 formatter,
181 " has position range {start}..{end} outside the valid range 0..={size}",
182 ),
183 ArgumentErrorKind::Bounds {
184 offset,
185 length,
186 total_length,
187 } => write!(
188 formatter,
189 " has offset {offset} and length {length} outside total length {total_length}",
190 ),
191 ArgumentErrorKind::Pattern { pattern, expectation } => match expectation {
192 PatternExpectation::Match => {
193 let pattern = escape_for_display(pattern, Some('\''));
194 write!(formatter, " must match pattern '{pattern}'")
195 }
196 PatternExpectation::NoMatch => {
197 let pattern = escape_for_display(pattern, Some('\''));
198 write!(formatter, " must not match pattern '{pattern}'")
199 }
200 },
201 ArgumentErrorKind::Custom { code, message } => {
202 let code = escape_for_display(code, Some(']'));
203 let message = escape_for_display(message, None);
204 write!(formatter, " failed validation [{code}]: {message}")
205 }
206 }
207 }
208}
209
210impl Error for ArgumentError {}
211
212fn escape_for_display(value: &str, delimiter: Option<char>) -> String {
218 let mut escaped = String::with_capacity(value.len());
219 for character in value.chars() {
220 match character {
221 '\\' => escaped.push_str("\\\\"),
222 '\r' => escaped.push_str("\\r"),
223 '\n' => escaped.push_str("\\n"),
224 '\t' => escaped.push_str("\\t"),
225 character if Some(character) == delimiter => {
226 escaped.push('\\');
227 escaped.push(character);
228 }
229 character if character.is_control() => {
230 escaped.extend(character.escape_unicode());
231 }
232 character => escaped.push(character),
233 }
234 }
235 escaped
236}
237
238#[inline]
243fn length_metric_label(metric: &LengthMetric) -> &'static str {
244 match metric {
245 LengthMetric::Bytes => "byte length",
246 LengthMetric::UnicodeScalars => "Unicode scalar count",
247 LengthMetric::Elements => "element count",
248 }
249}
250
251fn write_length_constraint(formatter: &mut Formatter<'_>, constraint: &LengthConstraint) -> fmt::Result {
256 match constraint {
257 LengthConstraint::Exact(expected) => {
258 write!(formatter, "exactly {expected}")
259 }
260 LengthConstraint::AtLeast(min) => write!(formatter, "at least {min}"),
261 LengthConstraint::AtMost(max) => write!(formatter, "at most {max}"),
262 LengthConstraint::InRange { min, max } => {
263 write!(formatter, "between {min} and {max}")
264 }
265 }
266}
267
268fn write_comparison_constraint(formatter: &mut Formatter<'_>, constraint: &ComparisonConstraint) -> fmt::Result {
273 match constraint {
274 ComparisonConstraint::EqualTo(expected) => {
275 write!(formatter, "equal to {expected}")
276 }
277 ComparisonConstraint::NotEqualTo(expected) => {
278 write!(formatter, "not equal to {expected}")
279 }
280 ComparisonConstraint::LessThan(bound) => {
281 write!(formatter, "less than {bound}")
282 }
283 ComparisonConstraint::AtMost(bound) => {
284 write!(formatter, "at most {bound}")
285 }
286 ComparisonConstraint::GreaterThan(bound) => {
287 write!(formatter, "greater than {bound}")
288 }
289 ComparisonConstraint::AtLeast(bound) => {
290 write!(formatter, "at least {bound}")
291 }
292 }
293}
294
295fn write_range_constraint(formatter: &mut Formatter<'_>, constraint: &RangeConstraint) -> fmt::Result {
300 match constraint.lower() {
301 ArgumentBound::Unbounded => formatter.write_str("(-infinity"),
302 ArgumentBound::Included(value) => write!(formatter, "[{value}"),
303 ArgumentBound::Excluded(value) => write!(formatter, "({value}"),
304 }?;
305 formatter.write_str(", ")?;
306 match constraint.upper() {
307 ArgumentBound::Unbounded => formatter.write_str("infinity)"),
308 ArgumentBound::Included(value) => write!(formatter, "{value}]"),
309 ArgumentBound::Excluded(value) => write!(formatter, "{value})"),
310 }
311}
312
313pub type ArgumentResult<T> = Result<T, ArgumentError>;