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