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#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct ArgumentError {
36 path: ArgumentPath,
37 kind: Box<ArgumentErrorKind>,
38}
39
40impl ArgumentError {
41 #[inline]
46 pub fn new(path: &str, kind: ArgumentErrorKind) -> Self {
47 Self {
48 path: ArgumentPath::new(path),
49 kind: Box::new(kind),
50 }
51 }
52
53 #[inline]
55 pub fn path(&self) -> &ArgumentPath {
56 &self.path
57 }
58
59 #[inline]
61 pub fn kind(&self) -> &ArgumentErrorKind {
62 self.kind.as_ref()
63 }
64
65 #[inline]
75 pub fn with_path_prefix(mut self, prefix: &str) -> Self {
76 self.path = self.path.with_prefix(prefix);
77 self
78 }
79
80 #[inline]
85 pub fn into_parts(self) -> (ArgumentPath, ArgumentErrorKind) {
86 let Self { path, kind } = self;
87 (path, *kind)
88 }
89}
90
91impl Display for ArgumentError {
92 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
94 let path = escape_for_display(self.path.as_ref(), Some('\''));
95 write!(formatter, "argument '{path}'")?;
96 match self.kind.as_ref() {
97 ArgumentErrorKind::Missing => formatter.write_str(" is missing"),
98 ArgumentErrorKind::Blank => {
99 formatter.write_str(" must not be blank")
100 }
101 ArgumentErrorKind::Empty => {
102 formatter.write_str(" must not be empty")
103 }
104 ArgumentErrorKind::Length {
105 actual,
106 constraint,
107 metric,
108 } => {
109 let label = length_metric_label(metric);
110 write!(formatter, " has {label} {actual}, expected ")?;
111 write_length_constraint(formatter, constraint)
112 }
113 ArgumentErrorKind::Comparison { actual, constraint } => {
114 write!(formatter, " has value {actual}, expected ")?;
115 write_comparison_constraint(formatter, constraint)
116 }
117 ArgumentErrorKind::Range { actual, constraint } => {
118 write!(formatter, " has value {actual}, expected range ")?;
119 write_range_constraint(formatter, constraint)
120 }
121 ArgumentErrorKind::InvalidLengthConstraint {
122 constraint,
123 metric,
124 } => {
125 let label = length_metric_label(metric);
126 write!(formatter, " has invalid {label} constraint ")?;
127 write_length_constraint(formatter, constraint)
128 }
129 ArgumentErrorKind::InvalidRangeConstraint { constraint } => {
130 formatter.write_str(" has invalid range constraint ")?;
131 write_range_constraint(formatter, constraint)
132 }
133 ArgumentErrorKind::NotANumber => {
134 formatter.write_str(" contains a NaN value")
135 }
136 ArgumentErrorKind::NotFinite { actual } => {
137 write!(formatter, " has non-finite value {actual}")
138 }
139 ArgumentErrorKind::Index { index, size, role } => match role {
140 IndexRole::Element => write!(
141 formatter,
142 " has element index {index} outside the valid range 0..{size}",
143 ),
144 IndexRole::Position => write!(
145 formatter,
146 " has position index {index} outside the valid range 0..={size}",
147 ),
148 },
149 ArgumentErrorKind::IndexRange { start, end, size } => write!(
150 formatter,
151 " has position range {start}..{end} outside the valid range 0..={size}",
152 ),
153 ArgumentErrorKind::Bounds {
154 offset,
155 length,
156 total_length,
157 } => write!(
158 formatter,
159 " has offset {offset} and length {length} outside total length {total_length}",
160 ),
161 ArgumentErrorKind::Pattern {
162 pattern,
163 expectation,
164 } => match expectation {
165 PatternExpectation::Match => {
166 let pattern = escape_for_display(pattern, Some('\''));
167 write!(formatter, " must match pattern '{pattern}'")
168 }
169 PatternExpectation::NoMatch => {
170 let pattern = escape_for_display(pattern, Some('\''));
171 write!(formatter, " must not match pattern '{pattern}'")
172 }
173 },
174 ArgumentErrorKind::Custom { code, message } => {
175 let code = escape_for_display(code, Some(']'));
176 let message = escape_for_display(message, None);
177 write!(formatter, " failed validation [{code}]: {message}")
178 }
179 }
180 }
181}
182
183impl std::error::Error for ArgumentError {}
184
185fn escape_for_display(value: &str, delimiter: Option<char>) -> String {
191 let mut escaped = String::with_capacity(value.len());
192 for character in value.chars() {
193 match character {
194 '\\' => escaped.push_str("\\\\"),
195 '\r' => escaped.push_str("\\r"),
196 '\n' => escaped.push_str("\\n"),
197 '\t' => escaped.push_str("\\t"),
198 character if Some(character) == delimiter => {
199 escaped.push('\\');
200 escaped.push(character);
201 }
202 character if character.is_control() => {
203 escaped.extend(character.escape_unicode());
204 }
205 character => escaped.push(character),
206 }
207 }
208 escaped
209}
210
211fn length_metric_label(metric: &LengthMetric) -> &'static str {
216 match metric {
217 LengthMetric::Bytes => "byte length",
218 LengthMetric::UnicodeScalars => "Unicode scalar count",
219 LengthMetric::Elements => "element count",
220 }
221}
222
223fn write_length_constraint(
228 formatter: &mut Formatter<'_>,
229 constraint: &LengthConstraint,
230) -> fmt::Result {
231 match constraint {
232 LengthConstraint::Exact(expected) => {
233 write!(formatter, "exactly {expected}")
234 }
235 LengthConstraint::AtLeast(min) => write!(formatter, "at least {min}"),
236 LengthConstraint::AtMost(max) => write!(formatter, "at most {max}"),
237 LengthConstraint::InRange { min, max } => {
238 write!(formatter, "between {min} and {max}")
239 }
240 }
241}
242
243fn write_comparison_constraint(
248 formatter: &mut Formatter<'_>,
249 constraint: &ComparisonConstraint,
250) -> fmt::Result {
251 match constraint {
252 ComparisonConstraint::EqualTo(expected) => {
253 write!(formatter, "equal to {expected}")
254 }
255 ComparisonConstraint::NotEqualTo(expected) => {
256 write!(formatter, "not equal to {expected}")
257 }
258 ComparisonConstraint::LessThan(bound) => {
259 write!(formatter, "less than {bound}")
260 }
261 ComparisonConstraint::AtMost(bound) => {
262 write!(formatter, "at most {bound}")
263 }
264 ComparisonConstraint::GreaterThan(bound) => {
265 write!(formatter, "greater than {bound}")
266 }
267 ComparisonConstraint::AtLeast(bound) => {
268 write!(formatter, "at least {bound}")
269 }
270 }
271}
272
273fn write_range_constraint(
278 formatter: &mut Formatter<'_>,
279 constraint: &RangeConstraint,
280) -> fmt::Result {
281 match constraint.lower() {
282 ArgumentBound::Unbounded => formatter.write_str("(-infinity"),
283 ArgumentBound::Included(value) => write!(formatter, "[{value}"),
284 ArgumentBound::Excluded(value) => write!(formatter, "({value}"),
285 }?;
286 formatter.write_str(", ")?;
287 match constraint.upper() {
288 ArgumentBound::Unbounded => formatter.write_str("infinity)"),
289 ArgumentBound::Included(value) => write!(formatter, "{value}]"),
290 ArgumentBound::Excluded(value) => write!(formatter, "{value})"),
291 }
292}
293
294pub type ArgumentResult<T> = Result<T, ArgumentError>;