1use 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#[doc(hidden)]
31pub type ValidationError = Error;
32#[doc(hidden)]
33pub type ValidationErrorKind = ErrorKind;
34
35pub type Result<T, E = Error> = core::result::Result<T, E>;
41
42#[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 #[must_use]
83 pub fn kind(&self) -> &ErrorKind {
84 &self.0.kind
85 }
86
87 #[must_use]
89 pub fn span(&self) -> &Span {
90 &self.0.span
91 }
92
93 #[must_use]
95 pub fn path(&self) -> &[PathSegment] {
96 &self.0.path
97 }
98
99 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[must_use]
189 pub fn in_map_key(mut self) -> Self {
190 self.0.path.push(PathSegment::MapKey);
191 self
192 }
193
194 #[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 #[must_use]
207 pub fn eof() -> Self {
208 Self::new(ErrorKind::Eof)
209 }
210
211 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[must_use]
386 pub fn suggestion(&self) -> Option<String> {
387 self.0.kind.suggestion()
388 }
389}
390
391#[derive(Debug, Clone)]
397#[non_exhaustive]
398pub enum ErrorKind {
399 Message(String),
404
405 Fmt,
407
408 Io {
410 message: String,
412 #[allow(clippy::redundant_allocation)]
414 source: Option<Arc<io::Error>>,
415 },
416
417 Utf8 {
419 message: String,
421 source: Option<Arc<Utf8Error>>,
423 },
424
425 Eof,
430
431 UnexpectedChar(char),
433
434 UnclosedBlockComment,
436
437 UnclosedLineComment,
439
440 InvalidEscape(Cow<'static, str>),
442
443 InvalidIntegerDigit {
445 digit: char,
447 base: u8,
449 },
450
451 UnderscoreAtBeginning,
453
454 FloatUnderscore,
456
457 Expected {
462 expected: Cow<'static, str>,
464 context: Option<&'static str>,
466 },
467
468 TrailingCharacters,
470
471 ExpectedStringEnd,
473
474 InvalidIdentifier(String),
476
477 SuggestRawIdentifier(String),
479
480 ExpectedRawValue,
482
483 TypeMismatch {
488 expected: String,
490 found: String,
492 },
493
494 LengthMismatch {
496 expected: String,
498 found: usize,
500 context: Option<&'static str>,
502 },
503
504 MissingField {
509 field: Cow<'static, str>,
511 outer: Option<Cow<'static, str>>,
513 },
514
515 UnknownField {
517 field: Cow<'static, str>,
519 expected: &'static [&'static str],
521 outer: Option<Cow<'static, str>>,
523 },
524
525 DuplicateField {
527 field: Cow<'static, str>,
529 outer: Option<Cow<'static, str>>,
531 },
532
533 UnknownVariant {
535 variant: Cow<'static, str>,
537 expected: &'static [&'static str],
539 outer: Option<Cow<'static, str>>,
541 },
542
543 ExpectedStructName {
545 expected: Cow<'static, str>,
547 found: Option<String>,
549 },
550
551 IntegerOutOfBounds {
556 value: Cow<'static, str>,
558 target_type: &'static str,
560 },
561
562 NoSuchExtension(String),
567
568 ExceededRecursionLimit,
570
571 TooManyFields {
573 count: usize,
575 limit: usize,
577 },
578}
579
580impl ErrorKind {
581 #[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
597fn find_similar(needle: &str, haystack: &[&str]) -> Option<String> {
599 let needle_lower = needle.to_lowercase();
600
601 haystack
603 .iter()
604 .filter_map(|s| {
605 let s_lower = s.to_lowercase();
606 let dist = edit_distance(&needle_lower, &s_lower);
607 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
619fn 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 if m == 0 {
628 return n;
629 }
630 if n == 0 {
631 return m;
632 }
633
634 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
650impl fmt::Display for Error {
655 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
656 if !self.0.span.is_synthetic() {
658 write!(f, "{}: ", self.0.span)?;
659 }
660
661 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 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 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 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 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 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 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 ErrorKind::IntegerOutOfBounds { value, target_type } => {
816 write!(f, "Integer {value} is out of bounds for {target_type}")
817 }
818
819 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
831impl 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 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
989impl 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
1007impl 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
1035struct 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#[cfg(test)]
1089mod tests {
1090 use super::*;
1091
1092 #[test]
1093 fn error_is_small() {
1094 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 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 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}