toml_spanner/
error.rs

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