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