ron2/
error.rs

1//! Error types for RON parsing and deserialization.
2//!
3//! This module provides a unified error type for all ron2 operations:
4//! - [`Error`] - The main error type (internally boxed for efficiency)
5//! - [`ErrorKind`] - All possible error variants
6//! - [`Position`], [`Span`] - Source position types
7//! - [`PathSegment`] - Path context for nested errors
8
9use alloc::{
10    borrow::Cow,
11    boxed::Box,
12    string::{String, ToString},
13    sync::Arc,
14    vec::Vec,
15};
16use core::{fmt, str::Utf8Error};
17use std::io;
18
19use unicode_ident::is_xid_continue;
20
21use crate::chars::{is_ident_first_char, is_ident_raw_char};
22
23mod path;
24mod span;
25
26pub use path::PathSegment;
27pub use span::{LineIndex, LineIndexCursor, Position, Span};
28
29// Compatibility aliases for schema validation
30#[doc(hidden)]
31pub type ValidationError = Error;
32#[doc(hidden)]
33pub type ValidationErrorKind = ErrorKind;
34
35// =============================================================================
36// Result type alias
37// =============================================================================
38
39/// Result type for ron2 operations.
40pub type Result<T, E = Error> = core::result::Result<T, E>;
41
42// =============================================================================
43// Error struct (internally boxed)
44// =============================================================================
45
46/// Unified error type for all ron2 operations.
47///
48/// This type is internally boxed to keep `Result<T, Error>` small (8 bytes),
49/// which improves performance in the common success case.
50///
51/// # Example
52///
53/// ```
54/// use ron2::{Error, ErrorKind, Span};
55///
56/// // Create an error with a span
57/// let err = Error::expected("closing `]`", Span::synthetic());
58///
59/// // Add path context
60/// let err = Error::type_mismatch("i32", "String")
61///     .in_element(0)
62///     .in_field("items");
63///
64/// println!("{}", err);
65/// ```
66#[derive(Debug, Clone)]
67pub struct Error(Box<ErrorInner>);
68
69#[derive(Debug, Clone)]
70struct ErrorInner {
71    kind: ErrorKind,
72    span: Span,
73    path: Vec<PathSegment>,
74}
75
76impl Error {
77    // =========================================================================
78    // Accessors
79    // =========================================================================
80
81    /// Returns the error kind.
82    #[must_use]
83    pub fn kind(&self) -> &ErrorKind {
84        &self.0.kind
85    }
86
87    /// Returns the source span.
88    #[must_use]
89    pub fn span(&self) -> &Span {
90        &self.0.span
91    }
92
93    /// Returns the path context (innermost to outermost).
94    #[must_use]
95    pub fn path(&self) -> &[PathSegment] {
96        &self.0.path
97    }
98
99    // =========================================================================
100    // Constructors
101    // =========================================================================
102
103    /// Create an error with a synthetic span (for errors without source location).
104    ///
105    /// Prefer `with_span` when a span is available for better error messages.
106    #[must_use]
107    pub fn new(kind: ErrorKind) -> Self {
108        Self(Box::new(ErrorInner {
109            kind,
110            span: Span::synthetic(),
111            path: Vec::new(),
112        }))
113    }
114
115    /// Create an error with a specific span.
116    #[must_use]
117    pub fn with_span(kind: ErrorKind, span: Span) -> Self {
118        Self(Box::new(ErrorInner {
119            kind,
120            span,
121            path: Vec::new(),
122        }))
123    }
124
125    /// Create an error spanning from (1,1) to the end of source.
126    #[must_use]
127    pub fn wrap(kind: ErrorKind, source: &str) -> Self {
128        Self(Box::new(ErrorInner {
129            kind,
130            span: Span {
131                start: Position { line: 1, col: 1 },
132                end: Position::from_src_end(source),
133                start_offset: 0,
134                end_offset: source.len(),
135            },
136            path: Vec::new(),
137        }))
138    }
139
140    /// Create an error at position (1,1) with zero-length span.
141    #[must_use]
142    pub fn at_start(kind: ErrorKind) -> Self {
143        Self(Box::new(ErrorInner {
144            kind,
145            span: Span {
146                start: Position { line: 1, col: 1 },
147                end: Position { line: 1, col: 1 },
148                start_offset: 0,
149                end_offset: 0,
150            },
151            path: Vec::new(),
152        }))
153    }
154
155    // =========================================================================
156    // Path builders (fluent API)
157    // =========================================================================
158
159    /// Add a field context to this error's path.
160    #[must_use]
161    pub fn in_field(mut self, name: impl Into<String>) -> Self {
162        self.0.path.push(PathSegment::Field(name.into()));
163        self
164    }
165
166    /// Add an element context to this error's path.
167    #[must_use]
168    pub fn in_element(mut self, index: usize) -> Self {
169        self.0.path.push(PathSegment::Element(index));
170        self
171    }
172
173    /// Add a variant context to this error's path.
174    #[must_use]
175    pub fn in_variant(mut self, name: impl Into<String>) -> Self {
176        self.0.path.push(PathSegment::Variant(name.into()));
177        self
178    }
179
180    /// Add a type ref context to this error's path.
181    #[must_use]
182    pub fn in_type_ref(mut self, path: impl Into<String>) -> Self {
183        self.0.path.push(PathSegment::TypeRef(path.into()));
184        self
185    }
186
187    /// Add a map key context to this error's path.
188    #[must_use]
189    pub fn in_map_key(mut self) -> Self {
190        self.0.path.push(PathSegment::MapKey);
191        self
192    }
193
194    /// Add a map value context to this error's path.
195    #[must_use]
196    pub fn in_map_value(mut self, key: impl Into<String>) -> Self {
197        self.0.path.push(PathSegment::MapValue(key.into()));
198        self
199    }
200
201    // =========================================================================
202    // Convenience constructors
203    // =========================================================================
204
205    /// Create an "unexpected end of input" error.
206    #[must_use]
207    pub fn eof() -> Self {
208        Self::new(ErrorKind::Eof)
209    }
210
211    /// Create an "expected X" error.
212    #[must_use]
213    pub fn expected(what: impl Into<Cow<'static, str>>, span: Span) -> Self {
214        Self::with_span(
215            ErrorKind::Expected {
216                expected: what.into(),
217                context: None,
218            },
219            span,
220        )
221    }
222
223    /// Create an "expected X in Y" error.
224    #[must_use]
225    pub fn expected_in(
226        what: impl Into<Cow<'static, str>>,
227        context: &'static str,
228        span: Span,
229    ) -> Self {
230        Self::with_span(
231            ErrorKind::Expected {
232                expected: what.into(),
233                context: Some(context),
234            },
235            span,
236        )
237    }
238
239    /// Create a type mismatch error.
240    #[must_use]
241    pub fn type_mismatch(expected: impl Into<String>, found: impl Into<String>) -> Self {
242        Self::new(ErrorKind::TypeMismatch {
243            expected: expected.into(),
244            found: found.into(),
245        })
246    }
247
248    /// Create a missing field error.
249    #[must_use]
250    pub fn missing_field(field: impl Into<Cow<'static, str>>) -> Self {
251        Self::new(ErrorKind::MissingField {
252            field: field.into(),
253            outer: None,
254        })
255    }
256
257    /// Create a missing field error with outer context.
258    #[must_use]
259    pub fn missing_field_in(
260        field: impl Into<Cow<'static, str>>,
261        outer: impl Into<Cow<'static, str>>,
262    ) -> Self {
263        Self::new(ErrorKind::MissingField {
264            field: field.into(),
265            outer: Some(outer.into()),
266        })
267    }
268
269    /// Create an unknown field error.
270    #[must_use]
271    pub fn unknown_field(
272        field: impl Into<Cow<'static, str>>,
273        expected: &'static [&'static str],
274    ) -> Self {
275        Self::new(ErrorKind::UnknownField {
276            field: field.into(),
277            expected,
278            outer: None,
279        })
280    }
281
282    /// Create an unknown field error with outer context.
283    #[must_use]
284    pub fn unknown_field_in(
285        field: impl Into<Cow<'static, str>>,
286        expected: &'static [&'static str],
287        outer: impl Into<Cow<'static, str>>,
288    ) -> Self {
289        Self::new(ErrorKind::UnknownField {
290            field: field.into(),
291            expected,
292            outer: Some(outer.into()),
293        })
294    }
295
296    /// Create a duplicate field error.
297    #[must_use]
298    pub fn duplicate_field(field: impl Into<Cow<'static, str>>) -> Self {
299        Self::new(ErrorKind::DuplicateField {
300            field: field.into(),
301            outer: None,
302        })
303    }
304
305    /// Create a duplicate field error with outer context.
306    #[must_use]
307    pub fn duplicate_field_in(
308        field: impl Into<Cow<'static, str>>,
309        outer: impl Into<Cow<'static, str>>,
310    ) -> Self {
311        Self::new(ErrorKind::DuplicateField {
312            field: field.into(),
313            outer: Some(outer.into()),
314        })
315    }
316
317    /// Create an unknown variant error.
318    #[must_use]
319    pub fn unknown_variant(
320        variant: impl Into<Cow<'static, str>>,
321        expected: &'static [&'static str],
322    ) -> Self {
323        Self::new(ErrorKind::UnknownVariant {
324            variant: variant.into(),
325            expected,
326            outer: None,
327        })
328    }
329
330    /// Create an unknown variant error with outer context.
331    #[must_use]
332    pub fn unknown_variant_in(
333        variant: impl Into<Cow<'static, str>>,
334        expected: &'static [&'static str],
335        outer: impl Into<Cow<'static, str>>,
336    ) -> Self {
337        Self::new(ErrorKind::UnknownVariant {
338            variant: variant.into(),
339            expected,
340            outer: Some(outer.into()),
341        })
342    }
343
344    /// Create a length mismatch error.
345    #[must_use]
346    pub fn length_mismatch(expected: impl Into<String>, found: usize) -> Self {
347        Self::new(ErrorKind::LengthMismatch {
348            expected: expected.into(),
349            found,
350            context: None,
351        })
352    }
353
354    /// Create a length mismatch error with context.
355    #[must_use]
356    pub fn length_mismatch_in(
357        expected: impl Into<String>,
358        found: usize,
359        context: &'static str,
360    ) -> Self {
361        Self::new(ErrorKind::LengthMismatch {
362            expected: expected.into(),
363            found,
364            context: Some(context),
365        })
366    }
367
368    /// Create an integer out of bounds error.
369    #[must_use]
370    pub fn integer_out_of_bounds(
371        value: impl Into<Cow<'static, str>>,
372        target_type: &'static str,
373    ) -> Self {
374        Self::new(ErrorKind::IntegerOutOfBounds {
375            value: value.into(),
376            target_type,
377        })
378    }
379
380    // =========================================================================
381    // Suggestions
382    // =========================================================================
383
384    /// Get a suggestion for fixing this error, if applicable.
385    #[must_use]
386    pub fn suggestion(&self) -> Option<String> {
387        self.0.kind.suggestion()
388    }
389}
390
391// =============================================================================
392// ErrorKind enum
393// =============================================================================
394
395/// The specific kind of error that occurred.
396#[derive(Debug, Clone)]
397#[non_exhaustive]
398pub enum ErrorKind {
399    // =========================================================================
400    // General / Infrastructure
401    // =========================================================================
402    /// Generic message (escape hatch for custom errors).
403    Message(String),
404
405    /// Formatting error (from `fmt::Error`).
406    Fmt,
407
408    /// IO error with preserved source for error chaining.
409    Io {
410        /// Human-readable error message.
411        message: String,
412        /// Original IO error (wrapped in Arc for Clone).
413        #[allow(clippy::redundant_allocation)]
414        source: Option<Arc<io::Error>>,
415    },
416
417    /// UTF-8 decoding error with preserved source.
418    Utf8 {
419        /// Human-readable error message.
420        message: String,
421        /// Original UTF-8 error (wrapped in Arc for Clone).
422        source: Option<Arc<Utf8Error>>,
423    },
424
425    // =========================================================================
426    // Lexical / Token Errors
427    // =========================================================================
428    /// Unexpected end of input.
429    Eof,
430
431    /// Unexpected character encountered.
432    UnexpectedChar(char),
433
434    /// Unclosed block comment (`/* ... */`).
435    UnclosedBlockComment,
436
437    /// Unclosed line comment (for `RawValue`).
438    UnclosedLineComment,
439
440    /// Invalid escape sequence in string.
441    InvalidEscape(Cow<'static, str>),
442
443    /// Invalid digit for the number's base.
444    InvalidIntegerDigit {
445        /// The invalid digit.
446        digit: char,
447        /// The number base (2, 8, 10, or 16).
448        base: u8,
449    },
450
451    /// Unexpected leading underscore in number.
452    UnderscoreAtBeginning,
453
454    /// Unexpected underscore in float.
455    FloatUnderscore,
456
457    // =========================================================================
458    // Structural / Syntax Errors
459    // =========================================================================
460    /// Expected a specific token or construct.
461    Expected {
462        /// What was expected (e.g., "closing `]`", "comma").
463        expected: Cow<'static, str>,
464        /// Optional context (e.g., "array", "struct field").
465        context: Option<&'static str>,
466    },
467
468    /// Non-whitespace trailing characters after main value.
469    TrailingCharacters,
470
471    /// Expected end of string literal.
472    ExpectedStringEnd,
473
474    /// Invalid identifier.
475    InvalidIdentifier(String),
476
477    /// Suggest using raw identifier syntax.
478    SuggestRawIdentifier(String),
479
480    /// Expected a `ron::value::RawValue`.
481    ExpectedRawValue,
482
483    // =========================================================================
484    // Type Mismatch Errors
485    // =========================================================================
486    /// Expected one type, found another.
487    TypeMismatch {
488        /// The expected type.
489        expected: String,
490        /// The found type.
491        found: String,
492    },
493
494    /// Wrong number of elements.
495    LengthMismatch {
496        /// Expected count description (e.g., "3 elements").
497        expected: String,
498        /// Actual count found.
499        found: usize,
500        /// Optional context (e.g., "tuple", "array").
501        context: Option<&'static str>,
502    },
503
504    // =========================================================================
505    // Struct/Enum Field Errors
506    // =========================================================================
507    /// Missing required struct field.
508    MissingField {
509        /// The name of the missing field.
510        field: Cow<'static, str>,
511        /// The containing struct name, if known.
512        outer: Option<Cow<'static, str>>,
513    },
514
515    /// Unknown struct field.
516    UnknownField {
517        /// The name of the unknown field.
518        field: Cow<'static, str>,
519        /// The list of expected field names.
520        expected: &'static [&'static str],
521        /// The containing struct name, if known.
522        outer: Option<Cow<'static, str>>,
523    },
524
525    /// Duplicate struct field.
526    DuplicateField {
527        /// The name of the duplicated field.
528        field: Cow<'static, str>,
529        /// The containing struct name, if known.
530        outer: Option<Cow<'static, str>>,
531    },
532
533    /// Unknown enum variant.
534    UnknownVariant {
535        /// The name of the unknown variant.
536        variant: Cow<'static, str>,
537        /// The list of expected variant names.
538        expected: &'static [&'static str],
539        /// The enum name, if known.
540        outer: Option<Cow<'static, str>>,
541    },
542
543    /// Expected a different struct name.
544    ExpectedStructName {
545        /// The expected struct name.
546        expected: Cow<'static, str>,
547        /// The found struct name (if any).
548        found: Option<String>,
549    },
550
551    // =========================================================================
552    // Numeric Errors
553    // =========================================================================
554    /// Integer out of bounds for target type.
555    IntegerOutOfBounds {
556        /// The string representation of the out-of-bounds value.
557        value: Cow<'static, str>,
558        /// The target type that couldn't hold the value.
559        target_type: &'static str,
560    },
561
562    // =========================================================================
563    // Extension / Config Errors
564    // =========================================================================
565    /// Unknown RON extension.
566    NoSuchExtension(String),
567
568    /// Exceeded recursion limit.
569    ExceededRecursionLimit,
570
571    /// Too many struct fields for deserialization (max 64).
572    TooManyFields {
573        /// Number of fields found.
574        count: usize,
575        /// Maximum supported.
576        limit: usize,
577    },
578}
579
580impl ErrorKind {
581    /// Get a suggestion for fixing this error, if applicable.
582    #[must_use]
583    pub fn suggestion(&self) -> Option<String> {
584        match self {
585            ErrorKind::UnknownField {
586                field, expected, ..
587            } => find_similar(field, expected),
588            ErrorKind::UnknownVariant {
589                variant, expected, ..
590            } => find_similar(variant, expected),
591            ErrorKind::SuggestRawIdentifier(ident) => Some(alloc::format!("r#{ident}")),
592            _ => None,
593        }
594    }
595}
596
597/// Find a similar string from a list of expected values.
598fn find_similar(needle: &str, haystack: &[&str]) -> Option<String> {
599    let needle_lower = needle.to_lowercase();
600
601    // Find best match using edit distance
602    haystack
603        .iter()
604        .filter_map(|s| {
605            let s_lower = s.to_lowercase();
606            let dist = edit_distance(&needle_lower, &s_lower);
607            // Only consider matches with edit distance <= 2 (or <= half the length for short strings)
608            let max_dist = (needle.len().max(s.len()) / 2).max(2);
609            if dist <= max_dist {
610                Some((*s, dist))
611            } else {
612                None
613            }
614        })
615        .min_by_key(|(_, dist)| *dist)
616        .map(|(s, _)| s.to_string())
617}
618
619/// Calculate edit distance between two strings.
620fn edit_distance(a: &str, b: &str) -> usize {
621    let a_chars: Vec<char> = a.chars().collect();
622    let b_chars: Vec<char> = b.chars().collect();
623    let m = a_chars.len();
624    let n = b_chars.len();
625
626    // Quick checks
627    if m == 0 {
628        return n;
629    }
630    if n == 0 {
631        return m;
632    }
633
634    // Use two-row optimization for space efficiency
635    let mut prev = (0..=n).collect::<Vec<_>>();
636    let mut curr = vec![0; n + 1];
637
638    for i in 1..=m {
639        curr[0] = i;
640        for j in 1..=n {
641            let cost = usize::from(a_chars[i - 1] != b_chars[j - 1]);
642            curr[j] = (prev[j] + 1).min(curr[j - 1] + 1).min(prev[j - 1] + cost);
643        }
644        core::mem::swap(&mut prev, &mut curr);
645    }
646
647    prev[n]
648}
649
650// =============================================================================
651// Display implementations
652// =============================================================================
653
654impl fmt::Display for Error {
655    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
656        // Span prefix (if not synthetic)
657        if !self.0.span.is_synthetic() {
658            write!(f, "{}: ", self.0.span)?;
659        }
660
661        // Path context (reversed - outermost first)
662        if !self.0.path.is_empty() {
663            write!(f, "in ")?;
664            for (i, seg) in self.0.path.iter().rev().enumerate() {
665                if i > 0 {
666                    write!(f, " -> ")?;
667                }
668                write!(f, "{seg}")?;
669            }
670            write!(f, ": ")?;
671        }
672
673        // Error kind message
674        write!(f, "{}", self.0.kind)
675    }
676}
677
678impl fmt::Display for ErrorKind {
679    #[allow(clippy::too_many_lines)]
680    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
681        match self {
682            // General
683            ErrorKind::Message(s) => f.write_str(s),
684            ErrorKind::Fmt => f.write_str("Formatting RON failed"),
685            ErrorKind::Io { message, .. } | ErrorKind::Utf8 { message, .. } => f.write_str(message),
686
687            // Lexical
688            ErrorKind::Eof => f.write_str("Unexpected end of RON"),
689            ErrorKind::UnexpectedChar(c) => write!(f, "Unexpected char {c:?}"),
690            ErrorKind::UnclosedBlockComment => f.write_str("Unclosed block comment"),
691            ErrorKind::UnclosedLineComment => f.write_str(
692                "`ron::value::RawValue` cannot end in unclosed line comment, \
693                try using a block comment or adding a newline",
694            ),
695            ErrorKind::InvalidEscape(s) => f.write_str(s),
696            ErrorKind::InvalidIntegerDigit { digit, base } => {
697                write!(f, "Invalid digit {digit:?} for base {base} integers")
698            }
699            ErrorKind::UnderscoreAtBeginning => {
700                f.write_str("Unexpected leading underscore in a number")
701            }
702            ErrorKind::FloatUnderscore => f.write_str("Unexpected underscore in float"),
703
704            // Structural
705            ErrorKind::Expected {
706                expected,
707                context: Some(ctx),
708            } => write!(f, "Expected {expected} in {ctx}"),
709            ErrorKind::Expected {
710                expected,
711                context: None,
712            } => write!(f, "Expected {expected}"),
713            ErrorKind::TrailingCharacters => f.write_str("Non-whitespace trailing characters"),
714            ErrorKind::ExpectedStringEnd => f.write_str("Expected end of string"),
715            ErrorKind::InvalidIdentifier(s) => write!(f, "Invalid identifier {s:?}"),
716            ErrorKind::SuggestRawIdentifier(s) => write!(
717                f,
718                "Found invalid std identifier {s:?}, try the raw identifier `r#{s}` instead"
719            ),
720            ErrorKind::ExpectedRawValue => f.write_str("Expected a `ron::value::RawValue`"),
721
722            // Type mismatch
723            ErrorKind::TypeMismatch { expected, found } => {
724                write!(f, "expected {expected} but found {found}")
725            }
726            ErrorKind::LengthMismatch {
727                expected,
728                found,
729                context,
730            } => {
731                if let Some(ctx) = context {
732                    write!(f, "{ctx} ")?;
733                }
734                write!(f, "expected {expected} but found ")?;
735                match found {
736                    0 => f.write_str("zero elements"),
737                    1 => f.write_str("one element"),
738                    n => write!(f, "{n} elements"),
739                }
740            }
741
742            // Fields/variants
743            ErrorKind::MissingField { field, outer } => {
744                write!(f, "missing required field {}", Identifier(field))?;
745                if let Some(outer) = outer {
746                    write!(f, " in {}", Identifier(outer))?;
747                }
748                Ok(())
749            }
750            ErrorKind::UnknownField {
751                field,
752                expected,
753                outer,
754            } => {
755                write!(f, "Unknown field {}", Identifier(field))?;
756                if let Some(outer) = outer {
757                    write!(f, " in {}", Identifier(outer))?;
758                }
759                write!(
760                    f,
761                    ", {}",
762                    OneOf {
763                        alts: expected,
764                        none: "fields"
765                    }
766                )
767            }
768            ErrorKind::DuplicateField { field, outer } => {
769                write!(f, "Duplicate field {}", Identifier(field))?;
770                if let Some(outer) = outer {
771                    write!(f, " in {}", Identifier(outer))?;
772                }
773                Ok(())
774            }
775            ErrorKind::UnknownVariant {
776                variant,
777                expected,
778                outer,
779            } => {
780                f.write_str("Unknown ")?;
781                if outer.is_none() {
782                    f.write_str("enum ")?;
783                }
784                write!(f, "variant {}", Identifier(variant))?;
785                if let Some(outer) = outer {
786                    write!(f, " in enum {}", Identifier(outer))?;
787                }
788                write!(
789                    f,
790                    ", {}",
791                    OneOf {
792                        alts: expected,
793                        none: "variants"
794                    }
795                )
796            }
797            ErrorKind::ExpectedStructName { expected, found } => {
798                if let Some(found) = found {
799                    write!(
800                        f,
801                        "Expected struct {} but found {}",
802                        Identifier(expected),
803                        Identifier(found)
804                    )
805                } else {
806                    write!(
807                        f,
808                        "Expected the explicit struct name {}, but none was found",
809                        Identifier(expected)
810                    )
811                }
812            }
813
814            // Numeric
815            ErrorKind::IntegerOutOfBounds { value, target_type } => {
816                write!(f, "Integer {value} is out of bounds for {target_type}")
817            }
818
819            // Config
820            ErrorKind::NoSuchExtension(name) => {
821                write!(f, "No RON extension named {}", Identifier(name))
822            }
823            ErrorKind::ExceededRecursionLimit => f.write_str("Exceeded recursion limit"),
824            ErrorKind::TooManyFields { count, limit } => {
825                write!(f, "Struct has {count} fields but maximum is {limit}")
826            }
827        }
828    }
829}
830
831// =============================================================================
832// PartialEq / Eq (manual implementation)
833// =============================================================================
834
835impl PartialEq for Error {
836    fn eq(&self, other: &Self) -> bool {
837        self.0.kind == other.0.kind && self.0.span == other.0.span && self.0.path == other.0.path
838    }
839}
840
841impl Eq for Error {}
842
843impl PartialEq for ErrorKind {
844    #[allow(clippy::too_many_lines)]
845    fn eq(&self, other: &Self) -> bool {
846        // Compare variants, skipping source fields (not meaningful for equality)
847        match (self, other) {
848            (ErrorKind::Message(a), ErrorKind::Message(b))
849            | (ErrorKind::InvalidIdentifier(a), ErrorKind::InvalidIdentifier(b))
850            | (ErrorKind::SuggestRawIdentifier(a), ErrorKind::SuggestRawIdentifier(b))
851            | (ErrorKind::NoSuchExtension(a), ErrorKind::NoSuchExtension(b))
852            | (ErrorKind::Io { message: a, .. }, ErrorKind::Io { message: b, .. })
853            | (ErrorKind::Utf8 { message: a, .. }, ErrorKind::Utf8 { message: b, .. }) => a == b,
854            (ErrorKind::Fmt, ErrorKind::Fmt)
855            | (ErrorKind::Eof, ErrorKind::Eof)
856            | (ErrorKind::UnclosedBlockComment, ErrorKind::UnclosedBlockComment)
857            | (ErrorKind::UnclosedLineComment, ErrorKind::UnclosedLineComment)
858            | (ErrorKind::UnderscoreAtBeginning, ErrorKind::UnderscoreAtBeginning)
859            | (ErrorKind::FloatUnderscore, ErrorKind::FloatUnderscore)
860            | (ErrorKind::TrailingCharacters, ErrorKind::TrailingCharacters)
861            | (ErrorKind::ExpectedStringEnd, ErrorKind::ExpectedStringEnd)
862            | (ErrorKind::ExpectedRawValue, ErrorKind::ExpectedRawValue)
863            | (ErrorKind::ExceededRecursionLimit, ErrorKind::ExceededRecursionLimit) => true,
864            (ErrorKind::UnexpectedChar(a), ErrorKind::UnexpectedChar(b)) => a == b,
865            (ErrorKind::InvalidEscape(a), ErrorKind::InvalidEscape(b)) => a == b,
866            (
867                ErrorKind::InvalidIntegerDigit {
868                    digit: d1,
869                    base: b1,
870                },
871                ErrorKind::InvalidIntegerDigit {
872                    digit: d2,
873                    base: b2,
874                },
875            ) => d1 == d2 && b1 == b2,
876            (
877                ErrorKind::Expected {
878                    expected: e1,
879                    context: c1,
880                },
881                ErrorKind::Expected {
882                    expected: e2,
883                    context: c2,
884                },
885            ) => e1 == e2 && c1 == c2,
886            (
887                ErrorKind::TypeMismatch {
888                    expected: e1,
889                    found: f1,
890                },
891                ErrorKind::TypeMismatch {
892                    expected: e2,
893                    found: f2,
894                },
895            ) => e1 == e2 && f1 == f2,
896            (
897                ErrorKind::LengthMismatch {
898                    expected: e1,
899                    found: f1,
900                    context: c1,
901                },
902                ErrorKind::LengthMismatch {
903                    expected: e2,
904                    found: f2,
905                    context: c2,
906                },
907            ) => e1 == e2 && f1 == f2 && c1 == c2,
908            (
909                ErrorKind::MissingField {
910                    field: f1,
911                    outer: o1,
912                },
913                ErrorKind::MissingField {
914                    field: f2,
915                    outer: o2,
916                },
917            )
918            | (
919                ErrorKind::DuplicateField {
920                    field: f1,
921                    outer: o1,
922                },
923                ErrorKind::DuplicateField {
924                    field: f2,
925                    outer: o2,
926                },
927            ) => f1 == f2 && o1 == o2,
928            (
929                ErrorKind::UnknownField {
930                    field: f1,
931                    expected: e1,
932                    outer: o1,
933                },
934                ErrorKind::UnknownField {
935                    field: f2,
936                    expected: e2,
937                    outer: o2,
938                },
939            ) => f1 == f2 && e1 == e2 && o1 == o2,
940            (
941                ErrorKind::UnknownVariant {
942                    variant: v1,
943                    expected: e1,
944                    outer: o1,
945                },
946                ErrorKind::UnknownVariant {
947                    variant: v2,
948                    expected: e2,
949                    outer: o2,
950                },
951            ) => v1 == v2 && e1 == e2 && o1 == o2,
952            (
953                ErrorKind::ExpectedStructName {
954                    expected: e1,
955                    found: f1,
956                },
957                ErrorKind::ExpectedStructName {
958                    expected: e2,
959                    found: f2,
960                },
961            ) => e1 == e2 && f1 == f2,
962            (
963                ErrorKind::IntegerOutOfBounds {
964                    value: v1,
965                    target_type: t1,
966                },
967                ErrorKind::IntegerOutOfBounds {
968                    value: v2,
969                    target_type: t2,
970                },
971            ) => v1 == v2 && t1 == t2,
972            (
973                ErrorKind::TooManyFields {
974                    count: c1,
975                    limit: l1,
976                },
977                ErrorKind::TooManyFields {
978                    count: c2,
979                    limit: l2,
980                },
981            ) => c1 == c2 && l1 == l2,
982            _ => false,
983        }
984    }
985}
986
987impl Eq for ErrorKind {}
988
989// =============================================================================
990// std::error::Error implementation
991// =============================================================================
992
993impl core::error::Error for Error {
994    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
995        match &self.0.kind {
996            ErrorKind::Io {
997                source: Some(e), ..
998            } => Some(e.as_ref()),
999            ErrorKind::Utf8 {
1000                source: Some(e), ..
1001            } => Some(e.as_ref()),
1002            _ => None,
1003        }
1004    }
1005}
1006
1007// =============================================================================
1008// From implementations
1009// =============================================================================
1010
1011impl From<Utf8Error> for Error {
1012    fn from(e: Utf8Error) -> Self {
1013        Error::new(ErrorKind::Utf8 {
1014            message: e.to_string(),
1015            source: Some(Arc::new(e)),
1016        })
1017    }
1018}
1019
1020impl From<fmt::Error> for Error {
1021    fn from(_: fmt::Error) -> Self {
1022        Error::new(ErrorKind::Fmt)
1023    }
1024}
1025
1026impl From<io::Error> for Error {
1027    fn from(e: io::Error) -> Self {
1028        Error::new(ErrorKind::Io {
1029            message: e.to_string(),
1030            source: Some(Arc::new(e)),
1031        })
1032    }
1033}
1034
1035// =============================================================================
1036// Helper display types
1037// =============================================================================
1038
1039struct OneOf {
1040    alts: &'static [&'static str],
1041    none: &'static str,
1042}
1043
1044impl fmt::Display for OneOf {
1045    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1046        match self.alts {
1047            [] => write!(f, "there are no {}", self.none),
1048            [a1] => write!(f, "expected {} instead", Identifier(a1)),
1049            [a1, a2] => write!(
1050                f,
1051                "expected either {} or {} instead",
1052                Identifier(a1),
1053                Identifier(a2)
1054            ),
1055            [a1, alts @ .., an] => {
1056                write!(f, "expected one of {}", Identifier(a1))?;
1057                for alt in alts {
1058                    write!(f, ", {}", Identifier(alt))?;
1059                }
1060                write!(f, ", or {} instead", Identifier(an))
1061            }
1062        }
1063    }
1064}
1065
1066struct Identifier<'a>(&'a str);
1067
1068impl fmt::Display for Identifier<'_> {
1069    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1070        if self.0.is_empty() || !self.0.chars().all(is_ident_raw_char) {
1071            return write!(f, "{:?}_[invalid identifier]", self.0);
1072        }
1073
1074        let mut chars = self.0.chars();
1075
1076        if !chars.next().is_some_and(is_ident_first_char) || !chars.all(is_xid_continue) {
1077            write!(f, "`r#{}`", self.0)
1078        } else {
1079            write!(f, "`{}`", self.0)
1080        }
1081    }
1082}
1083
1084// =============================================================================
1085// Tests
1086// =============================================================================
1087
1088#[cfg(test)]
1089mod tests {
1090    use super::*;
1091
1092    #[test]
1093    fn error_is_small() {
1094        // Error should be pointer-sized (8 bytes on 64-bit)
1095        assert_eq!(
1096            core::mem::size_of::<Error>(),
1097            core::mem::size_of::<*const ()>()
1098        );
1099    }
1100
1101    #[test]
1102    fn error_messages() {
1103        check_message(&Error::from(fmt::Error), "Formatting RON failed");
1104        check_message(&Error::new(ErrorKind::Message("custom".into())), "custom");
1105        check_message(&Error::eof(), "Unexpected end of RON");
1106        check_message(
1107            &Error::expected("opening `[`", Span::synthetic()),
1108            "Expected opening `[`",
1109        );
1110        check_message(
1111            &Error::expected_in("comma", "array", Span::synthetic()),
1112            "Expected comma in array",
1113        );
1114        check_message(
1115            &Error::type_mismatch("i32", "String"),
1116            "expected i32 but found String",
1117        );
1118        check_message(
1119            &Error::missing_field("name"),
1120            "missing required field `name`",
1121        );
1122        check_message(
1123            &Error::missing_field_in("name", "Config"),
1124            "missing required field `name` in `Config`",
1125        );
1126        check_message(
1127            &Error::integer_out_of_bounds("256", "u8"),
1128            "Integer 256 is out of bounds for u8",
1129        );
1130    }
1131
1132    fn check_message(err: &Error, expected: &str) {
1133        assert_eq!(alloc::format!("{err}"), expected);
1134    }
1135
1136    #[test]
1137    fn path_context() {
1138        let err = Error::type_mismatch("i32", "String")
1139            .in_element(0)
1140            .in_field("items");
1141        assert_eq!(
1142            err.to_string(),
1143            "in field 'items' -> element 0: expected i32 but found String"
1144        );
1145    }
1146
1147    #[test]
1148    fn span_display() {
1149        let span = Span {
1150            start: Position { line: 3, col: 15 },
1151            end: Position { line: 3, col: 20 },
1152            start_offset: 50,
1153            end_offset: 55,
1154        };
1155        let err = Error::with_span(ErrorKind::Eof, span);
1156        assert_eq!(err.to_string(), "3:15-3:20: Unexpected end of RON");
1157    }
1158
1159    #[test]
1160    fn synthetic_span_hidden() {
1161        let err = Error::type_mismatch("i32", "String");
1162        // Should NOT show span prefix
1163        assert!(!err.to_string().contains(':'));
1164        assert!(err.to_string().starts_with("expected"));
1165    }
1166
1167    #[test]
1168    fn suggestions() {
1169        let err = Error::unknown_field("nme", &["name", "age", "email"]);
1170        assert_eq!(err.suggestion(), Some("name".to_string()));
1171
1172        let err = Error::unknown_variant("Tru", &["True", "False"]);
1173        assert_eq!(err.suggestion(), Some("True".to_string()));
1174
1175        let err = Error::new(ErrorKind::SuggestRawIdentifier("type".into()));
1176        assert_eq!(err.suggestion(), Some("r#type".to_string()));
1177    }
1178
1179    #[test]
1180    fn error_source_chain() {
1181        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
1182        let err = Error::from(io_err);
1183
1184        // Should have source
1185        assert!(core::error::Error::source(&err).is_some());
1186    }
1187
1188    #[test]
1189    fn error_equality() {
1190        let err1 = Error::type_mismatch("i32", "String");
1191        let err2 = Error::type_mismatch("i32", "String");
1192        assert_eq!(err1, err2);
1193
1194        let err3 = Error::type_mismatch("i32", "bool");
1195        assert_ne!(err1, err3);
1196    }
1197}