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
7use winnow::error::ContextError;
8use winnow::error::ParseError;
9
10/// Type representing a TOML parse error
11#[derive(Debug, Clone, Eq, PartialEq, Hash)]
12pub struct TomlError {
13    message: String,
14    original: Option<String>,
15    keys: Vec<String>,
16    span: Option<std::ops::Range<usize>>,
17}
18
19impl TomlError {
20    pub(crate) fn new(error: ParseError<Input<'_>, ContextError>, mut original: Input<'_>) -> Self {
21        use winnow::stream::Stream;
22
23        let offset = error.offset();
24        let span = if offset == original.len() {
25            offset..offset
26        } else {
27            offset..(offset + 1)
28        };
29
30        let message = error.inner().to_string();
31        let original = original.finish();
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
91impl 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
143impl StdError for TomlError {
144    fn description(&self) -> &'static str {
145        "TOML parse error"
146    }
147}
148
149fn translate_position(input: &[u8], index: usize) -> (usize, usize) {
150    if input.is_empty() {
151        return (0, index);
152    }
153
154    let safe_index = index.min(input.len() - 1);
155    let column_offset = index - safe_index;
156    let index = safe_index;
157
158    let nl = input[0..index]
159        .iter()
160        .rev()
161        .enumerate()
162        .find(|(_, b)| **b == b'\n')
163        .map(|(nl, _)| index - nl - 1);
164    let line_start = match nl {
165        Some(nl) => nl + 1,
166        None => 0,
167    };
168    let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
169    let line = line;
170
171    let column = std::str::from_utf8(&input[line_start..=index])
172        .map(|s| s.chars().count() - 1)
173        .unwrap_or_else(|_| index - line_start);
174    let column = column + column_offset;
175
176    (line, column)
177}
178
179#[cfg(test)]
180mod test_translate_position {
181    use super::*;
182
183    #[test]
184    fn empty() {
185        let input = b"";
186        let index = 0;
187        let position = translate_position(&input[..], index);
188        assert_eq!(position, (0, 0));
189    }
190
191    #[test]
192    fn start() {
193        let input = b"Hello";
194        let index = 0;
195        let position = translate_position(&input[..], index);
196        assert_eq!(position, (0, 0));
197    }
198
199    #[test]
200    fn end() {
201        let input = b"Hello";
202        let index = input.len() - 1;
203        let position = translate_position(&input[..], index);
204        assert_eq!(position, (0, input.len() - 1));
205    }
206
207    #[test]
208    fn after() {
209        let input = b"Hello";
210        let index = input.len();
211        let position = translate_position(&input[..], index);
212        assert_eq!(position, (0, input.len()));
213    }
214
215    #[test]
216    fn first_line() {
217        let input = b"Hello\nWorld\n";
218        let index = 2;
219        let position = translate_position(&input[..], index);
220        assert_eq!(position, (0, 2));
221    }
222
223    #[test]
224    fn end_of_line() {
225        let input = b"Hello\nWorld\n";
226        let index = 5;
227        let position = translate_position(&input[..], index);
228        assert_eq!(position, (0, 5));
229    }
230
231    #[test]
232    fn start_of_second_line() {
233        let input = b"Hello\nWorld\n";
234        let index = 6;
235        let position = translate_position(&input[..], index);
236        assert_eq!(position, (1, 0));
237    }
238
239    #[test]
240    fn second_line() {
241        let input = b"Hello\nWorld\n";
242        let index = 8;
243        let position = translate_position(&input[..], index);
244        assert_eq!(position, (1, 2));
245    }
246}
247
248#[derive(Debug, Clone)]
249pub(crate) enum CustomError {
250    DuplicateKey {
251        key: String,
252        table: Option<Vec<Key>>,
253    },
254    DottedKeyExtendWrongType {
255        key: Vec<Key>,
256        actual: &'static str,
257    },
258    OutOfRange,
259    #[cfg_attr(feature = "unbounded", allow(dead_code))]
260    RecursionLimitExceeded,
261}
262
263impl CustomError {
264    pub(crate) fn duplicate_key(path: &[Key], i: usize) -> Self {
265        assert!(i < path.len());
266        let key = &path[i];
267        let repr = key.display_repr();
268        Self::DuplicateKey {
269            key: repr.into(),
270            table: Some(path[..i].to_vec()),
271        }
272    }
273
274    pub(crate) fn extend_wrong_type(path: &[Key], i: usize, actual: &'static str) -> Self {
275        assert!(i < path.len());
276        Self::DottedKeyExtendWrongType {
277            key: path[..=i].to_vec(),
278            actual,
279        }
280    }
281}
282
283impl StdError for CustomError {
284    fn description(&self) -> &'static str {
285        "TOML parse error"
286    }
287}
288
289impl Display for CustomError {
290    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
291        match self {
292            CustomError::DuplicateKey { key, table } => {
293                if let Some(table) = table {
294                    if table.is_empty() {
295                        write!(f, "duplicate key `{}` in document root", key)
296                    } else {
297                        let path = table.iter().map(|k| k.get()).collect::<Vec<_>>().join(".");
298                        write!(f, "duplicate key `{}` in table `{}`", key, path)
299                    }
300                } else {
301                    write!(f, "duplicate key `{}`", key)
302                }
303            }
304            CustomError::DottedKeyExtendWrongType { key, actual } => {
305                let path = key.iter().map(|k| k.get()).collect::<Vec<_>>().join(".");
306                write!(
307                    f,
308                    "dotted key `{}` attempted to extend non-table type ({})",
309                    path, actual
310                )
311            }
312            CustomError::OutOfRange => write!(f, "value is out of range"),
313            CustomError::RecursionLimitExceeded => write!(f, "recursion limit exceeded"),
314        }
315    }
316}