1use crate::budget::BudgetBreach;
2use crate::Location;
3use crate::localizer::{ExternalMessageSource, Localizer, DEFAULT_ENGLISH_LOCALIZER};
4use crate::location::Locations;
5use crate::parse_scalars::{
6 parse_int_signed, parse_yaml11_bool, parse_yaml12_float, scalar_is_nullish,
7};
8#[cfg(feature = "garde")]
9use crate::path_map::path_key_from_garde;
10#[cfg(any(feature = "garde", feature = "validator"))]
11use crate::path_map::{PathKey, PathMap, format_path_with_resolved_leaf};
12use crate::tags::SfTag;
13use saphyr_parser::{ScalarStyle, ScanError};
14use serde::de::{self};
15use annotate_snippets::Level;
16use std::borrow::Cow;
17use std::cell::Cell;
18use std::fmt;
19#[cfg(feature = "validator")]
20use validator::{ValidationErrors, ValidationErrorsKind};
21use crate::de_snipped::fmt_snippet_window_offset_or_fallback;
22
23#[cfg(any(feature = "garde", feature = "validator"))]
24use crate::localizer::ExternalMessage;
25
26pub trait MessageFormatter {
78 fn localizer(&self) -> &dyn Localizer {
83 &DEFAULT_ENGLISH_LOCALIZER
84 }
85
86 fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str>;
91}
92
93#[derive(Debug, Default, Clone, Copy)]
105pub struct UserMessageFormatter;
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum SnippetMode {
110 Auto,
112 Off,
114}
115
116#[non_exhaustive]
139#[derive(Clone, Copy)]
140pub struct RenderOptions<'a> {
141 pub formatter: &'a dyn MessageFormatter,
143 pub snippets: SnippetMode,
145}
146
147impl<'a> Default for RenderOptions<'a> {
148 #[inline]
149 fn default() -> Self {
150 static DEFAULT_FMT: crate::message_formatters::DefaultMessageFormatter =
152 crate::message_formatters::DefaultMessageFormatter;
153
154 Self::new(&DEFAULT_FMT)
155 }
156}
157
158impl<'a> RenderOptions<'a> {
159 #[inline]
165 pub fn new(formatter: &'a dyn MessageFormatter) -> Self {
166 Self {
167 formatter,
168 snippets: SnippetMode::Auto,
169 }
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct CroppedRegion {
179 pub text: String,
181 pub start_line: usize,
183 pub end_line: usize,
185}
186
187impl CroppedRegion {
188 fn covers(&self, location: &Location) -> bool {
189 if location == &Location::UNKNOWN {
190 return false;
191 }
192 let line = location.line as usize;
193 self.start_line <= line && line <= self.end_line
194 }
195}
196
197fn line_count_including_trailing_empty_line(text: &str) -> usize {
198 let mut lines = text.split_terminator('\n').count().max(1);
199 if text.ends_with('\n') {
200 lines = lines.saturating_add(1);
201 }
202 lines
203}
204
205#[cfg(any(feature = "garde", feature = "validator"))]
206#[derive(Debug, Clone)]
207pub(crate) struct ValidationIssue {
208 pub(crate) path: PathKey,
209 pub(crate) code: String,
210 pub(crate) message: Option<String>,
211 pub(crate) params: Vec<(String, String)>,
212}
213
214#[cfg(any(feature = "garde", feature = "validator"))]
215impl ValidationIssue {
216 pub(crate) fn display_entry(&self) -> String {
217 if let Some(msg) = &self.message {
218 return msg.clone();
219 }
220
221 if self.params.is_empty() {
222 return self.code.clone();
223 }
224
225 let mut params = String::new();
226 for (i, (k, v)) in self.params.iter().enumerate() {
227 if i > 0 {
228 params.push_str(", ");
229 }
230 params.push_str(k);
231 params.push('=');
232 params.push_str(v);
233 }
234 format!("{} ({params})", self.code)
235 }
236
237 pub(crate) fn display_entry_overridden(
238 &self,
239 l10n: &dyn Localizer,
240 source: ExternalMessageSource,
241 ) -> String {
242 let raw = self.display_entry();
243 let overridden = l10n
244 .override_external_message(ExternalMessage {
245 source,
246 original: raw.as_str(),
247 code: Some(self.code.as_str()),
248 params: &self.params,
249 })
250 .unwrap_or(Cow::Borrowed(raw.as_str()));
251 overridden.into_owned()
252 }
253}
254
255thread_local! {
263 static MISSING_FIELD_FALLBACK: Cell<Option<Location>> = const { Cell::new(None) };
264}
265
266pub(crate) struct MissingFieldLocationGuard {
269 prev: Option<Location>,
270}
271
272impl MissingFieldLocationGuard {
273 pub(crate) fn new(location: Location) -> Self {
274 let prev = MISSING_FIELD_FALLBACK.with(|c| c.replace(Some(location)));
275 Self { prev }
276 }
277
278 pub(crate) fn replace_location(&mut self, location: Location) {
280 MISSING_FIELD_FALLBACK.with(|c| c.set(Some(location)));
281 }
282}
283
284impl Drop for MissingFieldLocationGuard {
285 fn drop(&mut self) {
286 MISSING_FIELD_FALLBACK.with(|c| c.set(self.prev));
287 }
288}
289
290#[non_exhaustive]
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub enum TransformReason {
297 EscapeSequence,
299 LineFolding,
301 MultiLineNormalization,
303 BlockScalarProcessing,
305 SingleQuoteEscape,
307 InputNotBorrowable,
311
312 ParserReturnedOwned,
318}
319
320impl fmt::Display for TransformReason {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 match self {
323 TransformReason::EscapeSequence => write!(f, "escape sequence processing"),
324 TransformReason::LineFolding => write!(f, "line folding"),
325 TransformReason::MultiLineNormalization => write!(f, "multi-line whitespace normalization"),
326 TransformReason::BlockScalarProcessing => write!(f, "block scalar processing"),
327 TransformReason::SingleQuoteEscape => write!(f, "single-quote escape processing"),
328 TransformReason::InputNotBorrowable => write!(f, "input is not available for borrowing"),
329 TransformReason::ParserReturnedOwned => write!(f, "parser returned an owned string"),
330 }
331 }
332}
333
334#[non_exhaustive]
336#[derive(Debug)]
337pub enum Error {
338 Message {
340 msg: String,
341 location: Location,
342 },
343
344 ExternalMessage {
349 source: ExternalMessageSource,
350 msg: String,
351 code: Option<String>,
353 params: Vec<(String, String)>,
355 location: Location,
356 },
357 Eof {
359 location: Location,
360 },
361 MultipleDocuments {
366 hint: &'static str,
368 location: Location,
369 },
370 Unexpected {
372 expected: &'static str,
373 location: Location,
374 },
375
376 MergeValueNotMapOrSeqOfMaps {
378 location: Location,
379 },
380
381 InvalidBinaryBase64 {
383 location: Location,
384 },
385
386 BinaryNotUtf8 {
388 location: Location,
389 },
390
391 TaggedScalarCannotDeserializeIntoString {
393 location: Location,
394 },
395
396 UnexpectedSequenceEnd {
398 location: Location,
399 },
400
401 UnexpectedMappingEnd {
403 location: Location,
404 },
405
406 InvalidBooleanStrict {
408 location: Location,
409 },
410
411 InvalidCharNull {
413 location: Location,
414 },
415
416 InvalidCharNotSingleScalar {
418 location: Location,
419 },
420
421 NullIntoString {
423 location: Location,
424 },
425
426 BytesNotSupportedMissingBinaryTag {
428 location: Location,
429 },
430
431 UnexpectedValueForUnit {
433 location: Location,
434 },
435
436 ExpectedEmptyMappingForUnitStruct {
438 location: Location,
439 },
440
441 UnexpectedContainerEndWhileSkippingNode {
443 location: Location,
444 },
445
446 InternalSeedReusedForMapKey {
448 location: Location,
449 },
450
451 ValueRequestedBeforeKey {
453 location: Location,
454 },
455
456 ExpectedStringKeyForExternallyTaggedEnum {
458 location: Location,
459 },
460
461 ExternallyTaggedEnumExpectedScalarOrMapping {
463 location: Location,
464 },
465
466 UnexpectedValueForUnitEnumVariant {
468 location: Location,
469 },
470
471 InvalidUtf8Input,
473
474 AliasReplayCounterOverflow {
476 location: Location,
477 },
478
479 AliasReplayLimitExceeded {
481 total_replayed_events: usize,
482 max_total_replayed_events: usize,
483 location: Location,
484 },
485
486 AliasExpansionLimitExceeded {
488 anchor_id: usize,
489 expansions: usize,
490 max_expansions_per_anchor: usize,
491 location: Location,
492 },
493
494 AliasReplayStackDepthExceeded {
496 depth: usize,
497 max_depth: usize,
498 location: Location,
499 },
500
501 FoldedBlockScalarMustIndentContent {
503 location: Location,
504 },
505
506 InternalDepthUnderflow {
508 location: Location,
509 },
510
511 InternalRecursionStackEmpty {
513 location: Location,
514 },
515
516 RecursiveReferencesRequireWeakTypes {
518 location: Location,
519 },
520
521 InvalidScalar {
523 ty: &'static str,
524 location: Location,
525 },
526
527 SerdeInvalidType {
529 unexpected: String,
530 expected: String,
531 location: Location,
532 },
533
534 SerdeInvalidValue {
536 unexpected: String,
537 expected: String,
538 location: Location,
539 },
540
541 SerdeUnknownVariant {
543 variant: String,
544 expected: Vec<&'static str>,
545 location: Location,
546 },
547
548 SerdeUnknownField {
550 field: String,
551 expected: Vec<&'static str>,
552 location: Location,
553 },
554
555 SerdeMissingField {
557 field: &'static str,
558 location: Location,
559 },
560
561 UnexpectedContainerEndWhileReadingKeyNode {
565 location: Location,
566 },
567
568 DuplicateMappingKey {
572 key: Option<String>,
573 location: Location,
574 },
575
576 TaggedEnumMismatch {
578 tagged: String,
579 target: &'static str,
580 location: Location,
581 },
582
583 SerdeVariantId {
585 msg: String,
586 location: Location,
587 },
588
589 ExpectedMappingEndAfterEnumVariantValue {
591 location: Location,
592 },
593 ContainerEndMismatch {
594 location: Location,
595 },
596 UnknownAnchor { location: Location },
598 AliasError {
603 msg: String,
604 locations: Locations,
605 },
606 HookError {
609 msg: String,
610 location: Location,
611 },
612 Budget {
614 breach: BudgetBreach,
615 location: Location,
616 },
617 IOError {
619 cause: std::io::Error,
620 },
621 QuotingRequired {
624 value: String, location: Location,
626 },
627
628 CannotBorrowTransformedString {
634 reason: TransformReason,
636 location: Location,
637 },
638
639 WithSnippet {
641 regions: Vec<CroppedRegion>,
646 crop_radius: usize,
647 error: Box<Error>,
648 },
649
650 #[cfg(feature = "garde")]
652 ValidationError {
653 report: garde::Report,
654 locations: PathMap,
655 },
656
657 #[cfg(feature = "garde")]
659 ValidationErrors {
660 errors: Vec<Error>,
661 },
662
663 #[cfg(feature = "validator")]
665 ValidatorError {
666 errors: ValidationErrors,
667 locations: PathMap,
668 },
669
670 #[cfg(feature = "validator")]
672 ValidatorErrors {
673 errors: Vec<Error>,
674 },
675}
676
677impl Error {
678 #[cold]
679 #[inline(never)]
680 pub(crate) fn with_snippet(self, text: &str, crop_radius: usize) -> Self {
681 let inner = match self {
684 Error::WithSnippet { error, .. } => *error,
685 other => other,
686 };
687
688 let text = text.strip_prefix('\u{FEFF}').unwrap_or(text);
690
691 fn push_region_for_location(
692 regions: &mut Vec<CroppedRegion>,
693 text: &str,
694 location: &Location,
695 mapping: crate::de_snipped::LineMapping,
696 crop_radius: usize,
697 ) {
698 if crop_radius == 0 || *location == Location::UNKNOWN {
699 return;
700 }
701 let (cropped, start_line) =
702 crate::de_snipped::crop_source_window(text, location, mapping, crop_radius);
703 if cropped.is_empty() {
704 return;
705 }
706 let lines = line_count_including_trailing_empty_line(cropped.as_str());
707 let end_line = start_line.saturating_add(lines.saturating_sub(1));
708 regions.push(CroppedRegion {
709 text: cropped,
710 start_line,
711 end_line,
712 });
713 }
714
715 let mut regions: Vec<CroppedRegion> = Vec::new();
716 let mapping = crate::de_snipped::LineMapping::Identity;
717
718 #[cfg(feature = "garde")]
721 if let Error::ValidationError { report, locations } = &inner {
722 for (path, _entry) in report.iter() {
723 let key = path_key_from_garde(path);
724 let (locs, _) = locations.search(&key).unwrap_or((Locations::UNKNOWN, String::new()));
725 push_region_for_location(&mut regions, text, &locs.reference_location, mapping, crop_radius);
726 if locs.defined_location != locs.reference_location {
727 push_region_for_location(&mut regions, text, &locs.defined_location, mapping, crop_radius);
728 }
729 }
730 }
731 #[cfg(feature = "validator")]
732 if let Error::ValidatorError { errors, locations } = &inner {
733 for issue in collect_validator_issues(errors) {
734 let (locs, _) = locations
735 .search(&issue.path)
736 .unwrap_or((Locations::UNKNOWN, String::new()));
737 push_region_for_location(&mut regions, text, &locs.reference_location, mapping, crop_radius);
738 if locs.defined_location != locs.reference_location {
739 push_region_for_location(&mut regions, text, &locs.defined_location, mapping, crop_radius);
740 }
741 }
742 }
743
744 if regions.is_empty() {
747 if let Some(locs) = inner.locations() {
748 push_region_for_location(&mut regions, text, &locs.reference_location, mapping, crop_radius);
749 if locs.defined_location != locs.reference_location {
750 push_region_for_location(&mut regions, text, &locs.defined_location, mapping, crop_radius);
751 }
752 } else if let Some(loc) = inner.location() {
753 push_region_for_location(&mut regions, text, &loc, mapping, crop_radius);
754 }
755 }
756
757 Error::WithSnippet {
758 regions,
759 crop_radius,
760 error: Box::new(inner),
761 }
762 }
763
764 #[cold]
770 #[inline(never)]
771 pub(crate) fn with_snippet_offset(
772 self,
773 text: &str,
774 start_line: usize,
775 crop_radius: usize,
776 ) -> Self {
777 let inner = match self {
778 Error::WithSnippet { error, .. } => *error,
779 other => other,
780 };
781
782 let text = text.strip_prefix('\u{FEFF}').unwrap_or(text);
784
785 fn push_region_for_location(
786 regions: &mut Vec<CroppedRegion>,
787 text: &str,
788 location: &Location,
789 mapping: crate::de_snipped::LineMapping,
790 crop_radius: usize,
791 ) {
792 if crop_radius == 0 || *location == Location::UNKNOWN {
793 return;
794 }
795 let (cropped, region_start_line) =
796 crate::de_snipped::crop_source_window(text, location, mapping, crop_radius);
797 if cropped.is_empty() {
798 return;
799 }
800 let lines = line_count_including_trailing_empty_line(cropped.as_str());
801 let end_line = region_start_line.saturating_add(lines.saturating_sub(1));
802 regions.push(CroppedRegion {
803 text: cropped,
804 start_line: region_start_line,
805 end_line,
806 });
807 }
808
809 let mut regions: Vec<CroppedRegion> = Vec::new();
810 let mapping = crate::de_snipped::LineMapping::Offset { start_line };
811
812 #[cfg(feature = "garde")]
813 if let Error::ValidationError { report, locations } = &inner {
814 for (path, _entry) in report.iter() {
815 let key = path_key_from_garde(path);
816 let (locs, _) = locations.search(&key).unwrap_or((Locations::UNKNOWN, String::new()));
817 push_region_for_location(&mut regions, text, &locs.reference_location, mapping, crop_radius);
818 if locs.defined_location != locs.reference_location {
819 push_region_for_location(&mut regions, text, &locs.defined_location, mapping, crop_radius);
820 }
821 }
822 }
823 #[cfg(feature = "validator")]
824 if let Error::ValidatorError { errors, locations } = &inner {
825 for issue in collect_validator_issues(errors) {
826 let (locs, _) = locations
827 .search(&issue.path)
828 .unwrap_or((Locations::UNKNOWN, String::new()));
829 push_region_for_location(&mut regions, text, &locs.reference_location, mapping, crop_radius);
830 if locs.defined_location != locs.reference_location {
831 push_region_for_location(&mut regions, text, &locs.defined_location, mapping, crop_radius);
832 }
833 }
834 }
835
836 if regions.is_empty() {
837 if let Some(locs) = inner.locations() {
838 push_region_for_location(&mut regions, text, &locs.reference_location, mapping, crop_radius);
839 if locs.defined_location != locs.reference_location {
840 push_region_for_location(&mut regions, text, &locs.defined_location, mapping, crop_radius);
841 }
842 } else if let Some(loc) = inner.location() {
843 push_region_for_location(&mut regions, text, &loc, mapping, crop_radius);
844 }
845 }
846
847 Error::WithSnippet {
848 regions,
849 crop_radius,
850 error: Box::new(inner),
851 }
852 }
853
854 pub fn without_snippet(&self) -> &Self {
856 match self {
857 Error::WithSnippet { error, .. } => error,
858 other => other,
859 }
860 }
861
862 pub fn render(&self) -> String {
868 self.render_with_options(RenderOptions::default())
869 }
870
871 pub fn render_with_formatter(&self, formatter: &dyn MessageFormatter) -> String {
873 self.render_with_options(RenderOptions {
874 formatter,
875 snippets: SnippetMode::Auto,
876 })
877 }
878
879 pub fn render_with_options(&self, options: RenderOptions<'_>) -> String {
881 struct RenderDisplay<'a> {
882 err: &'a Error,
883 options: RenderOptions<'a>,
884 }
885
886 impl fmt::Display for RenderDisplay<'_> {
887 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
888 fmt_error_rendered(f, self.err, self.options)
889 }
890 }
891
892 RenderDisplay {
893 err: self,
894 options,
895 }
896 .to_string()
897 }
898
899 #[cold]
910 #[inline(never)]
911 pub(crate) fn msg<S: Into<String>>(s: S) -> Self {
912 Error::Message {
913 msg: s.into(),
914 location: Location::UNKNOWN,
915 }
916 }
917
918 #[cold]
922 #[inline(never)]
923 pub(crate) fn quoting_required(value: &str) -> Self {
924 let location = Location::UNKNOWN;
927 let value = if parse_yaml12_float::<f64>(value, location, SfTag::None, false).is_ok()
928 || parse_int_signed::<i128>(value, "i128", location, false).is_ok()
929 || parse_yaml11_bool(value).is_ok()
930 || scalar_is_nullish(value, &ScalarStyle::Plain)
931 {
932 value.to_string()
933 } else {
934 String::new()
935 };
936 Error::QuotingRequired { value, location }
937 }
938
939 #[cold]
950 #[inline(never)]
951 pub(crate) fn unexpected(what: &'static str) -> Self {
952 Error::Unexpected {
953 expected: what,
954 location: Location::UNKNOWN,
955 }
956 }
957
958 #[cold]
963 #[inline(never)]
964 pub(crate) fn eof() -> Self {
965 Error::Eof {
966 location: Location::UNKNOWN,
967 }
968 }
969
970 #[cold]
971 #[inline(never)]
972 pub(crate) fn multiple_documents(hint: &'static str) -> Self {
973 Error::MultipleDocuments {
974 hint,
975 location: Location::UNKNOWN,
976 }
977 }
978
979 #[cold]
984 #[inline(never)]
985 pub(crate) fn unknown_anchor() -> Self {
986 Error::UnknownAnchor {
987 location: Location::UNKNOWN,
988 }
989 }
990
991 #[cold]
996 #[inline(never)]
997 pub fn cannot_borrow_transformed(reason: TransformReason) -> Self {
998 Error::CannotBorrowTransformedString {
999 reason,
1000 location: Location::UNKNOWN,
1001 }
1002 }
1003
1004 #[cold]
1015 #[inline(never)]
1016 pub(crate) fn with_location(mut self, set_location: Location) -> Self {
1017 match &mut self {
1018 Error::Message { location, .. }
1019 | Error::ExternalMessage { location, .. }
1020 | Error::Eof { location }
1021 | Error::MultipleDocuments { location, .. }
1022 | Error::Unexpected { location, .. }
1023 | Error::MergeValueNotMapOrSeqOfMaps { location }
1024 | Error::InvalidBinaryBase64 { location }
1025 | Error::BinaryNotUtf8 { location }
1026 | Error::TaggedScalarCannotDeserializeIntoString { location }
1027 | Error::UnexpectedSequenceEnd { location }
1028 | Error::UnexpectedMappingEnd { location }
1029 | Error::InvalidBooleanStrict { location }
1030 | Error::InvalidCharNull { location }
1031 | Error::InvalidCharNotSingleScalar { location }
1032 | Error::NullIntoString { location }
1033 | Error::BytesNotSupportedMissingBinaryTag { location }
1034 | Error::UnexpectedValueForUnit { location }
1035 | Error::ExpectedEmptyMappingForUnitStruct { location }
1036 | Error::UnexpectedContainerEndWhileSkippingNode { location }
1037 | Error::InternalSeedReusedForMapKey { location }
1038 | Error::ValueRequestedBeforeKey { location }
1039 | Error::ExpectedStringKeyForExternallyTaggedEnum { location }
1040 | Error::ExternallyTaggedEnumExpectedScalarOrMapping { location }
1041 | Error::UnexpectedValueForUnitEnumVariant { location }
1042 | Error::AliasReplayCounterOverflow { location }
1043 | Error::AliasReplayLimitExceeded { location, .. }
1044 | Error::AliasExpansionLimitExceeded { location, .. }
1045 | Error::AliasReplayStackDepthExceeded { location, .. }
1046 | Error::FoldedBlockScalarMustIndentContent { location }
1047 | Error::InternalDepthUnderflow { location }
1048 | Error::InternalRecursionStackEmpty { location }
1049 | Error::RecursiveReferencesRequireWeakTypes { location }
1050 | Error::InvalidScalar { location, .. }
1051 | Error::SerdeInvalidType { location, .. }
1052 | Error::SerdeInvalidValue { location, .. }
1053 | Error::SerdeUnknownVariant { location, .. }
1054 | Error::SerdeUnknownField { location, .. }
1055 | Error::SerdeMissingField { location, .. }
1056 | Error::UnexpectedContainerEndWhileReadingKeyNode { location }
1057 | Error::DuplicateMappingKey { location, .. }
1058 | Error::TaggedEnumMismatch { location, .. }
1059 | Error::SerdeVariantId { location, .. }
1060 | Error::ExpectedMappingEndAfterEnumVariantValue { location }
1061 | Error::HookError { location, .. }
1062 | Error::ContainerEndMismatch { location, .. }
1063 | Error::UnknownAnchor { location, .. }
1064 | Error::QuotingRequired { location, .. }
1065 | Error::Budget { location, .. }
1066 | Error::CannotBorrowTransformedString { location, .. } => {
1067 *location = set_location;
1068 }
1069 Error::InvalidUtf8Input => {}
1070 Error::IOError { .. } => {} Error::AliasError { .. } => {
1072 }
1074 Error::WithSnippet { error, .. } => {
1075 let inner = *std::mem::replace(error, Box::new(Error::eof()));
1076 **error = inner.with_location(set_location);
1077 }
1078 #[cfg(feature = "garde")]
1079 Error::ValidationError { .. } => {
1080 }
1082 #[cfg(feature = "garde")]
1083 Error::ValidationErrors { .. } => {
1084 }
1086 #[cfg(feature = "validator")]
1087 Error::ValidatorError { .. } => {
1088 }
1090 #[cfg(feature = "validator")]
1091 Error::ValidatorErrors { .. } => {
1092 }
1094 }
1095 self
1096 }
1097
1098 pub fn location(&self) -> Option<Location> {
1106 match self {
1107 Error::Message { location, .. }
1108 | Error::ExternalMessage { location, .. }
1109 | Error::Eof { location }
1110 | Error::MultipleDocuments { location, .. }
1111 | Error::Unexpected { location, .. }
1112 | Error::MergeValueNotMapOrSeqOfMaps { location }
1113 | Error::InvalidBinaryBase64 { location }
1114 | Error::BinaryNotUtf8 { location }
1115 | Error::TaggedScalarCannotDeserializeIntoString { location }
1116 | Error::UnexpectedSequenceEnd { location }
1117 | Error::UnexpectedMappingEnd { location }
1118 | Error::InvalidBooleanStrict { location }
1119 | Error::InvalidCharNull { location }
1120 | Error::InvalidCharNotSingleScalar { location }
1121 | Error::NullIntoString { location }
1122 | Error::BytesNotSupportedMissingBinaryTag { location }
1123 | Error::UnexpectedValueForUnit { location }
1124 | Error::ExpectedEmptyMappingForUnitStruct { location }
1125 | Error::UnexpectedContainerEndWhileSkippingNode { location }
1126 | Error::InternalSeedReusedForMapKey { location }
1127 | Error::ValueRequestedBeforeKey { location }
1128 | Error::ExpectedStringKeyForExternallyTaggedEnum { location }
1129 | Error::ExternallyTaggedEnumExpectedScalarOrMapping { location }
1130 | Error::UnexpectedValueForUnitEnumVariant { location }
1131 | Error::AliasReplayCounterOverflow { location }
1132 | Error::AliasReplayLimitExceeded { location, .. }
1133 | Error::AliasExpansionLimitExceeded { location, .. }
1134 | Error::AliasReplayStackDepthExceeded { location, .. }
1135 | Error::FoldedBlockScalarMustIndentContent { location }
1136 | Error::InternalDepthUnderflow { location }
1137 | Error::InternalRecursionStackEmpty { location }
1138 | Error::RecursiveReferencesRequireWeakTypes { location }
1139 | Error::InvalidScalar { location, .. }
1140 | Error::SerdeInvalidType { location, .. }
1141 | Error::SerdeInvalidValue { location, .. }
1142 | Error::SerdeUnknownVariant { location, .. }
1143 | Error::SerdeUnknownField { location, .. }
1144 | Error::SerdeMissingField { location, .. }
1145 | Error::UnexpectedContainerEndWhileReadingKeyNode { location }
1146 | Error::DuplicateMappingKey { location, .. }
1147 | Error::TaggedEnumMismatch { location, .. }
1148 | Error::SerdeVariantId { location, .. }
1149 | Error::ExpectedMappingEndAfterEnumVariantValue { location }
1150 | Error::HookError { location, .. }
1151 | Error::ContainerEndMismatch { location, .. }
1152 | Error::UnknownAnchor { location, .. }
1153 | Error::QuotingRequired { location, .. }
1154 | Error::Budget { location, .. }
1155 | Error::CannotBorrowTransformedString { location, .. } => {
1156 if location != &Location::UNKNOWN {
1157 Some(*location)
1158 } else {
1159 None
1160 }
1161 }
1162 Error::InvalidUtf8Input => None,
1163 Error::IOError { cause: _ } => None,
1164 Error::AliasError { locations, .. } => Locations::primary_location(*locations),
1165 Error::WithSnippet { error, .. } => error.location(),
1166 #[cfg(feature = "garde")]
1167 Error::ValidationError { report, locations } => report.iter().next().and_then(|(path, _)| {
1168 let key = path_key_from_garde(path);
1169 let (locs, _) = locations.search(&key)?;
1170 let loc = if locs.reference_location != Location::UNKNOWN {
1171 locs.reference_location
1172 } else {
1173 locs.defined_location
1174 };
1175 if loc != Location::UNKNOWN { Some(loc) } else { None }
1176 }),
1177 #[cfg(feature = "garde")]
1178 Error::ValidationErrors { errors } => errors.iter().find_map(|e| e.location()),
1179 #[cfg(feature = "validator")]
1180 Error::ValidatorError { errors, locations } => collect_validator_issues(errors).first().and_then(|issue| {
1181 let (locs, _) = locations.search(&issue.path)?;
1182 let loc = if locs.reference_location != Location::UNKNOWN {
1183 locs.reference_location
1184 } else {
1185 locs.defined_location
1186 };
1187 if loc != Location::UNKNOWN { Some(loc) } else { None }
1188 }),
1189 #[cfg(feature = "validator")]
1190 Error::ValidatorErrors { errors } => errors.iter().find_map(|e| e.location()),
1191 }
1192 }
1193
1194 pub fn locations(&self) -> Option<Locations> {
1204 match self {
1205 Error::Message { location, .. }
1206 | Error::ExternalMessage { location, .. }
1207 | Error::Eof { location }
1208 | Error::MultipleDocuments { location, .. }
1209 | Error::Unexpected { location, .. }
1210 | Error::MergeValueNotMapOrSeqOfMaps { location }
1211 | Error::InvalidBinaryBase64 { location }
1212 | Error::BinaryNotUtf8 { location }
1213 | Error::TaggedScalarCannotDeserializeIntoString { location }
1214 | Error::UnexpectedSequenceEnd { location }
1215 | Error::UnexpectedMappingEnd { location }
1216 | Error::InvalidBooleanStrict { location }
1217 | Error::InvalidCharNull { location }
1218 | Error::InvalidCharNotSingleScalar { location }
1219 | Error::NullIntoString { location }
1220 | Error::BytesNotSupportedMissingBinaryTag { location }
1221 | Error::UnexpectedValueForUnit { location }
1222 | Error::ExpectedEmptyMappingForUnitStruct { location }
1223 | Error::UnexpectedContainerEndWhileSkippingNode { location }
1224 | Error::InternalSeedReusedForMapKey { location }
1225 | Error::ValueRequestedBeforeKey { location }
1226 | Error::ExpectedStringKeyForExternallyTaggedEnum { location }
1227 | Error::ExternallyTaggedEnumExpectedScalarOrMapping { location }
1228 | Error::UnexpectedValueForUnitEnumVariant { location }
1229 | Error::AliasReplayCounterOverflow { location }
1230 | Error::AliasReplayLimitExceeded { location, .. }
1231 | Error::AliasExpansionLimitExceeded { location, .. }
1232 | Error::AliasReplayStackDepthExceeded { location, .. }
1233 | Error::FoldedBlockScalarMustIndentContent { location }
1234 | Error::InternalDepthUnderflow { location }
1235 | Error::InternalRecursionStackEmpty { location }
1236 | Error::RecursiveReferencesRequireWeakTypes { location }
1237 | Error::InvalidScalar { location, .. }
1238 | Error::SerdeInvalidType { location, .. }
1239 | Error::SerdeInvalidValue { location, .. }
1240 | Error::SerdeUnknownVariant { location, .. }
1241 | Error::SerdeUnknownField { location, .. }
1242 | Error::SerdeMissingField { location, .. }
1243 | Error::UnexpectedContainerEndWhileReadingKeyNode { location }
1244 | Error::DuplicateMappingKey { location, .. }
1245 | Error::TaggedEnumMismatch { location, .. }
1246 | Error::SerdeVariantId { location, .. }
1247 | Error::ExpectedMappingEndAfterEnumVariantValue { location }
1248 | Error::HookError { location, .. }
1249 | Error::ContainerEndMismatch { location, .. }
1250 | Error::UnknownAnchor { location, .. }
1251 | Error::QuotingRequired { location, .. }
1252 | Error::Budget { location, .. }
1253 | Error::CannotBorrowTransformedString { location, .. } => Locations::same(location),
1254 Error::InvalidUtf8Input => None,
1255 Error::IOError { .. } => None,
1256 Error::AliasError { locations, .. } => Some(*locations),
1257 Error::WithSnippet { error, .. } => error.locations(),
1258 #[cfg(feature = "garde")]
1259 Error::ValidationError { report, locations } => {
1260 report.iter().next().and_then(|(path, _)| {
1261 let key = path_key_from_garde(path);
1262 search_locations_with_ancestor_fallback(locations, &key)
1263 })
1264 }
1265 #[cfg(feature = "garde")]
1266 Error::ValidationErrors { errors } => errors.first().and_then(Error::locations),
1267 #[cfg(feature = "validator")]
1268 Error::ValidatorError { errors, locations } => collect_validator_issues(errors)
1269 .first()
1270 .and_then(|issue| locations.search(&issue.path).map(|(locs, _)| locs)),
1271 #[cfg(feature = "validator")]
1272 Error::ValidatorErrors { errors } => errors.first().and_then(Error::locations),
1273 }
1274 }
1275
1276 #[cold]
1281 #[inline(never)]
1282 pub(crate) fn from_scan_error(err: ScanError) -> Self {
1283 use crate::location::SpanIndex;
1284 let mark = err.marker();
1285 let location =
1286 Location::new(mark.line(), mark.col() + 1).with_span(crate::location::Span {
1287 offset: mark.index() as SpanIndex,
1288 len: 1,
1289 byte_info: (0, 0),
1290 });
1291
1292 let info = err.info();
1300 if info.to_ascii_lowercase().contains("unknown anchor") {
1301 return Error::UnknownAnchor { location };
1302 }
1303
1304 Error::ExternalMessage {
1305 source: ExternalMessageSource::SaphyrParser,
1306 msg: info.to_owned(),
1307 code: None,
1308 params: Vec::new(),
1309 location,
1310 }
1311 }
1312}
1313
1314fn fmt_error_plain_with_formatter(
1315 f: &mut fmt::Formatter<'_>,
1316 err: &Error,
1317 formatter: &dyn MessageFormatter,
1318) -> fmt::Result {
1319 let err = err.without_snippet();
1320
1321 let msg = formatter.format_message(err);
1322
1323 #[cfg(feature = "garde")]
1327 if matches!(err, Error::ValidationError { .. }) {
1328 return write!(f, "{msg}");
1329 }
1330 #[cfg(feature = "validator")]
1331 if matches!(err, Error::ValidatorError { .. }) {
1332 return write!(f, "{msg}");
1333 }
1334
1335 if let Some(loc) = err.location() {
1336 fmt_with_location(f, formatter.localizer(), msg.as_ref(), &loc)?;
1337 } else {
1338 write!(f, "{msg}")?;
1339 }
1340
1341 #[cfg(feature = "garde")]
1342 if let Error::ValidationErrors { errors } = err {
1343 for err in errors {
1344 writeln!(f)?;
1345 writeln!(f)?;
1346 fmt_error_plain_with_formatter(f, err, formatter)?;
1347 }
1348 }
1349
1350 #[cfg(feature = "validator")]
1351 if let Error::ValidatorErrors { errors } = err {
1352 for err in errors {
1353 writeln!(f)?;
1354 writeln!(f)?;
1355 fmt_error_plain_with_formatter(f, err, formatter)?;
1356 }
1357 }
1358
1359 Ok(())
1360}
1361
1362fn pick_cropped_region<'a>(
1363 regions: &'a [CroppedRegion],
1364 location: &Location,
1365) -> Option<&'a CroppedRegion> {
1366 regions
1367 .iter()
1368 .find(|r| r.covers(location))
1369 .or_else(|| regions.first())
1370}
1371
1372fn fmt_error_rendered(
1373 f: &mut fmt::Formatter<'_>,
1374 err: &Error,
1375 options: RenderOptions<'_>,
1376) -> fmt::Result {
1377 if options.snippets == SnippetMode::Off {
1378 return fmt_error_plain_with_formatter(f, err, options.formatter);
1379 }
1380
1381 match err {
1382 #[cfg(feature = "garde")]
1383 Error::ValidationErrors { errors } => {
1384 let msg = options.formatter.format_message(err);
1385 if !msg.is_empty() {
1386 writeln!(f, "{}", msg)?;
1387 }
1388 let mut first = true;
1389 for err in errors {
1390 if !first {
1391 writeln!(f)?;
1392 writeln!(f)?;
1393 }
1394 first = false;
1395 fmt_error_rendered(f, err, options)?;
1396 }
1397 Ok(())
1398 }
1399
1400 #[cfg(feature = "validator")]
1401 Error::ValidatorErrors { errors } => {
1402 let msg = options.formatter.format_message(err);
1403 if !msg.is_empty() {
1404 writeln!(f, "{}", msg)?;
1405 }
1406 let mut first = true;
1407 for err in errors {
1408 if !first {
1409 writeln!(f)?;
1410 writeln!(f)?;
1411 }
1412 first = false;
1413 fmt_error_rendered(f, err, options)?;
1414 }
1415 Ok(())
1416 }
1417
1418 Error::WithSnippet {
1419 regions,
1420 crop_radius,
1421 error,
1422 } => {
1423 if *crop_radius == 0 {
1424 return fmt_error_plain_with_formatter(f, error, options.formatter);
1426 }
1427
1428 if regions.is_empty() {
1429 return fmt_error_plain_with_formatter(f, error, options.formatter);
1430 }
1431
1432 #[cfg(feature = "garde")]
1435 if let Error::ValidationError { report, locations } = error.as_ref() {
1436 return fmt_validation_error_with_snippets_offset(
1437 f,
1438 options.formatter.localizer(),
1439 report,
1440 locations,
1441 regions,
1442 *crop_radius,
1443 );
1444 }
1445 #[cfg(feature = "garde")]
1446 if let Error::ValidationErrors { errors } = error.as_ref() {
1447 let msg = options.formatter.format_message(error);
1448 if !msg.is_empty() {
1449 writeln!(f, "{}", msg)?;
1450 }
1451 let mut first = true;
1452 for err in errors {
1453 if !first {
1454 writeln!(f)?;
1455 writeln!(f)?;
1456 }
1457 first = false;
1458 fmt_error_with_snippets_offset(
1459 f,
1460 err,
1461 regions,
1462 *crop_radius,
1463 options.formatter,
1464 )?;
1465 }
1466 return Ok(());
1467 }
1468
1469 #[cfg(feature = "validator")]
1470 if let Error::ValidatorError { errors, locations } = error.as_ref() {
1471 return fmt_validator_error_with_snippets_offset(
1472 f,
1473 options.formatter.localizer(),
1474 errors,
1475 locations,
1476 regions,
1477 *crop_radius,
1478 );
1479 }
1480 #[cfg(feature = "validator")]
1481 if let Error::ValidatorErrors { errors } = error.as_ref() {
1482 let msg = options.formatter.format_message(error);
1483 if !msg.is_empty() {
1484 writeln!(f, "{}", msg)?;
1485 }
1486 let mut first = true;
1487 for err in errors {
1488 if !first {
1489 writeln!(f)?;
1490 writeln!(f)?;
1491 }
1492 first = false;
1493 fmt_error_with_snippets_offset(
1494 f,
1495 err,
1496 regions,
1497 *crop_radius,
1498 options.formatter,
1499 )?;
1500 }
1501 return Ok(());
1502 }
1503
1504 let Some(location) = error.location() else {
1507 return fmt_error_plain_with_formatter(f, error, options.formatter);
1508 };
1509 if location == Location::UNKNOWN {
1510 return fmt_error_plain_with_formatter(f, error, options.formatter);
1511 }
1512
1513 let l10n = options.formatter.localizer();
1514
1515 let region = match pick_cropped_region(regions, &location) {
1516 Some(r) => r,
1517 None => return fmt_error_plain_with_formatter(f, error, options.formatter),
1518 };
1519
1520 let dual_locations = error.locations().filter(|locs| {
1522 locs.reference_location != Location::UNKNOWN
1523 && locs.defined_location != Location::UNKNOWN
1524 && locs.reference_location != locs.defined_location
1525 });
1526
1527 let mut msg = options.formatter.format_message(error);
1528
1529 if dual_locations.is_some() && let Error::AliasError { locations, .. } = error.as_ref() {
1533 let suffix = l10n.alias_defined_at(locations.defined_location);
1534 if let Some(stripped) = msg.as_ref().strip_suffix(&suffix) {
1535 msg = Cow::Owned(stripped.to_string());
1536 }
1537 }
1538
1539 if let Some(locs) = dual_locations {
1540 let ref_loc = locs.reference_location;
1541 let def_loc = locs.defined_location;
1542
1543 let used_region = pick_cropped_region(regions, &ref_loc).unwrap_or(region);
1544 let label = l10n.value_used_here();
1545 let ctx = crate::de_snipped::Snippet::new(
1546 used_region.text.as_str(),
1547 label.as_ref(),
1548 *crop_radius,
1549 )
1550 .with_offset(used_region.start_line);
1551 ctx.fmt_or_fallback(f, Level::ERROR, l10n, msg.as_ref(), &ref_loc)?;
1552
1553 let def_region = pick_cropped_region(regions, &def_loc).unwrap_or(region);
1554 writeln!(f)?;
1555 writeln!(f, "{}", l10n.value_comes_from_the_anchor(def_loc))?;
1556 fmt_snippet_window_offset_or_fallback(
1557 f,
1558 l10n,
1559 &def_loc,
1560 def_region.text.as_str(),
1561 def_region.start_line,
1562 l10n.defined_window().as_ref(),
1563 *crop_radius,
1564 )?;
1565 Ok(())
1566 } else {
1567 let ctx = crate::de_snipped::Snippet::new(region.text.as_str(), "<input>", *crop_radius)
1569 .with_offset(region.start_line);
1570 ctx.fmt_or_fallback(f, Level::ERROR, l10n, msg.as_ref(), &location)
1571 }
1572 }
1573 _ => fmt_error_plain_with_formatter(f, err, options.formatter),
1574 }
1575}
1576
1577#[cfg(any(feature = "garde", feature = "validator"))]
1578fn search_locations_with_ancestor_fallback(
1579 locations: &PathMap,
1580 path: &PathKey,
1581) -> Option<Locations> {
1582 if let Some((locs, _)) = locations.search(path) {
1583 return Some(locs);
1584 }
1585
1586 let mut p = path.parent();
1587 while let Some(cur) = p {
1588 if let Some((locs, _)) = locations.search(&cur) {
1589 return Some(locs);
1590 }
1591 p = cur.parent();
1592 }
1593
1594 None
1595}
1596
1597impl fmt::Display for Error {
1598 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1599 fmt_error_rendered(
1600 f,
1601 self,
1602 RenderOptions::default(),
1603 )
1604 }
1605}
1606
1607
1608
1609#[cfg(feature = "garde")]
1610fn fmt_validation_error_with_snippets_offset(
1611 f: &mut fmt::Formatter<'_>,
1612 l10n: &dyn Localizer,
1613 report: &garde::Report,
1614 locations: &PathMap,
1615 regions: &[CroppedRegion],
1616 crop_radius: usize,
1617) -> fmt::Result {
1618 let mut first = true;
1619 for (path, entry) in report.iter() {
1620 if !first {
1621 writeln!(f)?;
1622 }
1623 first = false;
1624
1625 let path_key = path_key_from_garde(path);
1626 let original_leaf = path_key
1627 .leaf_string()
1628 .unwrap_or_else(|| l10n.root_path_label().into_owned());
1629
1630 let (locs, resolved_leaf) = locations
1631 .search(&path_key)
1632 .unwrap_or((Locations::UNKNOWN, original_leaf));
1633
1634 let ref_loc = locs.reference_location;
1635 let def_loc = locs.defined_location;
1636
1637 let resolved_path = format_path_with_resolved_leaf(&path_key, &resolved_leaf);
1638 let entry_raw = entry.to_string();
1639 let entry = l10n
1640 .override_external_message(ExternalMessage {
1641 source: ExternalMessageSource::Garde,
1642 original: entry_raw.as_str(),
1643 code: None,
1644 params: &[],
1645 })
1646 .unwrap_or(Cow::Borrowed(entry_raw.as_str()));
1647 let base_msg = l10n.validation_base_message(entry.as_ref(), &resolved_path);
1648
1649 match (ref_loc, def_loc) {
1650 (Location::UNKNOWN, Location::UNKNOWN) => {
1651 write!(f, "{base_msg}")?;
1652 }
1653 (r, d) if r != Location::UNKNOWN && (d == Location::UNKNOWN || d == r) => {
1654 let label = l10n.defined();
1655 if let Some(region) = pick_cropped_region(regions, &r) {
1656 let ctx = crate::de_snipped::Snippet::new(
1657 region.text.as_str(),
1658 label.as_ref(),
1659 crop_radius,
1660 )
1661 .with_offset(region.start_line);
1662 ctx.fmt_or_fallback(f, Level::ERROR, l10n, &base_msg, &r)?;
1663 } else {
1664 fmt_with_location(f, l10n, &base_msg, &r)?;
1665 }
1666 }
1667 (r, d) if r == Location::UNKNOWN && d != Location::UNKNOWN => {
1668 let label = l10n.defined_here();
1669 if let Some(region) = pick_cropped_region(regions, &d) {
1670 let ctx = crate::de_snipped::Snippet::new(
1671 region.text.as_str(),
1672 label.as_ref(),
1673 crop_radius,
1674 )
1675 .with_offset(region.start_line);
1676 ctx.fmt_or_fallback(f, Level::ERROR, l10n, &base_msg, &d)?;
1677 } else {
1678 fmt_with_location(f, l10n, &base_msg, &d)?;
1679 }
1680 }
1681 (r, d) => {
1682 let label = l10n.value_used_here();
1683 let invalid_here = l10n.invalid_here(&base_msg);
1684 if let Some(region) = pick_cropped_region(regions, &r) {
1685 let ctx = crate::de_snipped::Snippet::new(
1686 region.text.as_str(),
1687 label.as_ref(),
1688 crop_radius,
1689 )
1690 .with_offset(region.start_line);
1691 ctx.fmt_or_fallback(f, Level::ERROR, l10n, &invalid_here, &r)?;
1692 } else {
1693 fmt_with_location(f, l10n, &invalid_here, &r)?;
1694 }
1695 writeln!(f)?;
1696 writeln!(f, "{}", l10n.value_comes_from_the_anchor(d))?;
1697 if let Some(region) = pick_cropped_region(regions, &d) {
1698 crate::de_snipped::fmt_snippet_window_offset_or_fallback(
1699 f,
1700 l10n,
1701 &d,
1702 region.text.as_str(),
1703 region.start_line,
1704 l10n.defined_window().as_ref(),
1705 crop_radius,
1706 )?;
1707 } else {
1708 fmt_with_location(f, l10n, l10n.defined_window().as_ref(), &d)?;
1709 }
1710 }
1711 }
1712 }
1713 Ok(())
1714}
1715
1716
1717#[cfg(feature = "validator")]
1718fn fmt_validator_error_with_snippets_offset(
1719 f: &mut fmt::Formatter<'_>,
1720 l10n: &dyn Localizer,
1721 errors: &ValidationErrors,
1722 locations: &PathMap,
1723 regions: &[CroppedRegion],
1724 crop_radius: usize,
1725) -> fmt::Result {
1726 let entries = collect_validator_issues(errors);
1727 let mut first = true;
1728
1729 for issue in entries {
1730 if !first {
1731 writeln!(f)?;
1732 }
1733 first = false;
1734
1735 let original_leaf = issue
1736 .path
1737 .leaf_string()
1738 .unwrap_or_else(|| l10n.root_path_label().into_owned());
1739 let (locs, resolved_leaf) = locations
1740 .search(&issue.path)
1741 .unwrap_or((Locations::UNKNOWN, original_leaf));
1742
1743 let resolved_path = format_path_with_resolved_leaf(&issue.path, &resolved_leaf);
1744 let entry = issue.display_entry_overridden(l10n, ExternalMessageSource::Validator);
1745 let base_msg = l10n.validation_base_message(&entry, &resolved_path);
1746
1747 match (locs.reference_location, locs.defined_location) {
1748 (Location::UNKNOWN, Location::UNKNOWN) => {
1749 write!(f, "{base_msg}")?;
1750 }
1751 (r, d) if r != Location::UNKNOWN && (d == Location::UNKNOWN || d == r) => {
1752 let label = l10n.defined();
1753 if let Some(region) = pick_cropped_region(regions, &r) {
1754 let ctx = crate::de_snipped::Snippet::new(
1755 region.text.as_str(),
1756 label.as_ref(),
1757 crop_radius,
1758 )
1759 .with_offset(region.start_line);
1760 ctx.fmt_or_fallback(f, Level::ERROR, l10n, &base_msg, &r)?;
1761 } else {
1762 fmt_with_location(f, l10n, &base_msg, &r)?;
1763 }
1764 }
1765 (r, d) if r == Location::UNKNOWN && d != Location::UNKNOWN => {
1766 let label = l10n.defined_here();
1767 if let Some(region) = pick_cropped_region(regions, &d) {
1768 let ctx = crate::de_snipped::Snippet::new(
1769 region.text.as_str(),
1770 label.as_ref(),
1771 crop_radius,
1772 )
1773 .with_offset(region.start_line);
1774 ctx.fmt_or_fallback(f, Level::ERROR, l10n, &base_msg, &d)?;
1775 } else {
1776 fmt_with_location(f, l10n, &base_msg, &d)?;
1777 }
1778 }
1779 (r, d) => {
1780 let label = l10n.value_used_here();
1781 let invalid_here = l10n.invalid_here(&base_msg);
1782 if let Some(region) = pick_cropped_region(regions, &r) {
1783 let ctx = crate::de_snipped::Snippet::new(
1784 region.text.as_str(),
1785 label.as_ref(),
1786 crop_radius,
1787 )
1788 .with_offset(region.start_line);
1789 ctx.fmt_or_fallback(f, Level::ERROR, l10n, &invalid_here, &r)?;
1790 } else {
1791 fmt_with_location(f, l10n, &invalid_here, &r)?;
1792 }
1793 writeln!(f)?;
1794 writeln!(f, "{}", l10n.value_comes_from_the_anchor(d))?;
1795 if let Some(region) = pick_cropped_region(regions, &d) {
1796 crate::de_snipped::fmt_snippet_window_offset_or_fallback(
1797 f,
1798 l10n,
1799 &d,
1800 region.text.as_str(),
1801 region.start_line,
1802 l10n.defined_window().as_ref(),
1803 crop_radius,
1804 )?;
1805 } else {
1806 fmt_with_location(f, l10n, l10n.defined_window().as_ref(), &d)?;
1807 }
1808 }
1809 }
1810 }
1811
1812 Ok(())
1813}
1814
1815#[cfg(any(feature = "garde", feature = "validator"))]
1816fn fmt_error_with_snippets_offset(
1817 f: &mut fmt::Formatter<'_>,
1818 err: &Error,
1819 regions: &[CroppedRegion],
1820 crop_radius: usize,
1821 formatter: &dyn MessageFormatter,
1822) -> fmt::Result {
1823 if crop_radius == 0 {
1824 return fmt_error_plain_with_formatter(f, err, formatter);
1825 }
1826
1827 if let Error::WithSnippet { .. } = err {
1829 return fmt_error_rendered(f, err, RenderOptions::new(formatter));
1830 }
1831
1832 #[cfg(feature = "garde")]
1833 if let Error::ValidationError { report, locations } = err {
1834 return fmt_validation_error_with_snippets_offset(
1835 f,
1836 formatter.localizer(),
1837 report,
1838 locations,
1839 regions,
1840 crop_radius,
1841 );
1842 }
1843
1844 #[cfg(feature = "validator")]
1845 if let Error::ValidatorError { errors, locations } = err {
1846 return fmt_validator_error_with_snippets_offset(
1847 f,
1848 formatter.localizer(),
1849 errors,
1850 locations,
1851 regions,
1852 crop_radius,
1853 );
1854 }
1855
1856 let msg = formatter.format_message(err);
1857 let Some(location) = err.location() else {
1858 return write!(f, "{msg}");
1859 };
1860 if location == Location::UNKNOWN {
1861 return write!(f, "{msg}");
1862 }
1863
1864 let Some(region) = pick_cropped_region(regions, &location) else {
1865 return fmt_with_location(f, formatter.localizer(), msg.as_ref(), &location);
1866 };
1867 let ctx = crate::de_snipped::Snippet::new(region.text.as_str(), "<input>", crop_radius)
1868 .with_offset(region.start_line);
1869 ctx.fmt_or_fallback(f, Level::ERROR, formatter.localizer(), msg.as_ref(), &location)
1870}
1871
1872#[cfg(feature = "validator")]
1873pub(crate) fn collect_validator_issues(errors: &ValidationErrors) -> Vec<ValidationIssue> {
1874 let mut out = Vec::new();
1875 let root = PathKey::empty();
1876 collect_validator_issues_inner(errors, &root, &mut out);
1877 out
1878}
1879
1880#[cfg(feature = "validator")]
1881fn collect_validator_issues_inner(
1882 errors: &ValidationErrors,
1883 path: &PathKey,
1884 out: &mut Vec<ValidationIssue>,
1885) {
1886 for (field, kind) in errors.errors() {
1887 let field_path = path.clone().join(field.as_ref());
1888 match kind {
1889 ValidationErrorsKind::Field(entries) => {
1890 for entry in entries {
1891 let mut params = Vec::new();
1892 for (k, v) in &entry.params {
1893 params.push((k.to_string(), v.to_string()));
1894 }
1895
1896 out.push(ValidationIssue {
1897 path: field_path.clone(),
1898 code: entry.code.to_string(),
1899 message: entry.message.as_ref().map(|m| m.to_string()),
1900 params,
1901 });
1902 }
1903 }
1904 ValidationErrorsKind::Struct(inner) => {
1905 collect_validator_issues_inner(inner, &field_path, out);
1906 }
1907 ValidationErrorsKind::List(list) => {
1908 for (idx, inner) in list {
1909 let index_path = field_path.clone().join(*idx);
1910 collect_validator_issues_inner(inner, &index_path, out);
1911 }
1912 }
1913 }
1914 }
1915}
1916
1917#[cfg(feature = "garde")]
1918pub(crate) fn collect_garde_issues(report: &garde::Report) -> Vec<ValidationIssue> {
1919 let mut out = Vec::new();
1920 for (path, entry) in report.iter() {
1921 out.push(ValidationIssue {
1922 path: path_key_from_garde(path),
1923 code: "garde".to_string(),
1924 message: Some(entry.message().to_string()),
1925 params: Vec::new(),
1926 });
1927 }
1928 out
1929}
1930impl std::error::Error for Error {}
1931
1932fn maybe_attach_fallback_location(mut err: Error) -> Error {
1934 let loc = MISSING_FIELD_FALLBACK.with(|c| c.get());
1935 if let Some(loc) = loc
1936 && loc != Location::UNKNOWN
1937 {
1938 err = err.with_location(loc);
1939 }
1940 err
1941}
1942
1943impl de::Error for Error {
1944 fn custom<T: fmt::Display>(msg: T) -> Self {
1945 Error::msg(msg.to_string())
1949 }
1950
1951 fn invalid_type(unexp: de::Unexpected, exp: &dyn de::Expected) -> Self {
1952 maybe_attach_fallback_location(Error::SerdeInvalidType {
1954 unexpected: unexp.to_string(),
1955 expected: exp.to_string(),
1956 location: Location::UNKNOWN,
1957 })
1958 }
1959
1960 fn invalid_value(unexp: de::Unexpected, exp: &dyn de::Expected) -> Self {
1961 maybe_attach_fallback_location(Error::SerdeInvalidValue {
1962 unexpected: unexp.to_string(),
1963 expected: exp.to_string(),
1964 location: Location::UNKNOWN,
1965 })
1966 }
1967
1968 fn unknown_variant(variant: &str, expected: &'static [&'static str]) -> Self {
1969 maybe_attach_fallback_location(Error::SerdeUnknownVariant {
1970 variant: variant.to_owned(),
1971 expected: expected.to_vec(),
1972 location: Location::UNKNOWN,
1973 })
1974 }
1975
1976 fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
1977 maybe_attach_fallback_location(Error::SerdeUnknownField {
1978 field: field.to_owned(),
1979 expected: expected.to_vec(),
1980 location: Location::UNKNOWN,
1981 })
1982 }
1983
1984 fn missing_field(field: &'static str) -> Self {
1985 maybe_attach_fallback_location(Error::SerdeMissingField {
1986 field,
1987 location: Location::UNKNOWN,
1988 })
1989 }
1990}
1991
1992#[cold]
2002#[inline(never)]
2003fn fmt_with_location(
2004 f: &mut fmt::Formatter<'_>,
2005 l10n: &dyn Localizer,
2006 msg: &str,
2007 location: &Location,
2008) -> fmt::Result {
2009 let out = l10n.attach_location(Cow::Borrowed(msg), *location);
2010 write!(f, "{out}")
2011}
2012
2013
2014#[cold]
2025#[inline(never)]
2026pub(crate) fn budget_error(breach: BudgetBreach) -> Error {
2027 Error::Budget {
2028 breach,
2029 location: Location::UNKNOWN,
2030 }
2031}
2032
2033#[cfg(test)]
2034mod tests {
2035 use super::*;
2036
2037 #[test]
2038 fn locations_for_basic_error_duplicates_location() {
2039 let l = Location::new(3, 7);
2040 let err = Error::Message {
2041 msg: "x".to_owned(),
2042 location: l,
2043 };
2044 assert_eq!(
2045 err.locations(),
2046 Some(Locations {
2047 reference_location: l,
2048 defined_location: l,
2049 })
2050 );
2051 }
2052
2053 #[test]
2054 fn locations_for_io_error_is_unknown() {
2055 let err = Error::IOError {
2056 cause: std::io::Error::other("x"),
2057 };
2058 assert_eq!(err.locations(), None);
2059 }
2060
2061 #[test]
2062 fn alias_error_returns_both_locations() {
2063 let ref_loc = Location::new(5, 10);
2064 let def_loc = Location::new(2, 3);
2065 let err = Error::AliasError {
2066 msg: "test error".to_owned(),
2067 locations: Locations {
2068 reference_location: ref_loc,
2069 defined_location: def_loc,
2070 },
2071 };
2072
2073 assert_eq!(err.location(), Some(ref_loc));
2075
2076 assert_eq!(
2078 err.locations(),
2079 Some(Locations {
2080 reference_location: ref_loc,
2081 defined_location: def_loc,
2082 })
2083 );
2084 }
2085
2086 #[test]
2087 fn alias_error_display_shows_both_locations() {
2088 let ref_loc = Location::new(5, 10);
2089 let def_loc = Location::new(2, 3);
2090 let err = Error::AliasError {
2091 msg: "invalid value".to_owned(),
2092 locations: Locations {
2093 reference_location: ref_loc,
2094 defined_location: def_loc,
2095 },
2096 };
2097
2098 let display = err.to_string();
2099 assert!(display.contains("invalid value"));
2100 assert!(display.contains("line 5"));
2101 assert!(display.contains("column 10"));
2102 assert!(display.contains("line 2"));
2103 assert!(display.contains("column 3"));
2104 }
2105
2106 #[test]
2107 fn alias_error_display_with_same_locations() {
2108 let loc = Location::new(3, 7);
2109 let err = Error::AliasError {
2110 msg: "test".to_owned(),
2111 locations: Locations {
2112 reference_location: loc,
2113 defined_location: loc,
2114 },
2115 };
2116
2117 let display = err.to_string();
2118 assert!(display.contains("line 3"));
2120 assert!(display.contains("column 7"));
2121 assert!(!display.contains("defined at"));
2123 }
2124
2125 #[test]
2126 fn with_snippet_counts_trailing_empty_line_for_end_line() {
2127 let text = "a\n";
2129 let err = Error::Message {
2130 msg: "x".to_owned(),
2131 location: Location::new(2, 1),
2132 };
2133
2134 let wrapped = err.with_snippet(text, 50);
2135 let Error::WithSnippet { regions, .. } = wrapped else {
2136 panic!("expected WithSnippet wrapper");
2137 };
2138 assert_eq!(regions.len(), 1);
2139 assert_eq!(regions[0].start_line, 1);
2140 assert_eq!(regions[0].end_line, 2);
2141 }
2142
2143 #[test]
2144 fn with_snippet_offset_counts_trailing_empty_line_for_end_line() {
2145 let text = "a\n";
2147 let err = Error::Message {
2148 msg: "x".to_owned(),
2149 location: Location::new(11, 1),
2150 };
2151
2152 let wrapped = err.with_snippet_offset(text, 10, 50);
2153 let Error::WithSnippet { regions, .. } = wrapped else {
2154 panic!("expected WithSnippet wrapper");
2155 };
2156 assert_eq!(regions.len(), 1);
2157 assert_eq!(regions[0].start_line, 10);
2158 assert_eq!(regions[0].end_line, 11);
2159 }
2160
2161 #[cfg(feature = "validator")]
2162 #[test]
2163 fn locations_for_validator_error_uses_first_entry() {
2164 use validator::Validate;
2165
2166 #[derive(Debug, Validate)]
2167 struct Cfg {
2168 #[validate(length(min = 2))]
2169 second_string: String,
2170 }
2171
2172 let cfg = Cfg {
2173 second_string: "x".to_owned(),
2174 };
2175 let errors = cfg.validate().expect_err("validation error expected");
2176
2177 let referenced_loc = Location::new(3, 15);
2178 let defined_loc = Location::new(2, 18);
2179
2180 let mut locations = PathMap::new();
2181 locations.insert(
2182 PathKey::empty().join("secondString"),
2183 Locations {
2184 reference_location: referenced_loc,
2185 defined_location: defined_loc,
2186 },
2187 );
2188
2189 let err = Error::ValidatorError { errors, locations };
2190 assert_eq!(
2191 err.locations(),
2192 Some(Locations {
2193 reference_location: referenced_loc,
2194 defined_location: defined_loc,
2195 })
2196 );
2197 }
2198
2199 #[test]
2200 fn nested_snippet_preserves_custom_formatter() {
2201 struct Custom;
2202 impl MessageFormatter for Custom {
2203 fn localizer(&self) -> &dyn Localizer {
2204 &DEFAULT_ENGLISH_LOCALIZER
2205 }
2206 fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
2207 match err {
2208 Error::Message { msg, .. } => Cow::Owned(format!("CUSTOM: {}", msg.as_str())),
2209 _ => Cow::Borrowed(""),
2210 }
2211 }
2212 }
2213 let loc = Location::new(1, 1);
2214 let base = Error::Message {
2215 msg: "original".to_string(),
2216 location: loc,
2217 };
2218 let text = "input";
2219 let start_line = 1;
2220 let radius = 1;
2221 let inner = base.with_snippet_offset(text, start_line, radius);
2222 let outer = inner.with_snippet_offset(text, start_line, radius);
2223 let rendered = outer.render_with_options(RenderOptions::new(&Custom));
2224 assert!(rendered.contains("CUSTOM: original"));
2225 }
2226
2227 #[test]
2228 fn alias_error_dual_snippet_rendering() {
2229 let yaml = r#"config:
2231 anchor: &myval 42
2232 other: stuff
2233 more: data
2234 use_it: *myval
2235"#;
2236 let ref_loc = Location::new(5, 11);
2238 let def_loc = Location::new(2, 11);
2240
2241 let err = Error::AliasError {
2242 msg: "invalid value type".to_owned(),
2243 locations: Locations {
2244 reference_location: ref_loc,
2245 defined_location: def_loc,
2246 },
2247 };
2248
2249 let wrapped = err.with_snippet(yaml, 5);
2251 let rendered = wrapped.render();
2252
2253 assert!(rendered.contains("invalid value type"), "rendered: {}", rendered);
2255
2256 assert!(
2259 !rendered.contains("(defined at line"),
2260 "did not expect alias defined-at suffix when secondary window is present: {}",
2261 rendered
2262 );
2263 assert!(rendered.contains("the value is used here") || rendered.contains("use_it"),
2265 "rendered should show reference location context: {}", rendered);
2266 assert!(rendered.contains("defined here") || rendered.contains("anchor"),
2268 "rendered should show defined location context: {}", rendered);
2269 assert!(rendered.contains("5") || rendered.contains("use_it"),
2271 "rendered should reference line 5: {}", rendered);
2272 assert!(rendered.contains("2") || rendered.contains("anchor"),
2273 "rendered should reference line 2: {}", rendered);
2274 }
2275
2276 #[test]
2277 fn alias_error_same_location_single_snippet() {
2278 let yaml = "value: &anchor 42\n";
2279 let loc = Location::new(1, 8);
2280
2281 let err = Error::AliasError {
2282 msg: "test error".to_owned(),
2283 locations: Locations {
2284 reference_location: loc,
2285 defined_location: loc,
2286 },
2287 };
2288
2289 let wrapped = err.with_snippet(yaml, 5);
2290 let rendered = wrapped.render();
2291
2292 assert!(rendered.contains("test error"), "rendered: {}", rendered);
2294 assert!(!rendered.contains("defined here"),
2296 "should not show 'defined here' when locations are same: {}", rendered);
2297 assert!(!rendered.contains("the value is used here"),
2298 "should not show 'value used here' when locations are same: {}", rendered);
2299 }
2300}