1 | use std::error::Error as StdError; |
2 | use std::fmt::{Display, Formatter, Result}; |
3 | |
4 | use crate::parser::prelude::*; |
5 | use crate::Key; |
6 | |
7 | use winnow::BStr; |
8 | |
9 | /// Type representing a TOML parse error |
10 | #[derive (Debug, Clone, Eq, PartialEq, Hash)] |
11 | pub struct TomlError { |
12 | message: String, |
13 | original: Option<String>, |
14 | keys: Vec<String>, |
15 | span: Option<std::ops::Range<usize>>, |
16 | } |
17 | |
18 | impl TomlError { |
19 | pub(crate) fn new(error: ParserError<'_>, original: Input<'_>) -> Self { |
20 | use winnow::stream::Offset; |
21 | use winnow::stream::Stream; |
22 | |
23 | let offset = original.offset_to(&error.input); |
24 | let span = if offset == original.len() { |
25 | offset..offset |
26 | } else { |
27 | offset..(offset + 1) |
28 | }; |
29 | |
30 | let message = error.to_string(); |
31 | let original = original.next_slice(original.eof_offset()).1; |
32 | |
33 | Self { |
34 | message, |
35 | original: Some( |
36 | String::from_utf8(original.to_owned()).expect("original document was utf8" ), |
37 | ), |
38 | keys: Vec::new(), |
39 | span: Some(span), |
40 | } |
41 | } |
42 | |
43 | #[cfg (feature = "serde" )] |
44 | pub(crate) fn custom(message: String, span: Option<std::ops::Range<usize>>) -> Self { |
45 | Self { |
46 | message, |
47 | original: None, |
48 | keys: Vec::new(), |
49 | span, |
50 | } |
51 | } |
52 | |
53 | #[cfg (feature = "serde" )] |
54 | pub(crate) fn add_key(&mut self, key: String) { |
55 | self.keys.insert(0, key); |
56 | } |
57 | |
58 | /// What went wrong |
59 | pub fn message(&self) -> &str { |
60 | &self.message |
61 | } |
62 | |
63 | /// The start/end index into the original document where the error occurred |
64 | pub fn span(&self) -> Option<std::ops::Range<usize>> { |
65 | self.span.clone() |
66 | } |
67 | |
68 | #[cfg (feature = "serde" )] |
69 | pub(crate) fn set_span(&mut self, span: Option<std::ops::Range<usize>>) { |
70 | self.span = span; |
71 | } |
72 | |
73 | #[cfg (feature = "serde" )] |
74 | pub(crate) fn set_original(&mut self, original: Option<String>) { |
75 | self.original = original; |
76 | } |
77 | } |
78 | |
79 | /// Displays a TOML parse error |
80 | /// |
81 | /// # Example |
82 | /// |
83 | /// TOML parse error at line 1, column 10 |
84 | /// | |
85 | /// 1 | 00:32:00.a999999 |
86 | /// | ^ |
87 | /// Unexpected `a` |
88 | /// Expected `digit` |
89 | /// While parsing a Time |
90 | /// While parsing a Date-Time |
91 | impl Display for TomlError { |
92 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { |
93 | let mut context = false; |
94 | if let (Some(original), Some(span)) = (&self.original, self.span()) { |
95 | context = true; |
96 | |
97 | let (line, column) = translate_position(original.as_bytes(), span.start); |
98 | let line_num = line + 1; |
99 | let col_num = column + 1; |
100 | let gutter = line_num.to_string().len(); |
101 | let content = original.split(' \n' ).nth(line).expect("valid line number" ); |
102 | |
103 | writeln!( |
104 | f, |
105 | "TOML parse error at line {}, column {}" , |
106 | line_num, col_num |
107 | )?; |
108 | // | |
109 | for _ in 0..=gutter { |
110 | write!(f, " " )?; |
111 | } |
112 | writeln!(f, "|" )?; |
113 | |
114 | // 1 | 00:32:00.a999999 |
115 | write!(f, " {} | " , line_num)?; |
116 | writeln!(f, " {}" , content)?; |
117 | |
118 | // | ^ |
119 | for _ in 0..=gutter { |
120 | write!(f, " " )?; |
121 | } |
122 | write!(f, "|" )?; |
123 | for _ in 0..=column { |
124 | write!(f, " " )?; |
125 | } |
126 | // The span will be empty at eof, so we need to make sure we always print at least |
127 | // one `^` |
128 | write!(f, "^" )?; |
129 | for _ in (span.start + 1)..(span.end.min(span.start + content.len())) { |
130 | write!(f, "^" )?; |
131 | } |
132 | writeln!(f)?; |
133 | } |
134 | writeln!(f, " {}" , self.message)?; |
135 | if !context && !self.keys.is_empty() { |
136 | writeln!(f, "in ` {}`" , self.keys.join("." ))?; |
137 | } |
138 | |
139 | Ok(()) |
140 | } |
141 | } |
142 | |
143 | impl StdError for TomlError { |
144 | fn description(&self) -> &'static str { |
145 | "TOML parse error" |
146 | } |
147 | } |
148 | |
149 | #[derive (Debug)] |
150 | pub(crate) struct ParserError<'b> { |
151 | input: Input<'b>, |
152 | context: Vec<Context>, |
153 | cause: Option<Box<dyn std::error::Error + Send + Sync + 'static>>, |
154 | } |
155 | |
156 | impl<'b> winnow::error::ParseError<Input<'b>> for ParserError<'b> { |
157 | fn from_error_kind(input: Input<'b>, _kind: winnow::error::ErrorKind) -> Self { |
158 | Self { |
159 | input, |
160 | context: Default::default(), |
161 | cause: Default::default(), |
162 | } |
163 | } |
164 | |
165 | fn append(self, _input: Input<'b>, _kind: winnow::error::ErrorKind) -> Self { |
166 | self |
167 | } |
168 | |
169 | fn or(self, other: Self) -> Self { |
170 | other |
171 | } |
172 | } |
173 | |
174 | impl<'b> winnow::error::ParseError<&'b str> for ParserError<'b> { |
175 | fn from_error_kind(input: &'b str, _kind: winnow::error::ErrorKind) -> Self { |
176 | Self { |
177 | input: Input::new(input:BStr::new(bytes:input)), |
178 | context: Default::default(), |
179 | cause: Default::default(), |
180 | } |
181 | } |
182 | |
183 | fn append(self, _input: &'b str, _kind: winnow::error::ErrorKind) -> Self { |
184 | self |
185 | } |
186 | |
187 | fn or(self, other: Self) -> Self { |
188 | other |
189 | } |
190 | } |
191 | |
192 | impl<'b> winnow::error::ContextError<Input<'b>, Context> for ParserError<'b> { |
193 | fn add_context(mut self, _input: Input<'b>, ctx: Context) -> Self { |
194 | self.context.push(ctx); |
195 | self |
196 | } |
197 | } |
198 | |
199 | impl<'b, E: std::error::Error + Send + Sync + 'static> |
200 | winnow::error::FromExternalError<Input<'b>, E> for ParserError<'b> |
201 | { |
202 | fn from_external_error(input: Input<'b>, _kind: winnow::error::ErrorKind, e: E) -> Self { |
203 | Self { |
204 | input, |
205 | context: Default::default(), |
206 | cause: Some(Box::new(e)), |
207 | } |
208 | } |
209 | } |
210 | |
211 | impl<'b, E: std::error::Error + Send + Sync + 'static> winnow::error::FromExternalError<&'b str, E> |
212 | for ParserError<'b> |
213 | { |
214 | fn from_external_error(input: &'b str, _kind: winnow::error::ErrorKind, e: E) -> Self { |
215 | Self { |
216 | input: Input::new(input:BStr::new(bytes:input)), |
217 | context: Default::default(), |
218 | cause: Some(Box::new(e)), |
219 | } |
220 | } |
221 | } |
222 | |
223 | // For tests |
224 | impl<'b> std::cmp::PartialEq for ParserError<'b> { |
225 | fn eq(&self, other: &Self) -> bool { |
226 | self.input == other.input |
227 | && self.context == other.context |
228 | && self.cause.as_ref().map(ToString::to_string) |
229 | == other.cause.as_ref().map(ToString::to_string) |
230 | } |
231 | } |
232 | |
233 | impl<'a> std::fmt::Display for ParserError<'a> { |
234 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
235 | let expression = self.context.iter().find_map(|c| match c { |
236 | Context::Expression(c) => Some(c), |
237 | _ => None, |
238 | }); |
239 | let expected = self |
240 | .context |
241 | .iter() |
242 | .filter_map(|c| match c { |
243 | Context::Expected(c) => Some(c), |
244 | _ => None, |
245 | }) |
246 | .collect::<Vec<_>>(); |
247 | |
248 | let mut newline = false; |
249 | |
250 | if let Some(expression) = expression { |
251 | newline = true; |
252 | |
253 | write!(f, "invalid {}" , expression)?; |
254 | } |
255 | |
256 | if !expected.is_empty() { |
257 | if newline { |
258 | writeln!(f)?; |
259 | } |
260 | newline = true; |
261 | |
262 | write!(f, "expected " )?; |
263 | for (i, expected) in expected.iter().enumerate() { |
264 | if i != 0 { |
265 | write!(f, ", " )?; |
266 | } |
267 | write!(f, " {}" , expected)?; |
268 | } |
269 | } |
270 | if let Some(cause) = &self.cause { |
271 | if newline { |
272 | writeln!(f)?; |
273 | } |
274 | write!(f, " {}" , cause)?; |
275 | } |
276 | |
277 | Ok(()) |
278 | } |
279 | } |
280 | |
281 | #[derive (Copy, Clone, Debug, PartialEq)] |
282 | pub(crate) enum Context { |
283 | Expression(&'static str), |
284 | Expected(ParserValue), |
285 | } |
286 | |
287 | #[derive (Copy, Clone, Debug, PartialEq)] |
288 | pub(crate) enum ParserValue { |
289 | CharLiteral(char), |
290 | StringLiteral(&'static str), |
291 | Description(&'static str), |
292 | } |
293 | |
294 | impl std::fmt::Display for ParserValue { |
295 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
296 | match self { |
297 | ParserValue::CharLiteral(' \n' ) => "newline" .fmt(f), |
298 | ParserValue::CharLiteral('`' ) => "'`'" .fmt(f), |
299 | ParserValue::CharLiteral(c: &char) if c.is_ascii_control() => { |
300 | write!(f, "` {}`" , c.escape_debug()) |
301 | } |
302 | ParserValue::CharLiteral(c: &char) => write!(f, "` {}`" , c), |
303 | ParserValue::StringLiteral(c: &&str) => write!(f, "` {}`" , c), |
304 | ParserValue::Description(c: &&str) => write!(f, " {}" , c), |
305 | } |
306 | } |
307 | } |
308 | |
309 | fn translate_position(input: &[u8], index: usize) -> (usize, usize) { |
310 | if input.is_empty() { |
311 | return (0, index); |
312 | } |
313 | |
314 | let safe_index = index.min(input.len() - 1); |
315 | let column_offset = index - safe_index; |
316 | let index = safe_index; |
317 | |
318 | let nl = input[0..index] |
319 | .iter() |
320 | .rev() |
321 | .enumerate() |
322 | .find(|(_, b)| **b == b' \n' ) |
323 | .map(|(nl, _)| index - nl - 1); |
324 | let line_start = match nl { |
325 | Some(nl) => nl + 1, |
326 | None => 0, |
327 | }; |
328 | let line = input[0..line_start].iter().filter(|b| **b == b' \n' ).count(); |
329 | let line = line; |
330 | |
331 | let column = std::str::from_utf8(&input[line_start..=index]) |
332 | .map(|s| s.chars().count() - 1) |
333 | .unwrap_or_else(|_| index - line_start); |
334 | let column = column + column_offset; |
335 | |
336 | (line, column) |
337 | } |
338 | |
339 | #[cfg (test)] |
340 | mod test_translate_position { |
341 | use super::*; |
342 | |
343 | #[test ] |
344 | fn empty() { |
345 | let input = b"" ; |
346 | let index = 0; |
347 | let position = translate_position(&input[..], index); |
348 | assert_eq!(position, (0, 0)); |
349 | } |
350 | |
351 | #[test ] |
352 | fn start() { |
353 | let input = b"Hello" ; |
354 | let index = 0; |
355 | let position = translate_position(&input[..], index); |
356 | assert_eq!(position, (0, 0)); |
357 | } |
358 | |
359 | #[test ] |
360 | fn end() { |
361 | let input = b"Hello" ; |
362 | let index = input.len() - 1; |
363 | let position = translate_position(&input[..], index); |
364 | assert_eq!(position, (0, input.len() - 1)); |
365 | } |
366 | |
367 | #[test ] |
368 | fn after() { |
369 | let input = b"Hello" ; |
370 | let index = input.len(); |
371 | let position = translate_position(&input[..], index); |
372 | assert_eq!(position, (0, input.len())); |
373 | } |
374 | |
375 | #[test ] |
376 | fn first_line() { |
377 | let input = b"Hello \nWorld \n" ; |
378 | let index = 2; |
379 | let position = translate_position(&input[..], index); |
380 | assert_eq!(position, (0, 2)); |
381 | } |
382 | |
383 | #[test ] |
384 | fn end_of_line() { |
385 | let input = b"Hello \nWorld \n" ; |
386 | let index = 5; |
387 | let position = translate_position(&input[..], index); |
388 | assert_eq!(position, (0, 5)); |
389 | } |
390 | |
391 | #[test ] |
392 | fn start_of_second_line() { |
393 | let input = b"Hello \nWorld \n" ; |
394 | let index = 6; |
395 | let position = translate_position(&input[..], index); |
396 | assert_eq!(position, (1, 0)); |
397 | } |
398 | |
399 | #[test ] |
400 | fn second_line() { |
401 | let input = b"Hello \nWorld \n" ; |
402 | let index = 8; |
403 | let position = translate_position(&input[..], index); |
404 | assert_eq!(position, (1, 2)); |
405 | } |
406 | } |
407 | |
408 | #[derive (Debug, Clone)] |
409 | pub(crate) enum CustomError { |
410 | DuplicateKey { |
411 | key: String, |
412 | table: Option<Vec<Key>>, |
413 | }, |
414 | DottedKeyExtendWrongType { |
415 | key: Vec<Key>, |
416 | actual: &'static str, |
417 | }, |
418 | OutOfRange, |
419 | #[cfg_attr (feature = "unbounded" , allow(dead_code))] |
420 | RecursionLimitExceeded, |
421 | } |
422 | |
423 | impl CustomError { |
424 | pub(crate) fn duplicate_key(path: &[Key], i: usize) -> Self { |
425 | assert!(i < path.len()); |
426 | let key: &Key = &path[i]; |
427 | let repr: Cow<'_, str> = key.display_repr(); |
428 | Self::DuplicateKey { |
429 | key: repr.into(), |
430 | table: Some(path[..i].to_vec()), |
431 | } |
432 | } |
433 | |
434 | pub(crate) fn extend_wrong_type(path: &[Key], i: usize, actual: &'static str) -> Self { |
435 | assert!(i < path.len()); |
436 | Self::DottedKeyExtendWrongType { |
437 | key: path[..=i].to_vec(), |
438 | actual, |
439 | } |
440 | } |
441 | } |
442 | |
443 | impl StdError for CustomError { |
444 | fn description(&self) -> &'static str { |
445 | "TOML parse error" |
446 | } |
447 | } |
448 | |
449 | impl Display for CustomError { |
450 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { |
451 | match self { |
452 | CustomError::DuplicateKey { key, table } => { |
453 | if let Some(table) = table { |
454 | if table.is_empty() { |
455 | write!(f, "duplicate key ` {}` in document root" , key) |
456 | } else { |
457 | let path = table.iter().map(|k| k.get()).collect::<Vec<_>>().join("." ); |
458 | write!(f, "duplicate key ` {}` in table ` {}`" , key, path) |
459 | } |
460 | } else { |
461 | write!(f, "duplicate key ` {}`" , key) |
462 | } |
463 | } |
464 | CustomError::DottedKeyExtendWrongType { key, actual } => { |
465 | let path = key.iter().map(|k| k.get()).collect::<Vec<_>>().join("." ); |
466 | write!( |
467 | f, |
468 | "dotted key ` {}` attempted to extend non-table type ( {})" , |
469 | path, actual |
470 | ) |
471 | } |
472 | CustomError::OutOfRange => write!(f, "value is out of range" ), |
473 | CustomError::RecursionLimitExceeded => write!(f, "recursion limit exceded" ), |
474 | } |
475 | } |
476 | } |
477 | |