1use std::fmt::{
11 self,
12 Display,
13 Formatter,
14};
15
16use crate::argument::{
17 ArgumentBound,
18 ArgumentErrorKind,
19 ArgumentPath,
20 ComparisonConstraint,
21 IndexRole,
22 LengthConstraint,
23 LengthMetric,
24 PatternExpectation,
25 RangeConstraint,
26};
27
28#[must_use]
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct ArgumentError {
44 path: ArgumentPath,
45 kind: Box<ArgumentErrorKind>,
46}
47
48impl ArgumentError {
49 #[inline]
54 pub fn new(path: &str, kind: ArgumentErrorKind) -> Self {
55 Self {
56 path: ArgumentPath::new(path),
57 kind: Box::new(kind),
58 }
59 }
60
61 #[inline(always)]
63 pub fn path(&self) -> &ArgumentPath {
64 &self.path
65 }
66
67 #[inline(always)]
69 pub fn kind(&self) -> &ArgumentErrorKind {
70 self.kind.as_ref()
71 }
72
73 #[inline]
83 pub fn with_path_prefix(mut self, prefix: &str) -> Self {
84 self.path = self.path.with_prefix(prefix);
85 self
86 }
87
88 #[inline]
93 pub fn into_parts(self) -> (ArgumentPath, ArgumentErrorKind) {
94 let Self { path, kind } = self;
95 (path, *kind)
96 }
97}
98
99impl Display for ArgumentError {
100 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
102 let path = escape_for_display(self.path.as_ref(), Some('\''));
103 write!(formatter, "argument '{path}'")?;
104 match self.kind.as_ref() {
105 ArgumentErrorKind::Missing => formatter.write_str(" is missing"),
106 ArgumentErrorKind::Blank => {
107 formatter.write_str(" must not be blank")
108 }
109 ArgumentErrorKind::Empty => {
110 formatter.write_str(" must not be empty")
111 }
112 ArgumentErrorKind::Length {
113 actual,
114 constraint,
115 metric,
116 } => {
117 let label = length_metric_label(metric);
118 write!(formatter, " has {label} {actual}, expected ")?;
119 write_length_constraint(formatter, constraint)
120 }
121 ArgumentErrorKind::Comparison { actual, constraint } => {
122 write!(formatter, " has value {actual}, expected ")?;
123 write_comparison_constraint(formatter, constraint)
124 }
125 ArgumentErrorKind::Range { actual, constraint } => {
126 write!(formatter, " has value {actual}, expected range ")?;
127 write_range_constraint(formatter, constraint)
128 }
129 ArgumentErrorKind::InvalidLengthConstraint {
130 constraint,
131 metric,
132 } => {
133 let label = length_metric_label(metric);
134 write!(formatter, " has invalid {label} constraint ")?;
135 write_length_constraint(formatter, constraint)
136 }
137 ArgumentErrorKind::InvalidRangeConstraint { constraint } => {
138 formatter.write_str(" has invalid range constraint ")?;
139 write_range_constraint(formatter, constraint)
140 }
141 ArgumentErrorKind::NotANumber => {
142 formatter.write_str(" contains a NaN value")
143 }
144 ArgumentErrorKind::NotFinite { actual } => {
145 write!(formatter, " has non-finite value {actual}")
146 }
147 ArgumentErrorKind::Index { index, size, role } => match role {
148 IndexRole::Element => write!(
149 formatter,
150 " has element index {index} outside the valid range 0..{size}",
151 ),
152 IndexRole::Position => write!(
153 formatter,
154 " has position index {index} outside the valid range 0..={size}",
155 ),
156 },
157 ArgumentErrorKind::IndexRange { start, end, size } => write!(
158 formatter,
159 " has position range {start}..{end} outside the valid range 0..={size}",
160 ),
161 ArgumentErrorKind::Bounds {
162 offset,
163 length,
164 total_length,
165 } => write!(
166 formatter,
167 " has offset {offset} and length {length} outside total length {total_length}",
168 ),
169 ArgumentErrorKind::Pattern {
170 pattern,
171 expectation,
172 } => match expectation {
173 PatternExpectation::Match => {
174 let pattern = escape_for_display(pattern, Some('\''));
175 write!(formatter, " must match pattern '{pattern}'")
176 }
177 PatternExpectation::NoMatch => {
178 let pattern = escape_for_display(pattern, Some('\''));
179 write!(formatter, " must not match pattern '{pattern}'")
180 }
181 },
182 ArgumentErrorKind::Custom { code, message } => {
183 let code = escape_for_display(code, Some(']'));
184 let message = escape_for_display(message, None);
185 write!(formatter, " failed validation [{code}]: {message}")
186 }
187 }
188 }
189}
190
191impl std::error::Error for ArgumentError {}
192
193fn escape_for_display(value: &str, delimiter: Option<char>) -> String {
199 let mut escaped = String::with_capacity(value.len());
200 for character in value.chars() {
201 match character {
202 '\\' => escaped.push_str("\\\\"),
203 '\r' => escaped.push_str("\\r"),
204 '\n' => escaped.push_str("\\n"),
205 '\t' => escaped.push_str("\\t"),
206 character if Some(character) == delimiter => {
207 escaped.push('\\');
208 escaped.push(character);
209 }
210 character if character.is_control() => {
211 escaped.extend(character.escape_unicode());
212 }
213 character => escaped.push(character),
214 }
215 }
216 escaped
217}
218
219#[inline]
224fn length_metric_label(metric: &LengthMetric) -> &'static str {
225 match metric {
226 LengthMetric::Bytes => "byte length",
227 LengthMetric::UnicodeScalars => "Unicode scalar count",
228 LengthMetric::Elements => "element count",
229 }
230}
231
232fn write_length_constraint(
237 formatter: &mut Formatter<'_>,
238 constraint: &LengthConstraint,
239) -> fmt::Result {
240 match constraint {
241 LengthConstraint::Exact(expected) => {
242 write!(formatter, "exactly {expected}")
243 }
244 LengthConstraint::AtLeast(min) => write!(formatter, "at least {min}"),
245 LengthConstraint::AtMost(max) => write!(formatter, "at most {max}"),
246 LengthConstraint::InRange { min, max } => {
247 write!(formatter, "between {min} and {max}")
248 }
249 }
250}
251
252fn write_comparison_constraint(
257 formatter: &mut Formatter<'_>,
258 constraint: &ComparisonConstraint,
259) -> fmt::Result {
260 match constraint {
261 ComparisonConstraint::EqualTo(expected) => {
262 write!(formatter, "equal to {expected}")
263 }
264 ComparisonConstraint::NotEqualTo(expected) => {
265 write!(formatter, "not equal to {expected}")
266 }
267 ComparisonConstraint::LessThan(bound) => {
268 write!(formatter, "less than {bound}")
269 }
270 ComparisonConstraint::AtMost(bound) => {
271 write!(formatter, "at most {bound}")
272 }
273 ComparisonConstraint::GreaterThan(bound) => {
274 write!(formatter, "greater than {bound}")
275 }
276 ComparisonConstraint::AtLeast(bound) => {
277 write!(formatter, "at least {bound}")
278 }
279 }
280}
281
282fn write_range_constraint(
287 formatter: &mut Formatter<'_>,
288 constraint: &RangeConstraint,
289) -> fmt::Result {
290 match constraint.lower() {
291 ArgumentBound::Unbounded => formatter.write_str("(-infinity"),
292 ArgumentBound::Included(value) => write!(formatter, "[{value}"),
293 ArgumentBound::Excluded(value) => write!(formatter, "({value}"),
294 }?;
295 formatter.write_str(", ")?;
296 match constraint.upper() {
297 ArgumentBound::Unbounded => formatter.write_str("infinity)"),
298 ArgumentBound::Included(value) => write!(formatter, "{value}]"),
299 ArgumentBound::Excluded(value) => write!(formatter, "{value})"),
300 }
301}
302
303pub type ArgumentResult<T> = Result<T, ArgumentError>;