Skip to main content

toml_spanner/
error.rs

1#![allow(clippy::question_mark)]
2
3#[cfg(feature = "from-toml")]
4use crate::Item;
5use crate::{Key, Span};
6use std::fmt::{self, Debug, Display};
7
8#[derive(Clone, Copy)]
9pub enum PathComponent<'de> {
10    Key(Key<'de>),
11    Index(usize),
12}
13
14/// Represents the dotted path to a value within a TOML document.
15///
16/// A path is a sequence of key and index components, where each entry is
17/// either a table key or an array index. Displaying a `TomlPath` produces a
18/// human-readable dotted string such as `dependencies.serde` or
19/// `servers[0].host`.
20///
21/// Paths are computed lazily after deserialization and attached to each
22/// [`Error`]. Retrieve them with [`Error::path`].
23///
24/// # Examples
25///
26/// ```
27/// # #[cfg(not(feature = "derive"))]
28/// # fn main() {}
29/// # #[cfg(feature = "derive")]
30/// # fn main() {
31/// # use toml_spanner::Arena;
32/// let arena = Arena::new();
33/// let mut doc = toml_spanner::parse("[server]\nport = 'oops'", &arena).unwrap();
34///
35/// #[derive(Debug, toml_spanner::Toml)]
36/// struct Config { server: Server }
37/// #[derive(Debug, toml_spanner::Toml)]
38/// struct Server { port: u16 }
39///
40/// let err = doc.to::<Config>().unwrap_err();
41/// let path = err.errors[0].path().unwrap();
42/// assert_eq!(path.to_string(), "server.port");
43/// # }
44/// ```
45#[repr(transparent)]
46pub struct TomlPath<'a>([PathComponent<'a>]);
47
48impl<'a> std::ops::Deref for TomlPath<'a> {
49    type Target = [PathComponent<'a>];
50    fn deref(&self) -> &Self::Target {
51        &self.0
52    }
53}
54
55impl Debug for TomlPath<'_> {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        let mut out = String::new();
58        push_toml_path(&mut out, &self.0);
59        Debug::fmt(&out, f)
60    }
61}
62
63impl Display for TomlPath<'_> {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        let mut out = String::new();
66        push_toml_path(&mut out, &self.0);
67        f.write_str(&out)
68    }
69}
70
71fn is_bare_key(key: &str) -> bool {
72    if key.is_empty() {
73        return false;
74    }
75    for &b in key.as_bytes() {
76        match b {
77            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-' => (),
78            _ => return false,
79        }
80    }
81    true
82}
83
84pub(crate) struct MaybeTomlPath {
85    ptr: std::ptr::NonNull<PathComponent<'static>>,
86    len: u32,
87    size: u32,
88}
89
90impl MaybeTomlPath {
91    pub(crate) fn empty() -> Self {
92        MaybeTomlPath {
93            ptr: std::ptr::NonNull::dangling(),
94            len: u32::MAX,
95            size: 0,
96        }
97    }
98
99    pub(crate) fn has_path(&self) -> bool {
100        self.size > 0
101    }
102
103    pub(crate) fn from_components(components: &[PathComponent<'_>]) -> MaybeTomlPath {
104        if components.is_empty() {
105            return Self::empty();
106        }
107
108        let len = components.len();
109        let mut total_string_bytes: usize = 0;
110        for comp in components {
111            if let PathComponent::Key(key) = comp {
112                total_string_bytes += key.name.len();
113            }
114        }
115
116        let comp_size = len * std::mem::size_of::<PathComponent<'static>>();
117        let size = comp_size + total_string_bytes;
118
119        // SAFETY: size > 0 because len >= 1 and size_of::<PathComponent>() > 0.
120        let layout = std::alloc::Layout::from_size_align(
121            size,
122            std::mem::align_of::<PathComponent<'static>>(),
123        )
124        .unwrap();
125        // SAFETY: layout has non-zero size
126        let raw = unsafe { std::alloc::alloc(layout) };
127        if raw.is_null() {
128            std::alloc::handle_alloc_error(layout);
129        }
130
131        let base = raw.cast::<PathComponent<'static>>();
132        let mut string_cursor = unsafe { raw.add(comp_size) };
133
134        for (i, comp) in components.iter().enumerate() {
135            let stored = match comp {
136                PathComponent::Key(key) => {
137                    let name_bytes = key.name.as_bytes();
138                    let name_len = name_bytes.len();
139                    // SAFETY: string_cursor points into the trailing region we allocated
140                    unsafe {
141                        std::ptr::copy_nonoverlapping(name_bytes.as_ptr(), string_cursor, name_len);
142                    }
143                    // SAFETY: we just wrote valid UTF-8 bytes here
144                    let name: &'static str = unsafe {
145                        std::str::from_utf8_unchecked(std::slice::from_raw_parts(
146                            string_cursor,
147                            name_len,
148                        ))
149                    };
150                    string_cursor = unsafe { string_cursor.add(name_len) };
151                    PathComponent::Key(Key {
152                        name,
153                        span: key.span,
154                    })
155                }
156                PathComponent::Index(idx) => PathComponent::Index(*idx),
157            };
158            // SAFETY: we allocated space for `len` PathComponents
159            unsafe {
160                base.add(i).write(stored);
161            }
162        }
163
164        MaybeTomlPath {
165            // SAFETY: raw was checked non-null above
166            ptr: unsafe { std::ptr::NonNull::new_unchecked(base) },
167            len: len as u32,
168            size: size as u32,
169        }
170    }
171    #[cfg(feature = "from-toml")]
172    #[inline(always)]
173    pub(crate) fn uncomputed(item_ptr: *const Item<'_>) -> Self {
174        MaybeTomlPath {
175            // SAFETY: item_ptr is non-null (points to an Item on the stack or in the arena).
176            // We store it cast to PathComponent just to reuse the ptr field.
177            ptr: unsafe {
178                std::ptr::NonNull::new_unchecked(item_ptr as *mut PathComponent<'static>)
179            },
180            len: 0,
181            size: 0,
182        }
183    }
184
185    #[cfg(feature = "from-toml")]
186    pub(crate) fn is_uncomputed(&self) -> bool {
187        self.size == 0 && self.len != u32::MAX
188    }
189
190    #[cfg(feature = "from-toml")]
191    pub(crate) fn uncomputed_ptr(&self) -> *const () {
192        self.ptr.as_ptr() as *const ()
193    }
194
195    fn as_toml_path<'a>(&'a self) -> Option<&'a TomlPath<'a>> {
196        if !self.has_path() {
197            return None;
198        }
199        // SAFETY: components live in the allocation, strings point into
200        // the same allocation. The returned TomlPath borrows self, so the
201        // inner 'static is shortened to 'a, preventing it from escaping.
202        let slice = unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len as usize) };
203        Some(unsafe { &*(slice as *const [PathComponent<'static>] as *const TomlPath<'a>) })
204    }
205}
206
207impl Drop for MaybeTomlPath {
208    fn drop(&mut self) {
209        let size = self.size as usize;
210        if size > 0 {
211            let layout = std::alloc::Layout::from_size_align(
212                size,
213                std::mem::align_of::<PathComponent<'static>>(),
214            )
215            .unwrap();
216            // SAFETY: ptr was allocated with this layout
217            unsafe {
218                std::alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
219            }
220        }
221    }
222}
223
224// SAFETY: TomlPath owns its allocation entirely and contains no thread-local state.
225unsafe impl Send for MaybeTomlPath {}
226// SAFETY: &TomlPath only gives &[PathComponent] access, which is safe to share.
227unsafe impl Sync for MaybeTomlPath {}
228
229/// A single error from parsing or converting a TOML document.
230///
231/// Errors arise from two phases:
232///
233/// - Parsing: syntax errors such as unterminated strings, duplicate keys,
234///   or unexpected characters. [`parse`](crate::parse) returns the first such
235///   error as `Err(Error)`.
236///
237/// - Conversion: type mismatches, missing fields, unknown keys, and
238///   other constraint violations detected by [`FromToml`](crate::FromToml).
239///   These accumulate so that a single pass surfaces as many problems as
240///   possible.
241///
242/// [`parse_recoverable`](crate::parse_recoverable) combines both phases,
243/// continuing past syntax errors and collecting them alongside conversion
244/// errors into a single [`Document::errors`](crate::Document::errors) list.
245///
246/// # Extracting information
247///
248/// | Method                                        | Returns                                                         |
249/// |-----------------------------------------------|-----------------------------------------------------------------|
250/// | [`kind()`](Self::kind)                        | The [`ErrorKind`] variant for this error                        |
251/// | [`span()`](Self::span)                        | Source [`Span`] (byte offsets), `0..0` when no location applies |
252/// | [`path()`](Self::path)                        | Optional [`TomlPath`] to the offending value                    |
253/// | [`message(source)`](Self::message)            | Human-readable diagnostic message                               |
254/// | [`primary_label()`](Self::primary_label)      | Optional `(Span, String)` label for the error site              |
255/// | [`secondary_label()`](Self::secondary_label)  | Optional `(Span, String)` for related locations                 |
256///
257/// The `message`, `primary_label`, and `secondary_label` methods provide
258/// building blocks for rich diagnostics, mapping onto the label model used by
259/// [`codespan-reporting`](https://docs.rs/codespan-reporting) and
260/// [`annotate-snippets`](https://docs.rs/annotate-snippets).
261///
262/// # Integration with error reporting libraries
263///
264/// <details>
265/// <summary><code>codespan-reporting</code> example</summary>
266///
267/// ```ignore
268/// use codespan_reporting::diagnostic::{Diagnostic, Label};
269///
270/// fn error_to_diagnostic(
271///     error: &toml_spanner::Error,
272///     source: &str,
273/// ) -> Diagnostic<()> {
274///     let mut labels = Vec::new();
275///     if let Some((span, text)) = error.secondary_label() {
276///         labels.push(Label::secondary((), span).with_message(text));
277///     }
278///     if let Some((span, label)) = error.primary_label() {
279///         let l = Label::primary((), span);
280///         labels.push(if label.is_empty() {
281///             l
282///         } else {
283///             l.with_message(label)
284///         });
285///     }
286///     Diagnostic::error()
287///         .with_code(error.kind().kind_name())
288///         .with_message(error.message_with_path(source))
289///         .with_labels(labels)
290/// }
291/// ```
292///
293/// </details>
294///
295/// <details>
296/// <summary><code>annotate-snippets</code> example</summary>
297///
298/// ```ignore
299/// use annotate_snippets::{AnnotationKind, Group, Level, Snippet};
300///
301/// fn error_to_snippet<'s>(
302///     error: &toml_spanner::Error,
303///     source: &'s str,
304///     path: &'s str,
305/// ) -> Group<'s> {
306///     let message = error.message_with_path(source);
307///     let mut snippet = Snippet::source(source).path(path).fold(true);
308///     if let Some((span, text)) = error.secondary_label() {
309///         snippet = snippet.annotation(
310///             AnnotationKind::Context.span(span.range()).label(text),
311///         );
312///     }
313///     if let Some((span, label)) = error.primary_label() {
314///         let ann = AnnotationKind::Primary.span(span.range());
315///         snippet = snippet.annotation(if label.is_empty() {
316///             ann
317///         } else {
318///             ann.label(label)
319///         });
320///     }
321///     Level::ERROR.primary_title(message).element(snippet)
322/// }
323/// ```
324///
325/// </details>
326///
327/// # Multiple error accumulation
328///
329/// During [`FromToml`](crate::FromToml) conversion, errors are pushed into a
330/// shared [`Context`](crate::Context) rather than causing an immediate abort.
331/// The sentinel type [`Failed`](crate::Failed) signals that a branch failed
332/// without carrying error details itself. When
333/// [`Document::to`](crate::Document::to) or [`from_str`](crate::from_str)
334/// finishes, all accumulated errors are returned in
335/// [`FromTomlError::errors`](crate::FromTomlError).
336///
337/// [`parse_recoverable`](crate::parse_recoverable) extends this to the
338/// parsing phase, collecting syntax errors into the same list so valid
339/// portions of the document remain available for inspection.
340pub struct Error {
341    pub(crate) kind: ErrorInner,
342    pub(crate) span: Span,
343    pub(crate) path: MaybeTomlPath,
344}
345
346pub(crate) enum ErrorInner {
347    Static(ErrorKind<'static>),
348    Custom(Box<str>),
349}
350/// The specific kind of error.
351#[non_exhaustive]
352#[derive(Clone, Copy)]
353pub enum ErrorKind<'a> {
354    /// A custom error message
355    Custom(&'a str),
356
357    /// EOF was reached when looking for a value.
358    UnexpectedEof,
359
360    /// The input file is larger than the maximum supported size of 512 MiB.
361    FileTooLarge,
362
363    /// An invalid character not allowed in a string was found.
364    InvalidCharInString(char),
365
366    /// An invalid character was found as an escape.
367    InvalidEscape(char),
368
369    /// An invalid character was found in a hex escape.
370    InvalidHexEscape(char),
371
372    /// An invalid escape value was specified in a hex escape in a string.
373    ///
374    /// Valid values are in the plane of unicode codepoints.
375    InvalidEscapeValue(u32),
376
377    /// An unexpected character was encountered, typically when looking for a
378    /// value.
379    Unexpected(char),
380
381    /// An unterminated string was found where EOF or a newline was reached
382    /// before the closing delimiter.
383    ///
384    /// The `char` is the expected closing delimiter (`"` or `'`).
385    UnterminatedString(char),
386
387    /// An integer literal failed to parse, with an optional reason.
388    InvalidInteger(&'static str),
389
390    /// A float literal failed to parse, with an optional reason.
391    InvalidFloat(&'static str),
392
393    /// A datetime literal failed to parse, with an optional reason.
394    InvalidDateTime(&'static str),
395
396    /// The number in the toml file cannot be losslessly converted to the specified
397    /// number type
398    OutOfRange {
399        /// The target type name (e.g. `"u8"`)
400        ty: &'static &'static str,
401        /// The accepted range as a display string (e.g. `"0..=255"`), or empty
402        range: &'static &'static str,
403    },
404
405    /// Wanted one sort of token, but found another.
406    Wanted {
407        /// Expected token type.
408        expected: &'static &'static str,
409        /// Actually found token type.
410        found: &'static &'static str,
411    },
412
413    /// A duplicate table definition was found.
414    DuplicateTable {
415        /// The span of the table name (for extracting the name from source)
416        name: Span,
417        /// The span where the table was first defined
418        first: Span,
419    },
420
421    /// Duplicate key in table.
422    DuplicateKey {
423        /// The span where the first key is located
424        first: Span,
425    },
426
427    /// A previously defined table was redefined as an array.
428    RedefineAsArray {
429        /// The span where the table was first defined
430        first: Span,
431    },
432
433    /// Multiline strings are not allowed for key.
434    MultilineStringKey,
435
436    /// Dotted key attempted to extend something that is not a table.
437    DottedKeyInvalidType {
438        /// The span where the non-table value was defined
439        first: Span,
440    },
441
442    /// An unexpected key was encountered.
443    ///
444    /// Used when converting a struct with a limited set of fields.
445    UnexpectedKey {
446        /// Developer provided association tag useful for programmatic filtering
447        /// or adding additional messages or notes to diagnostics. Defaults to 0.
448        tag: u32,
449    },
450
451    /// Unquoted string was found when quoted one was expected.
452    UnquotedString,
453
454    /// A required field is missing from a table
455    MissingField(&'static str),
456
457    /// A field was set more than once (e.g. via primary key and alias)
458    DuplicateField {
459        /// The canonical struct field name
460        field: &'static str,
461        /// The span where the key was first defined
462        first: Span,
463    },
464
465    /// A field in the table is deprecated and the new key should be used instead
466    Deprecated {
467        /// Developer provided association tag useful for programmatic filtering
468        /// or adding additional messages or notes to diagnostics such as version
469        /// info. Defaults to 0.
470        tag: u32,
471        /// The deprecated key name
472        old: &'static &'static str,
473        /// The key name that should be used instead
474        new: &'static &'static str,
475    },
476
477    /// An unexpected value was encountered
478    UnexpectedValue {
479        /// The list of values that could have been used
480        expected: &'static [&'static str],
481    },
482
483    /// A string did not match any known variant
484    UnexpectedVariant {
485        /// The list of variant names that would have been accepted
486        expected: &'static [&'static str],
487    },
488
489    /// A comma is missing between elements in an array.
490    MissingArrayComma,
491
492    /// An array was not closed before EOF.
493    UnclosedArray,
494
495    /// A comma is missing between entries in an inline table.
496    MissingInlineTableComma,
497
498    /// An inline table was not closed before EOF or a newline.
499    UnclosedInlineTable,
500}
501
502impl<'a> ErrorKind<'a> {
503    pub fn kind_name(&self) -> &'static str {
504        match self {
505            ErrorKind::Custom(_) => "Custom",
506            ErrorKind::UnexpectedEof => "UnexpectedEof",
507            ErrorKind::FileTooLarge => "FileTooLarge",
508            ErrorKind::InvalidCharInString(_) => "InvalidCharInString",
509            ErrorKind::InvalidEscape(_) => "InvalidEscape",
510            ErrorKind::InvalidHexEscape(_) => "InvalidHexEscape",
511            ErrorKind::InvalidEscapeValue(_) => "InvalidEscapeValue",
512            ErrorKind::Unexpected(_) => "Unexpected",
513            ErrorKind::UnterminatedString(_) => "UnterminatedString",
514            ErrorKind::InvalidInteger(_) => "InvalidInteger",
515            ErrorKind::InvalidFloat(_) => "InvalidFloat",
516            ErrorKind::InvalidDateTime(_) => "InvalidDateTime",
517            ErrorKind::OutOfRange { .. } => "OutOfRange",
518            ErrorKind::Wanted { .. } => "Wanted",
519            ErrorKind::DuplicateTable { .. } => "DuplicateTable",
520            ErrorKind::DuplicateKey { .. } => "DuplicateKey",
521            ErrorKind::RedefineAsArray { .. } => "RedefineAsArray",
522            ErrorKind::MultilineStringKey => "MultilineStringKey",
523            ErrorKind::DottedKeyInvalidType { .. } => "DottedKeyInvalidType",
524            ErrorKind::UnexpectedKey { .. } => "UnexpectedKey",
525            ErrorKind::UnquotedString => "UnquotedString",
526            ErrorKind::MissingField(_) => "MissingField",
527            ErrorKind::DuplicateField { .. } => "DuplicateField",
528            ErrorKind::Deprecated { .. } => "Deprecated",
529            ErrorKind::UnexpectedValue { .. } => "UnexpectedValue",
530            ErrorKind::UnexpectedVariant { .. } => "UnexpectedVariant",
531            ErrorKind::MissingArrayComma => "MissingArrayComma",
532            ErrorKind::UnclosedArray => "UnclosedArray",
533            ErrorKind::MissingInlineTableComma => "MissingInlineTableComma",
534            ErrorKind::UnclosedInlineTable => "UnclosedInlineTable",
535        }
536    }
537}
538
539impl Error {
540    /// Returns the source span where this error occurred.
541    ///
542    /// A span of `0..0` ([`Span::is_empty`]) means the error has no specific
543    /// source location, as with [`ErrorKind::FileTooLarge`].
544    pub fn span(&self) -> Span {
545        self.span
546    }
547
548    /// Returns the error kind.
549    pub fn kind(&self) -> ErrorKind<'_> {
550        match &self.kind {
551            ErrorInner::Static(kind) => *kind,
552            ErrorInner::Custom(error) => ErrorKind::Custom(error),
553        }
554    }
555
556    /// Returns the TOML path where this error occurred, if available.
557    pub fn path<'a>(&'a self) -> Option<&'a TomlPath<'a>> {
558        self.path.as_toml_path()
559    }
560
561    /// Creates an error with a custom message at the given source span.
562    pub fn custom(message: impl ToString, span: Span) -> Error {
563        Error {
564            kind: ErrorInner::Custom(message.to_string().into()),
565            span,
566            path: MaybeTomlPath::empty(),
567        }
568    }
569
570    /// Creates an error with a custom message anchored to `item`.
571    ///
572    /// Like [`Error::custom`], but in addition to setting
573    /// [`span`](Self::span) from `item.span()`, the error is bound to
574    /// `item` so that [`Error::path`] reports the dotted TOML path to it
575    /// after
576    /// [`Document::compute_error_paths`](crate::Document::compute_error_paths)
577    /// runs. [`Document::to`](crate::Document::to) and
578    /// [`Document::to_allowing_errors`](crate::Document::to_allowing_errors)
579    /// invoke that pass automatically.
580    ///
581    /// Use [`Error::custom`] when no item is available or when the dotted
582    /// path is not needed.
583    #[cfg(feature = "from-toml")]
584    pub fn custom_at(message: impl ToString, item: &Item<'_>) -> Error {
585        Error {
586            kind: ErrorInner::Custom(message.to_string().into()),
587            span: item.span(),
588            path: MaybeTomlPath::uncomputed(item),
589        }
590    }
591
592    /// Creates an error with a static message at the given source span.
593    #[cfg(feature = "from-toml")]
594    pub(crate) fn custom_static(message: &'static str, span: Span) -> Error {
595        Error {
596            kind: ErrorInner::Static(ErrorKind::Custom(message)),
597            span,
598            path: MaybeTomlPath::empty(),
599        }
600    }
601
602    /// Creates an error from a known error kind and span.
603    pub(crate) fn new(kind: ErrorKind<'static>, span: Span) -> Error {
604        Error {
605            kind: ErrorInner::Static(kind),
606            span,
607            path: MaybeTomlPath::empty(),
608        }
609    }
610
611    /// Creates an error from a known error kind, span, and TOML path.
612    pub(crate) fn new_with_path(kind: ErrorKind<'static>, span: Span, path: MaybeTomlPath) -> Self {
613        Error {
614            kind: ErrorInner::Static(kind),
615            span,
616            path,
617        }
618    }
619}
620
621impl<'a> Debug for ErrorKind<'a> {
622    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
623        f.write_str(self.kind_name())
624    }
625}
626
627fn kind_message(kind: ErrorKind<'_>) -> String {
628    let mut out = String::new();
629    kind_message_inner(kind, &mut out);
630    out
631}
632
633#[inline(never)]
634fn s_push(out: &mut String, s: &str) {
635    out.push_str(s);
636}
637
638#[inline(never)]
639fn s_push_char(out: &mut String, c: char) {
640    out.push(c);
641}
642
643fn push_escape(out: &mut String, c: char) {
644    if c.is_control() {
645        for esc in c.escape_default() {
646            s_push_char(out, esc);
647        }
648    } else {
649        s_push_char(out, c);
650    }
651}
652
653fn push_u32(out: &mut String, mut n: u32) {
654    let mut buf = [0u8; 10];
655    let mut i = buf.len();
656    if n == 0 {
657        s_push_char(out, '0');
658        return;
659    }
660    while n > 0 {
661        i -= 1;
662        buf[i] = b'0' + (n % 10) as u8;
663        n /= 10;
664    }
665    // SAFETY: digits are always valid ASCII/UTF-8
666    s_push(out, unsafe { std::str::from_utf8_unchecked(&buf[i..]) });
667}
668
669fn kind_message_inner(kind: ErrorKind<'_>, out: &mut String) {
670    match kind {
671        ErrorKind::Custom(message) => s_push(out, message),
672        ErrorKind::UnexpectedEof => s_push(out, "unexpected eof encountered"),
673        ErrorKind::FileTooLarge => s_push(out, "file is too large (maximum 512 MiB)"),
674        ErrorKind::InvalidCharInString(c) => {
675            s_push(out, "invalid character in string: `");
676            push_escape(out, c);
677            s_push_char(out, '`');
678        }
679        ErrorKind::InvalidEscape(c) => {
680            s_push(out, "invalid escape character in string: `");
681            push_escape(out, c);
682            s_push_char(out, '`');
683        }
684        ErrorKind::InvalidHexEscape(c) => {
685            s_push(out, "invalid hex escape character in string: `");
686            push_escape(out, c);
687            s_push_char(out, '`');
688        }
689        ErrorKind::InvalidEscapeValue(c) => {
690            s_push(out, "invalid unicode escape value `");
691            push_unicode_escape(out, c);
692            s_push_char(out, '`');
693        }
694        ErrorKind::Unexpected(c) => {
695            s_push(out, "unexpected character found: `");
696            push_escape(out, c);
697            s_push_char(out, '`');
698        }
699        ErrorKind::UnterminatedString(delim) => {
700            if delim == '\'' {
701                s_push(out, "unterminated literal string, expected `'`");
702            } else {
703                s_push(out, "unterminated basic string, expected `\"`");
704            }
705        }
706        ErrorKind::Wanted { expected, found } => {
707            s_push(out, "expected ");
708            s_push(out, expected);
709            s_push(out, ", found ");
710            s_push(out, found);
711        }
712        ErrorKind::InvalidInteger(reason)
713        | ErrorKind::InvalidFloat(reason)
714        | ErrorKind::InvalidDateTime(reason) => {
715            let prefix = match kind {
716                ErrorKind::InvalidInteger(_) => "invalid integer",
717                ErrorKind::InvalidFloat(_) => "invalid float",
718                _ => "invalid datetime",
719            };
720            s_push(out, prefix);
721            if !reason.is_empty() {
722                s_push(out, ": ");
723                s_push(out, reason);
724            }
725        }
726        ErrorKind::OutOfRange { ty, .. } => {
727            s_push(out, "value out of range for ");
728            s_push(out, ty);
729        }
730        ErrorKind::DuplicateTable { .. } => s_push(out, "redefinition of table"),
731        ErrorKind::DuplicateKey { .. } => s_push(out, "duplicate key"),
732        ErrorKind::RedefineAsArray { .. } => s_push(out, "table redefined as array"),
733        ErrorKind::MultilineStringKey => {
734            s_push(out, "multiline strings are not allowed for key");
735        }
736        ErrorKind::DottedKeyInvalidType { .. } => {
737            s_push(out, "dotted key attempted to extend non-table type");
738        }
739        ErrorKind::UnexpectedKey { .. } => s_push(out, "unexpected key"),
740        ErrorKind::UnquotedString => {
741            s_push(out, "string values must be quoted, expected string literal");
742        }
743        ErrorKind::MissingField(field) => {
744            s_push(out, "missing required key '");
745            s_push(out, field);
746            s_push_char(out, '\'');
747        }
748        ErrorKind::DuplicateField { field, .. } => {
749            s_push(out, "duplicate key '");
750            s_push(out, field);
751            s_push_char(out, '\'');
752        }
753        ErrorKind::Deprecated { old, new, .. } => {
754            s_push(out, "key '");
755            s_push(out, old);
756            s_push(out, "' is deprecated, use '");
757            s_push(out, new);
758            s_push(out, "' instead");
759        }
760        ErrorKind::UnexpectedValue { expected } => {
761            s_push(out, "unexpected value, expected one of: ");
762            let mut first = true;
763            for val in expected {
764                if !first {
765                    s_push(out, ", ");
766                }
767                first = false;
768                s_push(out, val);
769            }
770        }
771        ErrorKind::UnexpectedVariant { expected } => {
772            s_push(out, "unknown variant, expected one of: ");
773            let mut first = true;
774            for val in expected {
775                if !first {
776                    s_push(out, ", ");
777                }
778                first = false;
779                s_push(out, val);
780            }
781        }
782        ErrorKind::MissingArrayComma => {
783            s_push(out, "missing comma between elements, expected `,` in array");
784        }
785        ErrorKind::UnclosedArray => s_push(out, "unclosed array, expected `]`"),
786        ErrorKind::MissingInlineTableComma => {
787            s_push(
788                out,
789                "missing comma between fields, expected `,` in inline table",
790            );
791        }
792        ErrorKind::UnclosedInlineTable => s_push(out, "unclosed inline table, expected `}`"),
793    }
794}
795
796impl Display for Error {
797    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
798        f.write_str(&kind_message(self.kind()))?;
799        if let Some(path) = self.path() {
800            let components: &[PathComponent<'_>] = path;
801            let display = match self.kind() {
802                ErrorKind::DuplicateField { .. } | ErrorKind::Deprecated { .. } => {
803                    &components[..components.len().saturating_sub(1)]
804                }
805                _ => components,
806            };
807            if !display.is_empty() {
808                f.write_str(" at `")?;
809                let mut out = String::new();
810                push_toml_path(&mut out, display);
811                f.write_str(&out)?;
812                f.write_str("`")?;
813            }
814        }
815        Ok(())
816    }
817}
818
819impl Debug for Error {
820    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
821        let kind = self.kind();
822        f.debug_struct("Error")
823            .field("kind", &kind.kind_name())
824            .field("message", &kind_message(kind))
825            .field("span", &self.span().range())
826            .field("path", &self.path())
827            .finish()
828    }
829}
830
831impl std::error::Error for Error {}
832
833impl From<(ErrorKind<'static>, Span)> for Error {
834    fn from((kind, span): (ErrorKind<'static>, Span)) -> Self {
835        Self {
836            kind: ErrorInner::Static(kind),
837            span,
838            path: MaybeTomlPath::empty(),
839        }
840    }
841}
842
843fn push_toml_path(out: &mut String, path: &[PathComponent<'_>]) {
844    let mut first = true;
845    for component in path.iter() {
846        match component {
847            PathComponent::Key(key) => {
848                if !first {
849                    s_push_char(out, '.');
850                }
851                first = false;
852                if is_bare_key(key.name) {
853                    s_push(out, key.name);
854                } else {
855                    s_push_char(out, '"');
856                    for ch in key.name.chars() {
857                        match ch {
858                            '"' => s_push(out, "\\\""),
859                            '\\' => s_push(out, "\\\\"),
860                            '\n' => s_push(out, "\\n"),
861                            '\r' => s_push(out, "\\r"),
862                            '\t' => s_push(out, "\\t"),
863                            c if c.is_control() => {
864                                push_unicode_escape(out, c as u32);
865                            }
866                            c => s_push_char(out, c),
867                        }
868                    }
869                    s_push_char(out, '"');
870                }
871            }
872            PathComponent::Index(idx) => {
873                s_push_char(out, '[');
874                push_u32(out, *idx as u32);
875                s_push_char(out, ']');
876            }
877        }
878    }
879}
880
881fn push_unicode_escape(out: &mut String, n: u32) {
882    s_push(out, "\\u");
883    let mut buf = [0u8; 8];
884    let digits = if n <= 0xFFFF { 4 } else { 6 };
885    for i in (0..digits).rev() {
886        let nibble = ((n >> (i * 4)) & 0xF) as u8;
887        buf[digits - 1 - i] = if nibble < 10 {
888            b'0' + nibble
889        } else {
890            b'A' + nibble - 10
891        };
892    }
893    // SAFETY: hex digits are valid ASCII
894    s_push(out, unsafe {
895        std::str::from_utf8_unchecked(&buf[..digits])
896    });
897}
898
899impl Error {
900    /// Returns the diagnostic message for this error, without the TOML path.
901    ///
902    /// Some error kinds extract names from `source` for richer messages.
903    pub fn message(&self, source: &str) -> String {
904        let mut out = String::new();
905        self.message_inner(source, &mut out);
906        out
907    }
908
909    /// Returns the diagnostic message for this error, with the TOML path appended.
910    ///
911    /// Some error kinds extract names from `source` for richer messages.
912    pub fn message_with_path(&self, source: &str) -> String {
913        let mut out = String::new();
914        self.message_inner(source, &mut out);
915        self.append_path(&mut out);
916        out
917    }
918
919    fn append_path(&self, out: &mut String) {
920        if let Some(p) = self.path() {
921            let components: &[PathComponent<'_>] = p;
922            let display = match self.kind() {
923                ErrorKind::DuplicateKey { .. }
924                | ErrorKind::DuplicateTable { .. }
925                | ErrorKind::DuplicateField { .. }
926                | ErrorKind::Deprecated { .. } => &components[..components.len().saturating_sub(1)],
927                _ => components,
928            };
929            if !display.is_empty() {
930                s_push(out, " at `");
931                push_toml_path(out, display);
932                s_push_char(out, '`');
933            }
934        }
935    }
936
937    fn message_inner(&self, source: &str, out: &mut String) {
938        let span = self.span;
939        let kind = self.kind();
940        let path = self.path();
941
942        match kind {
943            ErrorKind::DuplicateKey { .. } => {
944                if let Some(name) = source.get(span.range()) {
945                    s_push(out, "the key `");
946                    s_push(out, name);
947                    s_push(out, "` is defined multiple times in table");
948                } else {
949                    kind_message_inner(kind, out);
950                }
951            }
952            ErrorKind::DuplicateTable { name, .. } => {
953                if let Some(table_name) = source.get(name.range()) {
954                    s_push(out, "redefinition of table `");
955                    s_push(out, table_name);
956                    s_push_char(out, '`');
957                } else {
958                    kind_message_inner(kind, out);
959                }
960            }
961            ErrorKind::UnexpectedKey { .. } if path.is_none() => {
962                if let Some(key_name) = source.get(span.range()) {
963                    s_push(out, "unexpected key `");
964                    s_push(out, key_name);
965                    s_push_char(out, '`');
966                } else {
967                    kind_message_inner(kind, out);
968                }
969            }
970            ErrorKind::DuplicateField { field, .. } => {
971                if let Some(key_name) = source.get(span.range()) {
972                    if key_name == field {
973                        s_push(out, "key '");
974                        s_push(out, field);
975                        s_push(out, "' defined multiple times in same table");
976                    } else {
977                        s_push(out, "both '");
978                        s_push(out, key_name);
979                        s_push(out, "' and '");
980                        s_push(out, field);
981                        s_push(out, "' are defined but resolve to the same field");
982                    }
983                } else {
984                    kind_message_inner(kind, out);
985                }
986            }
987            ErrorKind::UnexpectedVariant { .. } => {
988                if let Some(value) = source.get(span.range()) {
989                    s_push(out, "unknown variant ");
990                    match value.split_once('\n') {
991                        Some((first, _)) => {
992                            s_push(out, first);
993                            s_push(out, "...");
994                        }
995                        None => s_push(out, value),
996                    }
997                } else {
998                    kind_message_inner(kind, out);
999                }
1000            }
1001            _ => kind_message_inner(kind, out),
1002        }
1003    }
1004
1005    /// Returns the primary label span and text for this error, if any.
1006    pub fn primary_label(&self) -> Option<(Span, String)> {
1007        let mut out = String::new();
1008        self.primary_label_inner(&mut out);
1009        Some((self.span, out))
1010    }
1011
1012    fn primary_label_inner(&self, out: &mut String) {
1013        let kind = self.kind();
1014        match kind {
1015            ErrorKind::DuplicateKey { .. } => s_push(out, "duplicate key"),
1016            ErrorKind::DuplicateTable { .. } => s_push(out, "duplicate table"),
1017            ErrorKind::DottedKeyInvalidType { .. } => {
1018                s_push(out, "attempted to extend table here");
1019            }
1020            ErrorKind::Unexpected(c) => {
1021                s_push(out, "unexpected character '");
1022                push_escape(out, c);
1023                s_push_char(out, '\'');
1024            }
1025            ErrorKind::InvalidCharInString(c) => {
1026                s_push(out, "invalid character '");
1027                push_escape(out, c);
1028                s_push(out, "' in string");
1029            }
1030            ErrorKind::InvalidEscape(c) => {
1031                s_push(out, "invalid escape character '");
1032                push_escape(out, c);
1033                s_push(out, "' in string");
1034            }
1035            ErrorKind::InvalidEscapeValue(_) => s_push(out, "invalid unicode escape value"),
1036            ErrorKind::InvalidInteger(_)
1037            | ErrorKind::InvalidFloat(_)
1038            | ErrorKind::InvalidDateTime(_) => kind_message_inner(kind, out),
1039            ErrorKind::InvalidHexEscape(c) => {
1040                s_push(out, "invalid hex escape '");
1041                push_escape(out, c);
1042                s_push_char(out, '\'');
1043            }
1044            ErrorKind::Wanted { expected, .. } => {
1045                s_push(out, "expected ");
1046                s_push(out, expected);
1047            }
1048            ErrorKind::MultilineStringKey => s_push(out, "multiline keys are not allowed"),
1049            ErrorKind::UnterminatedString(delim) => {
1050                s_push(out, "expected `");
1051                s_push_char(out, delim);
1052                s_push_char(out, '`');
1053            }
1054            ErrorKind::UnquotedString => s_push(out, "string is not quoted"),
1055            ErrorKind::UnexpectedKey { .. } => s_push(out, "unexpected key"),
1056            ErrorKind::MissingField(field) => {
1057                s_push(out, "missing key '");
1058                s_push(out, field);
1059                s_push_char(out, '\'');
1060            }
1061            ErrorKind::DuplicateField { .. } => s_push(out, "duplicate key"),
1062            ErrorKind::Deprecated { .. } => s_push(out, "deprecated key"),
1063            ErrorKind::UnexpectedValue { .. } => s_push(out, "unexpected value"),
1064            ErrorKind::UnexpectedVariant { expected } => {
1065                s_push(out, "expected one of: ");
1066                let mut first = true;
1067                for val in expected {
1068                    if !first {
1069                        s_push(out, ", ");
1070                    }
1071                    first = false;
1072                    s_push(out, val);
1073                }
1074            }
1075            ErrorKind::MissingArrayComma => s_push(out, "expected `,`"),
1076            ErrorKind::UnclosedArray => s_push(out, "expected `]`"),
1077            ErrorKind::MissingInlineTableComma => s_push(out, "expected `,`"),
1078            ErrorKind::UnclosedInlineTable => s_push(out, "expected `}`"),
1079            ErrorKind::OutOfRange { range, .. } => {
1080                if !range.is_empty() {
1081                    s_push(out, "expected ");
1082                    s_push(out, range);
1083                }
1084            }
1085            ErrorKind::UnexpectedEof
1086            | ErrorKind::RedefineAsArray { .. }
1087            | ErrorKind::FileTooLarge
1088            | ErrorKind::Custom(..) => {}
1089        }
1090    }
1091
1092    /// Returns the secondary label span and text, if any.
1093    ///
1094    /// Some errors reference a related location (for example, the first
1095    /// definition of a duplicate key).
1096    pub fn secondary_label(&self) -> Option<(Span, String)> {
1097        let (first, text) = match self.kind() {
1098            ErrorKind::DuplicateKey { first } => (first, "first key instance"),
1099            ErrorKind::DuplicateTable { first, .. } => (first, "first table instance"),
1100            ErrorKind::DottedKeyInvalidType { first } => (first, "non-table"),
1101            ErrorKind::RedefineAsArray { first } => (first, "first defined as table"),
1102            ErrorKind::DuplicateField { first, .. } => (first, "first defined here"),
1103            _ => return None,
1104        };
1105        Some((first, String::from(text)))
1106    }
1107}