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 static message at the given source span.
571    #[cfg(feature = "from-toml")]
572    pub(crate) fn custom_static(message: &'static str, span: Span) -> Error {
573        Error {
574            kind: ErrorInner::Static(ErrorKind::Custom(message)),
575            span,
576            path: MaybeTomlPath::empty(),
577        }
578    }
579
580    /// Creates an error from a known error kind and span.
581    pub(crate) fn new(kind: ErrorKind<'static>, span: Span) -> Error {
582        Error {
583            kind: ErrorInner::Static(kind),
584            span,
585            path: MaybeTomlPath::empty(),
586        }
587    }
588
589    /// Creates an error from a known error kind, span, and TOML path.
590    pub(crate) fn new_with_path(kind: ErrorKind<'static>, span: Span, path: MaybeTomlPath) -> Self {
591        Error {
592            kind: ErrorInner::Static(kind),
593            span,
594            path,
595        }
596    }
597}
598
599impl<'a> Debug for ErrorKind<'a> {
600    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
601        f.write_str(self.kind_name())
602    }
603}
604
605fn kind_message(kind: ErrorKind<'_>) -> String {
606    let mut out = String::new();
607    kind_message_inner(kind, &mut out);
608    out
609}
610
611#[inline(never)]
612fn s_push(out: &mut String, s: &str) {
613    out.push_str(s);
614}
615
616#[inline(never)]
617fn s_push_char(out: &mut String, c: char) {
618    out.push(c);
619}
620
621fn push_escape(out: &mut String, c: char) {
622    if c.is_control() {
623        for esc in c.escape_default() {
624            s_push_char(out, esc);
625        }
626    } else {
627        s_push_char(out, c);
628    }
629}
630
631fn push_u32(out: &mut String, mut n: u32) {
632    let mut buf = [0u8; 10];
633    let mut i = buf.len();
634    if n == 0 {
635        s_push_char(out, '0');
636        return;
637    }
638    while n > 0 {
639        i -= 1;
640        buf[i] = b'0' + (n % 10) as u8;
641        n /= 10;
642    }
643    // SAFETY: digits are always valid ASCII/UTF-8
644    s_push(out, unsafe { std::str::from_utf8_unchecked(&buf[i..]) });
645}
646
647fn kind_message_inner(kind: ErrorKind<'_>, out: &mut String) {
648    match kind {
649        ErrorKind::Custom(message) => s_push(out, message),
650        ErrorKind::UnexpectedEof => s_push(out, "unexpected eof encountered"),
651        ErrorKind::FileTooLarge => s_push(out, "file is too large (maximum 512 MiB)"),
652        ErrorKind::InvalidCharInString(c) => {
653            s_push(out, "invalid character in string: `");
654            push_escape(out, c);
655            s_push_char(out, '`');
656        }
657        ErrorKind::InvalidEscape(c) => {
658            s_push(out, "invalid escape character in string: `");
659            push_escape(out, c);
660            s_push_char(out, '`');
661        }
662        ErrorKind::InvalidHexEscape(c) => {
663            s_push(out, "invalid hex escape character in string: `");
664            push_escape(out, c);
665            s_push_char(out, '`');
666        }
667        ErrorKind::InvalidEscapeValue(c) => {
668            s_push(out, "invalid unicode escape value `");
669            push_unicode_escape(out, c);
670            s_push_char(out, '`');
671        }
672        ErrorKind::Unexpected(c) => {
673            s_push(out, "unexpected character found: `");
674            push_escape(out, c);
675            s_push_char(out, '`');
676        }
677        ErrorKind::UnterminatedString(delim) => {
678            if delim == '\'' {
679                s_push(out, "unterminated literal string, expected `'`");
680            } else {
681                s_push(out, "unterminated basic string, expected `\"`");
682            }
683        }
684        ErrorKind::Wanted { expected, found } => {
685            s_push(out, "expected ");
686            s_push(out, expected);
687            s_push(out, ", found ");
688            s_push(out, found);
689        }
690        ErrorKind::InvalidInteger(reason)
691        | ErrorKind::InvalidFloat(reason)
692        | ErrorKind::InvalidDateTime(reason) => {
693            let prefix = match kind {
694                ErrorKind::InvalidInteger(_) => "invalid integer",
695                ErrorKind::InvalidFloat(_) => "invalid float",
696                _ => "invalid datetime",
697            };
698            s_push(out, prefix);
699            if !reason.is_empty() {
700                s_push(out, ": ");
701                s_push(out, reason);
702            }
703        }
704        ErrorKind::OutOfRange { ty, .. } => {
705            s_push(out, "value out of range for ");
706            s_push(out, ty);
707        }
708        ErrorKind::DuplicateTable { .. } => s_push(out, "redefinition of table"),
709        ErrorKind::DuplicateKey { .. } => s_push(out, "duplicate key"),
710        ErrorKind::RedefineAsArray { .. } => s_push(out, "table redefined as array"),
711        ErrorKind::MultilineStringKey => {
712            s_push(out, "multiline strings are not allowed for key");
713        }
714        ErrorKind::DottedKeyInvalidType { .. } => {
715            s_push(out, "dotted key attempted to extend non-table type");
716        }
717        ErrorKind::UnexpectedKey { .. } => s_push(out, "unexpected key"),
718        ErrorKind::UnquotedString => {
719            s_push(out, "string values must be quoted, expected string literal");
720        }
721        ErrorKind::MissingField(field) => {
722            s_push(out, "missing required key '");
723            s_push(out, field);
724            s_push_char(out, '\'');
725        }
726        ErrorKind::DuplicateField { field, .. } => {
727            s_push(out, "duplicate key '");
728            s_push(out, field);
729            s_push_char(out, '\'');
730        }
731        ErrorKind::Deprecated { old, new, .. } => {
732            s_push(out, "key '");
733            s_push(out, old);
734            s_push(out, "' is deprecated, use '");
735            s_push(out, new);
736            s_push(out, "' instead");
737        }
738        ErrorKind::UnexpectedValue { expected } => {
739            s_push(out, "unexpected value, expected one of: ");
740            let mut first = true;
741            for val in expected {
742                if !first {
743                    s_push(out, ", ");
744                }
745                first = false;
746                s_push(out, val);
747            }
748        }
749        ErrorKind::UnexpectedVariant { expected } => {
750            s_push(out, "unknown variant, expected one of: ");
751            let mut first = true;
752            for val in expected {
753                if !first {
754                    s_push(out, ", ");
755                }
756                first = false;
757                s_push(out, val);
758            }
759        }
760        ErrorKind::MissingArrayComma => {
761            s_push(out, "missing comma between elements, expected `,` in array");
762        }
763        ErrorKind::UnclosedArray => s_push(out, "unclosed array, expected `]`"),
764        ErrorKind::MissingInlineTableComma => {
765            s_push(
766                out,
767                "missing comma between fields, expected `,` in inline table",
768            );
769        }
770        ErrorKind::UnclosedInlineTable => s_push(out, "unclosed inline table, expected `}`"),
771    }
772}
773
774impl Display for Error {
775    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
776        f.write_str(&kind_message(self.kind()))?;
777        if let Some(path) = self.path() {
778            let components: &[PathComponent<'_>] = path;
779            let display = match self.kind() {
780                ErrorKind::DuplicateField { .. } | ErrorKind::Deprecated { .. } => {
781                    &components[..components.len().saturating_sub(1)]
782                }
783                _ => components,
784            };
785            if !display.is_empty() {
786                f.write_str(" at `")?;
787                let mut out = String::new();
788                push_toml_path(&mut out, display);
789                f.write_str(&out)?;
790                f.write_str("`")?;
791            }
792        }
793        Ok(())
794    }
795}
796
797impl Debug for Error {
798    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799        let kind = self.kind();
800        f.debug_struct("Error")
801            .field("kind", &kind.kind_name())
802            .field("message", &kind_message(kind))
803            .field("span", &self.span().range())
804            .field("path", &self.path())
805            .finish()
806    }
807}
808
809impl std::error::Error for Error {}
810
811impl From<(ErrorKind<'static>, Span)> for Error {
812    fn from((kind, span): (ErrorKind<'static>, Span)) -> Self {
813        Self {
814            kind: ErrorInner::Static(kind),
815            span,
816            path: MaybeTomlPath::empty(),
817        }
818    }
819}
820
821fn push_toml_path(out: &mut String, path: &[PathComponent<'_>]) {
822    let mut first = true;
823    for component in path.iter() {
824        match component {
825            PathComponent::Key(key) => {
826                if !first {
827                    s_push_char(out, '.');
828                }
829                first = false;
830                if is_bare_key(key.name) {
831                    s_push(out, key.name);
832                } else {
833                    s_push_char(out, '"');
834                    for ch in key.name.chars() {
835                        match ch {
836                            '"' => s_push(out, "\\\""),
837                            '\\' => s_push(out, "\\\\"),
838                            '\n' => s_push(out, "\\n"),
839                            '\r' => s_push(out, "\\r"),
840                            '\t' => s_push(out, "\\t"),
841                            c if c.is_control() => {
842                                push_unicode_escape(out, c as u32);
843                            }
844                            c => s_push_char(out, c),
845                        }
846                    }
847                    s_push_char(out, '"');
848                }
849            }
850            PathComponent::Index(idx) => {
851                s_push_char(out, '[');
852                push_u32(out, *idx as u32);
853                s_push_char(out, ']');
854            }
855        }
856    }
857}
858
859fn push_unicode_escape(out: &mut String, n: u32) {
860    s_push(out, "\\u");
861    let mut buf = [0u8; 8];
862    let digits = if n <= 0xFFFF { 4 } else { 6 };
863    for i in (0..digits).rev() {
864        let nibble = ((n >> (i * 4)) & 0xF) as u8;
865        buf[digits - 1 - i] = if nibble < 10 {
866            b'0' + nibble
867        } else {
868            b'A' + nibble - 10
869        };
870    }
871    // SAFETY: hex digits are valid ASCII
872    s_push(out, unsafe {
873        std::str::from_utf8_unchecked(&buf[..digits])
874    });
875}
876
877impl Error {
878    /// Returns the diagnostic message for this error, without the TOML path.
879    ///
880    /// Some error kinds extract names from `source` for richer messages.
881    pub fn message(&self, source: &str) -> String {
882        let mut out = String::new();
883        self.message_inner(source, &mut out);
884        out
885    }
886
887    /// Returns the diagnostic message for this error, with the TOML path appended.
888    ///
889    /// Some error kinds extract names from `source` for richer messages.
890    pub fn message_with_path(&self, source: &str) -> String {
891        let mut out = String::new();
892        self.message_inner(source, &mut out);
893        self.append_path(&mut out);
894        out
895    }
896
897    fn append_path(&self, out: &mut String) {
898        if let Some(p) = self.path() {
899            let components: &[PathComponent<'_>] = p;
900            let display = match self.kind() {
901                ErrorKind::DuplicateKey { .. }
902                | ErrorKind::DuplicateTable { .. }
903                | ErrorKind::DuplicateField { .. }
904                | ErrorKind::Deprecated { .. } => &components[..components.len().saturating_sub(1)],
905                _ => components,
906            };
907            if !display.is_empty() {
908                s_push(out, " at `");
909                push_toml_path(out, display);
910                s_push_char(out, '`');
911            }
912        }
913    }
914
915    fn message_inner(&self, source: &str, out: &mut String) {
916        let span = self.span;
917        let kind = self.kind();
918        let path = self.path();
919
920        match kind {
921            ErrorKind::DuplicateKey { .. } => {
922                if let Some(name) = source.get(span.range()) {
923                    s_push(out, "the key `");
924                    s_push(out, name);
925                    s_push(out, "` is defined multiple times in table");
926                } else {
927                    kind_message_inner(kind, out);
928                }
929            }
930            ErrorKind::DuplicateTable { name, .. } => {
931                if let Some(table_name) = source.get(name.range()) {
932                    s_push(out, "redefinition of table `");
933                    s_push(out, table_name);
934                    s_push_char(out, '`');
935                } else {
936                    kind_message_inner(kind, out);
937                }
938            }
939            ErrorKind::UnexpectedKey { .. } if path.is_none() => {
940                if let Some(key_name) = source.get(span.range()) {
941                    s_push(out, "unexpected key `");
942                    s_push(out, key_name);
943                    s_push_char(out, '`');
944                } else {
945                    kind_message_inner(kind, out);
946                }
947            }
948            ErrorKind::DuplicateField { field, .. } => {
949                if let Some(key_name) = source.get(span.range()) {
950                    if key_name == field {
951                        s_push(out, "key '");
952                        s_push(out, field);
953                        s_push(out, "' defined multiple times in same table");
954                    } else {
955                        s_push(out, "both '");
956                        s_push(out, key_name);
957                        s_push(out, "' and '");
958                        s_push(out, field);
959                        s_push(out, "' are defined but resolve to the same field");
960                    }
961                } else {
962                    kind_message_inner(kind, out);
963                }
964            }
965            ErrorKind::UnexpectedVariant { .. } => {
966                if let Some(value) = source.get(span.range()) {
967                    s_push(out, "unknown variant ");
968                    match value.split_once('\n') {
969                        Some((first, _)) => {
970                            s_push(out, first);
971                            s_push(out, "...");
972                        }
973                        None => s_push(out, value),
974                    }
975                } else {
976                    kind_message_inner(kind, out);
977                }
978            }
979            _ => kind_message_inner(kind, out),
980        }
981    }
982
983    /// Returns the primary label span and text for this error, if any.
984    pub fn primary_label(&self) -> Option<(Span, String)> {
985        let mut out = String::new();
986        self.primary_label_inner(&mut out);
987        Some((self.span, out))
988    }
989
990    fn primary_label_inner(&self, out: &mut String) {
991        let kind = self.kind();
992        match kind {
993            ErrorKind::DuplicateKey { .. } => s_push(out, "duplicate key"),
994            ErrorKind::DuplicateTable { .. } => s_push(out, "duplicate table"),
995            ErrorKind::DottedKeyInvalidType { .. } => {
996                s_push(out, "attempted to extend table here");
997            }
998            ErrorKind::Unexpected(c) => {
999                s_push(out, "unexpected character '");
1000                push_escape(out, c);
1001                s_push_char(out, '\'');
1002            }
1003            ErrorKind::InvalidCharInString(c) => {
1004                s_push(out, "invalid character '");
1005                push_escape(out, c);
1006                s_push(out, "' in string");
1007            }
1008            ErrorKind::InvalidEscape(c) => {
1009                s_push(out, "invalid escape character '");
1010                push_escape(out, c);
1011                s_push(out, "' in string");
1012            }
1013            ErrorKind::InvalidEscapeValue(_) => s_push(out, "invalid unicode escape value"),
1014            ErrorKind::InvalidInteger(_)
1015            | ErrorKind::InvalidFloat(_)
1016            | ErrorKind::InvalidDateTime(_) => kind_message_inner(kind, out),
1017            ErrorKind::InvalidHexEscape(c) => {
1018                s_push(out, "invalid hex escape '");
1019                push_escape(out, c);
1020                s_push_char(out, '\'');
1021            }
1022            ErrorKind::Wanted { expected, .. } => {
1023                s_push(out, "expected ");
1024                s_push(out, expected);
1025            }
1026            ErrorKind::MultilineStringKey => s_push(out, "multiline keys are not allowed"),
1027            ErrorKind::UnterminatedString(delim) => {
1028                s_push(out, "expected `");
1029                s_push_char(out, delim);
1030                s_push_char(out, '`');
1031            }
1032            ErrorKind::UnquotedString => s_push(out, "string is not quoted"),
1033            ErrorKind::UnexpectedKey { .. } => s_push(out, "unexpected key"),
1034            ErrorKind::MissingField(field) => {
1035                s_push(out, "missing key '");
1036                s_push(out, field);
1037                s_push_char(out, '\'');
1038            }
1039            ErrorKind::DuplicateField { .. } => s_push(out, "duplicate key"),
1040            ErrorKind::Deprecated { .. } => s_push(out, "deprecated key"),
1041            ErrorKind::UnexpectedValue { .. } => s_push(out, "unexpected value"),
1042            ErrorKind::UnexpectedVariant { expected } => {
1043                s_push(out, "expected one of: ");
1044                let mut first = true;
1045                for val in expected {
1046                    if !first {
1047                        s_push(out, ", ");
1048                    }
1049                    first = false;
1050                    s_push(out, val);
1051                }
1052            }
1053            ErrorKind::MissingArrayComma => s_push(out, "expected `,`"),
1054            ErrorKind::UnclosedArray => s_push(out, "expected `]`"),
1055            ErrorKind::MissingInlineTableComma => s_push(out, "expected `,`"),
1056            ErrorKind::UnclosedInlineTable => s_push(out, "expected `}`"),
1057            ErrorKind::OutOfRange { range, .. } => {
1058                if !range.is_empty() {
1059                    s_push(out, "expected ");
1060                    s_push(out, range);
1061                }
1062            }
1063            ErrorKind::UnexpectedEof
1064            | ErrorKind::RedefineAsArray { .. }
1065            | ErrorKind::FileTooLarge
1066            | ErrorKind::Custom(..) => {}
1067        }
1068    }
1069
1070    /// Returns the secondary label span and text, if any.
1071    ///
1072    /// Some errors reference a related location (for example, the first
1073    /// definition of a duplicate key).
1074    pub fn secondary_label(&self) -> Option<(Span, String)> {
1075        let (first, text) = match self.kind() {
1076            ErrorKind::DuplicateKey { first } => (first, "first key instance"),
1077            ErrorKind::DuplicateTable { first, .. } => (first, "first table instance"),
1078            ErrorKind::DottedKeyInvalidType { first } => (first, "non-table"),
1079            ErrorKind::RedefineAsArray { first } => (first, "first defined as table"),
1080            ErrorKind::DuplicateField { first, .. } => (first, "first defined here"),
1081            _ => return None,
1082        };
1083        Some((first, String::from(text)))
1084    }
1085}