Skip to main content

toml_spanner/
error.rs

1#![allow(clippy::question_mark)]
2
3#[cfg(test)]
4#[path = "./error_tests.rs"]
5mod tests;
6
7use crate::Span;
8use std::fmt::{self, Debug, Display};
9
10/// An error from parsing or deserializing TOML.
11///
12/// Contains both the error kind and the source location where it occurred.
13pub struct Error {
14    /// What went wrong.
15    pub kind: ErrorKind,
16    /// The primary source span for the error.
17    ///
18    /// Some [`ErrorKind`] variants carry additional spans (e.g. the location
19    /// of a first definition for duplicate-key errors).
20    pub span: Span,
21}
22
23impl std::error::Error for Error {}
24
25impl Debug for Error {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        f.debug_struct("Error")
28            .field("kind", &self.kind)
29            .field("span", &self.span)
30            .finish()
31    }
32}
33
34impl From<(ErrorKind, Span)> for Error {
35    fn from((kind, span): (ErrorKind, Span)) -> Self {
36        Self { kind, span }
37    }
38}
39
40/// The specific kind of parse or deserialization error.
41pub enum ErrorKind {
42    /// EOF was reached when looking for a value.
43    UnexpectedEof,
44
45    /// The input file is larger than the maximum supported size of 4GiB.
46    FileTooLarge,
47
48    /// An invalid character not allowed in a string was found.
49    InvalidCharInString(char),
50
51    /// An invalid character was found as an escape.
52    InvalidEscape(char),
53
54    /// An invalid character was found in a hex escape.
55    InvalidHexEscape(char),
56
57    /// An invalid escape value was specified in a hex escape in a string.
58    ///
59    /// Valid values are in the plane of unicode codepoints.
60    InvalidEscapeValue(u32),
61
62    /// An unexpected character was encountered, typically when looking for a
63    /// value.
64    Unexpected(char),
65
66    /// An unterminated string was found where EOF was found before the ending
67    /// EOF mark.
68    UnterminatedString,
69
70    /// A number failed to parse.
71    InvalidNumber,
72
73    /// The number in the toml file cannot be losslessly converted to the specified
74    /// number type
75    OutOfRange(&'static str),
76
77    /// Wanted one sort of token, but found another.
78    Wanted {
79        /// Expected token type.
80        expected: &'static str,
81        /// Actually found token type.
82        found: &'static str,
83    },
84
85    /// A duplicate table definition was found.
86    DuplicateTable {
87        /// The name of the duplicate table
88        name: String,
89        /// The span where the table was first defined
90        first: Span,
91    },
92
93    /// Duplicate key in table.
94    DuplicateKey {
95        /// The duplicate key
96        key: String,
97        /// The span where the first key is located
98        first: Span,
99    },
100
101    /// A previously defined table was redefined as an array.
102    RedefineAsArray,
103
104    /// Multiline strings are not allowed for key.
105    MultilineStringKey,
106
107    /// A custom error which could be generated when deserializing a particular
108    /// type.
109    Custom(std::borrow::Cow<'static, str>),
110
111    /// Dotted key attempted to extend something that is not a table.
112    DottedKeyInvalidType {
113        /// The span where the non-table value was defined
114        first: Span,
115    },
116
117    /// An unexpected key was encountered.
118    ///
119    /// Used when deserializing a struct with a limited set of fields.
120    UnexpectedKeys {
121        /// The unexpected keys.
122        keys: Vec<(String, Span)>,
123    },
124
125    /// Unquoted string was found when quoted one was expected.
126    UnquotedString,
127
128    /// A required field is missing from a table
129    MissingField(&'static str),
130
131    /// A field in the table is deprecated and the new key should be used instead
132    Deprecated {
133        /// The deprecated key name
134        old: &'static str,
135        /// The key name that should be used instead
136        new: &'static str,
137    },
138
139    /// An unexpected value was encountered
140    UnexpectedValue {
141        /// The list of values that could have been used, eg. typically enum variants
142        expected: &'static [&'static str],
143        /// The actual value that was found.
144        value: Option<String>,
145    },
146}
147
148impl Display for ErrorKind {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        let text = match self {
151            Self::UnexpectedEof => "unexpected-eof",
152            Self::FileTooLarge => "file-too-large",
153            Self::Custom(..) => "custom",
154            Self::DottedKeyInvalidType { .. } => "dotted-key-invalid-type",
155            Self::DuplicateKey { .. } => "duplicate-key",
156            Self::DuplicateTable { .. } => "duplicate-table",
157            Self::UnexpectedKeys { .. } => "unexpected-keys",
158            Self::UnquotedString => "unquoted-string",
159            Self::MultilineStringKey => "multiline-string-key",
160            Self::RedefineAsArray => "redefine-as-array",
161            Self::InvalidCharInString(..) => "invalid-char-in-string",
162            Self::InvalidEscape(..) => "invalid-escape",
163            Self::InvalidEscapeValue(..) => "invalid-escape-value",
164            Self::InvalidHexEscape(..) => "invalid-hex-escape",
165            Self::Unexpected(..) => "unexpected",
166            Self::UnterminatedString => "unterminated-string",
167            Self::InvalidNumber => "invalid-number",
168            Self::OutOfRange(_) => "out-of-range",
169            Self::Wanted { .. } => "wanted",
170            Self::MissingField(..) => "missing-field",
171            Self::Deprecated { .. } => "deprecated",
172            Self::UnexpectedValue { .. } => "unexpected-value",
173        };
174        f.write_str(text)
175    }
176}
177
178impl Debug for ErrorKind {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        Display::fmt(self, f)
181    }
182}
183
184struct Escape(char);
185
186impl fmt::Display for Escape {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        use std::fmt::Write as _;
189
190        if self.0.is_whitespace() {
191            for esc in self.0.escape_default() {
192                f.write_char(esc)?;
193            }
194            Ok(())
195        } else {
196            f.write_char(self.0)
197        }
198    }
199}
200
201macro_rules! rtry {
202    ($($tt:tt)*) => {
203        if let Err(err) = $($tt)* {
204            return Err(err);
205        }
206    };
207}
208impl Display for Error {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        match &self.kind {
211            ErrorKind::UnexpectedEof => f.write_str("unexpected eof encountered"),
212            ErrorKind::FileTooLarge => f.write_str("file is too large (maximum 4GiB)"),
213            ErrorKind::InvalidCharInString(c) => {
214                rtry!(f.write_str("invalid character in string: `"));
215                rtry!(std::fmt::Display::fmt(c, f));
216                f.write_str("`")
217            }
218            ErrorKind::InvalidEscape(c) => {
219                rtry!(f.write_str("invalid escape character in string: `"));
220                rtry!(Escape(*c).fmt(f));
221                f.write_str("`")
222            }
223            ErrorKind::InvalidHexEscape(c) => {
224                rtry!(f.write_str("invalid hex escape character in string: `"));
225                rtry!(std::fmt::Display::fmt(c, f));
226                f.write_str("`")
227            }
228            ErrorKind::InvalidEscapeValue(c) => {
229                rtry!(f.write_str("invalid escape value: `"));
230                rtry!(std::fmt::Display::fmt(c, f));
231                f.write_str("`")
232            }
233            ErrorKind::Unexpected(c) => {
234                rtry!(f.write_str("unexpected character found: `"));
235                rtry!(std::fmt::Display::fmt(c, f));
236                f.write_str("`")
237            }
238            ErrorKind::UnterminatedString => f.write_str("unterminated string"),
239            ErrorKind::Wanted { expected, found } => {
240                rtry!(f.write_str("expected "));
241                rtry!(f.write_str(expected));
242                rtry!(f.write_str(", found "));
243                f.write_str(found)
244            }
245            ErrorKind::InvalidNumber => f.write_str("invalid number"),
246            ErrorKind::OutOfRange(kind) => {
247                rtry!(f.write_str("out of range of '"));
248                rtry!(f.write_str(kind));
249                f.write_str("'")
250            }
251            ErrorKind::DuplicateTable { name, .. } => {
252                rtry!(f.write_str("redefinition of table `"));
253                rtry!(f.write_str(name));
254                f.write_str("`")
255            }
256            ErrorKind::DuplicateKey { key, .. } => {
257                rtry!(f.write_str("duplicate key: `"));
258                rtry!(f.write_str(key));
259                f.write_str("`")
260            }
261            ErrorKind::RedefineAsArray => f.write_str("table redefined as array"),
262            ErrorKind::MultilineStringKey => {
263                f.write_str("multiline strings are not allowed for key")
264            }
265            ErrorKind::Custom(message) => f.write_str(message),
266            ErrorKind::DottedKeyInvalidType { .. } => {
267                f.write_str("dotted key attempted to extend non-table type")
268            }
269            ErrorKind::UnexpectedKeys { keys } => {
270                rtry!(f.write_str("unexpected keys in table: ["));
271                let mut first = true;
272                for (key, _) in keys {
273                    if !first {
274                        rtry!(f.write_str(", "));
275                    }
276                    first = false;
277                    rtry!(f.write_str("\""));
278                    rtry!(f.write_str(key));
279                    rtry!(f.write_str("\""));
280                }
281                f.write_str("]")
282            }
283            ErrorKind::UnquotedString => {
284                f.write_str("invalid TOML value, did you mean to use a quoted string?")
285            }
286            ErrorKind::MissingField(field) => {
287                rtry!(f.write_str("missing field '"));
288                rtry!(f.write_str(field));
289                f.write_str("' in table")
290            }
291            ErrorKind::Deprecated { old, new } => {
292                rtry!(f.write_str("field '"));
293                rtry!(f.write_str(old));
294                rtry!(f.write_str("' is deprecated, '"));
295                rtry!(f.write_str(new));
296                f.write_str("' has replaced it")
297            }
298            ErrorKind::UnexpectedValue { expected, .. } => {
299                rtry!(f.write_str("expected '["));
300                let mut first = true;
301                for val in *expected {
302                    if !first {
303                        rtry!(f.write_str(", "));
304                    }
305                    first = false;
306                    rtry!(f.write_str(val));
307                }
308                f.write_str("]'")
309            }
310        }
311    }
312}
313
314#[cfg(feature = "reporting")]
315impl Error {
316    /// Converts this error into a [`codespan_reporting`](https://docs.rs/codespan-reporting) diagnostic.
317    pub fn to_diagnostic<FileId: Copy + PartialEq>(
318        &self,
319        fid: FileId,
320    ) -> codespan_reporting::diagnostic::Diagnostic<FileId> {
321        let diag =
322            codespan_reporting::diagnostic::Diagnostic::error().with_code(self.kind.to_string());
323
324        use codespan_reporting::diagnostic::Label;
325
326        match &self.kind {
327            ErrorKind::DuplicateKey { first, .. } => diag.with_labels(vec![
328                Label::secondary(fid, *first).with_message("first key instance"),
329                Label::primary(fid, self.span).with_message("duplicate key"),
330            ]),
331            ErrorKind::Unexpected(c) => diag.with_labels(vec![
332                Label::primary(fid, self.span)
333                    .with_message(format!("unexpected character '{}'", Escape(*c))),
334            ]),
335            ErrorKind::InvalidCharInString(c) => diag.with_labels(vec![
336                Label::primary(fid, self.span)
337                    .with_message(format!("invalid character '{}' in string", Escape(*c))),
338            ]),
339            ErrorKind::InvalidEscape(c) => {
340                diag.with_labels(vec![Label::primary(fid, self.span).with_message(format!(
341                    "invalid escape character '{}' in string",
342                    Escape(*c)
343                ))])
344            }
345            ErrorKind::InvalidEscapeValue(_) => diag.with_labels(vec![
346                Label::primary(fid, self.span).with_message("invalid escape value"),
347            ]),
348            ErrorKind::InvalidNumber => diag.with_labels(vec![
349                Label::primary(fid, self.span).with_message("unable to parse number"),
350            ]),
351            ErrorKind::OutOfRange(kind) => diag
352                .with_message(format!("number is out of range of '{kind}'"))
353                .with_labels(vec![Label::primary(fid, self.span)]),
354            ErrorKind::Wanted { expected, .. } => diag.with_labels(vec![
355                Label::primary(fid, self.span).with_message(format!("expected {expected}")),
356            ]),
357            ErrorKind::MultilineStringKey => diag.with_labels(vec![
358                Label::primary(fid, self.span).with_message("multiline keys are not allowed"),
359            ]),
360            ErrorKind::UnterminatedString => diag.with_labels(vec![
361                Label::primary(fid, self.span).with_message("eof reached before string terminator"),
362            ]),
363            ErrorKind::DuplicateTable { first, .. } => diag.with_labels(vec![
364                Label::secondary(fid, *first).with_message("first table instance"),
365                Label::primary(fid, self.span).with_message("duplicate table"),
366            ]),
367            ErrorKind::InvalidHexEscape(c) => diag.with_labels(vec![
368                Label::primary(fid, self.span)
369                    .with_message(format!("invalid hex escape '{}'", Escape(*c))),
370            ]),
371            ErrorKind::UnquotedString => diag.with_labels(vec![
372                Label::primary(fid, self.span).with_message("string is not quoted"),
373            ]),
374            ErrorKind::UnexpectedKeys { keys } => diag
375                .with_message(format!("found {} unexpected keys", keys.len()))
376                .with_labels(
377                    keys.iter()
378                        .map(|(_name, span)| Label::secondary(fid, *span))
379                        .collect(),
380                ),
381            ErrorKind::MissingField(field) => diag
382                .with_message(format!("missing field '{field}'"))
383                .with_labels(vec![
384                    Label::primary(fid, self.span).with_message("table with missing field"),
385                ]),
386            ErrorKind::Deprecated { new, .. } => diag
387                .with_message(format!(
388                    "deprecated field enountered, '{new}' should be used instead"
389                ))
390                .with_labels(vec![
391                    Label::primary(fid, self.span).with_message("deprecated field"),
392                ]),
393            ErrorKind::UnexpectedValue { expected, .. } => diag
394                .with_message(format!("expected '{expected:?}'"))
395                .with_labels(vec![
396                    Label::primary(fid, self.span).with_message("unexpected value"),
397                ]),
398            ErrorKind::UnexpectedEof => diag
399                .with_message("unexpected end of file")
400                .with_labels(vec![Label::primary(fid, self.span)]),
401            ErrorKind::DottedKeyInvalidType { first } => {
402                diag.with_message(self.to_string()).with_labels(vec![
403                    Label::primary(fid, self.span).with_message("attempted to extend table here"),
404                    Label::secondary(fid, *first).with_message("non-table"),
405                ])
406            }
407            ErrorKind::RedefineAsArray => diag
408                .with_message(self.to_string())
409                .with_labels(vec![Label::primary(fid, self.span)]),
410            ErrorKind::Custom(msg) => diag
411                .with_message(msg.to_string())
412                .with_labels(vec![Label::primary(fid, self.span)]),
413            ErrorKind::FileTooLarge => diag
414                .with_message("file is too large (maximum 4GiB)")
415                .with_labels(vec![Label::primary(fid, self.span)]),
416        }
417    }
418}