toml_edit/parser/
errors.rs

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