1use crate::Location;
2use crate::budget::BudgetBreach;
3use crate::de_snipped::fmt_snippet_window_offset_or_fallback;
4use crate::localizer::{DEFAULT_ENGLISH_LOCALIZER, ExternalMessageSource, Localizer};
5use crate::location::Locations;
6use crate::parse_scalars::{
7 parse_int_signed, parse_yaml11_bool, parse_yaml12_float, scalar_is_nullish,
8};
9#[cfg(feature = "garde")]
10use crate::path_map::path_key_from_garde;
11#[cfg(any(feature = "garde", feature = "validator"))]
12use crate::path_map::{PathKey, PathMap, format_path_with_resolved_leaf};
13use crate::tags::SfTag;
14use annotate_snippets::Level;
15use saphyr_parser::{ScalarStyle, ScanError};
16use serde::de::{self};
17use std::borrow::Cow;
18use std::cell::Cell;
19use std::fmt;
20#[cfg(feature = "validator")]
21use validator::{ValidationErrors, ValidationErrorsKind};
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 => {
326 write!(f, "multi-line whitespace normalization")
327 }
328 TransformReason::BlockScalarProcessing => write!(f, "block scalar processing"),
329 TransformReason::SingleQuoteEscape => write!(f, "single-quote escape processing"),
330 TransformReason::InputNotBorrowable => {
331 write!(f, "input is not available for borrowing")
332 }
333 TransformReason::ParserReturnedOwned => write!(f, "parser returned an owned string"),
334 }
335 }
336}
337
338#[non_exhaustive]
340#[derive(Debug)]
341pub enum Error {
342 Message {
344 msg: String,
345 location: Location,
346 },
347
348 ExternalMessage {
353 source: ExternalMessageSource,
354 msg: String,
355 code: Option<String>,
357 params: Vec<(String, String)>,
359 location: Location,
360 },
361 Eof {
363 location: Location,
364 },
365 MultipleDocuments {
370 hint: &'static str,
372 location: Location,
373 },
374 Unexpected {
376 expected: &'static str,
377 location: Location,
378 },
379
380 MergeValueNotMapOrSeqOfMaps {
382 location: Location,
383 },
384
385 InvalidBinaryBase64 {
387 location: Location,
388 },
389
390 BinaryNotUtf8 {
392 location: Location,
393 },
394
395 TaggedScalarCannotDeserializeIntoString {
397 location: Location,
398 },
399
400 UnexpectedSequenceEnd {
402 location: Location,
403 },
404
405 UnexpectedMappingEnd {
407 location: Location,
408 },
409
410 InvalidBooleanStrict {
412 location: Location,
413 },
414
415 InvalidCharNull {
417 location: Location,
418 },
419
420 InvalidCharNotSingleScalar {
422 location: Location,
423 },
424
425 NullIntoString {
427 location: Location,
428 },
429
430 BytesNotSupportedMissingBinaryTag {
432 location: Location,
433 },
434
435 UnexpectedValueForUnit {
437 location: Location,
438 },
439
440 ExpectedEmptyMappingForUnitStruct {
442 location: Location,
443 },
444
445 UnexpectedContainerEndWhileSkippingNode {
447 location: Location,
448 },
449
450 InternalSeedReusedForMapKey {
452 location: Location,
453 },
454
455 ValueRequestedBeforeKey {
457 location: Location,
458 },
459
460 ExpectedStringKeyForExternallyTaggedEnum {
462 location: Location,
463 },
464
465 ExternallyTaggedEnumExpectedScalarOrMapping {
467 location: Location,
468 },
469
470 UnexpectedValueForUnitEnumVariant {
472 location: Location,
473 },
474
475 InvalidUtf8Input,
477
478 AliasReplayCounterOverflow {
480 location: Location,
481 },
482
483 AliasReplayLimitExceeded {
485 total_replayed_events: usize,
486 max_total_replayed_events: usize,
487 location: Location,
488 },
489
490 AliasExpansionLimitExceeded {
492 anchor_id: usize,
493 expansions: usize,
494 max_expansions_per_anchor: usize,
495 location: Location,
496 },
497
498 AliasReplayStackDepthExceeded {
500 depth: usize,
501 max_depth: usize,
502 location: Location,
503 },
504
505 FoldedBlockScalarMustIndentContent {
507 location: Location,
508 },
509
510 InternalDepthUnderflow {
512 location: Location,
513 },
514
515 InternalRecursionStackEmpty {
517 location: Location,
518 },
519
520 RecursiveReferencesRequireWeakTypes {
522 location: Location,
523 },
524
525 InvalidScalar {
527 ty: &'static str,
528 location: Location,
529 },
530
531 SerdeInvalidType {
533 unexpected: String,
534 expected: String,
535 location: Location,
536 },
537
538 SerdeInvalidValue {
540 unexpected: String,
541 expected: String,
542 location: Location,
543 },
544
545 SerdeUnknownVariant {
547 variant: String,
548 expected: Vec<&'static str>,
549 location: Location,
550 },
551
552 SerdeUnknownField {
554 field: String,
555 expected: Vec<&'static str>,
556 location: Location,
557 },
558
559 SerdeMissingField {
561 field: &'static str,
562 location: Location,
563 },
564
565 UnexpectedContainerEndWhileReadingKeyNode {
569 location: Location,
570 },
571
572 DuplicateMappingKey {
576 key: Option<String>,
577 location: Location,
578 },
579
580 TaggedEnumMismatch {
582 tagged: String,
583 target: &'static str,
584 location: Location,
585 },
586
587 SerdeVariantId {
589 msg: String,
590 location: Location,
591 },
592
593 ExpectedMappingEndAfterEnumVariantValue {
595 location: Location,
596 },
597 ContainerEndMismatch {
598 location: Location,
599 },
600 UnknownAnchor {
602 location: Location,
603 },
604 AliasError {
609 msg: String,
610 locations: Locations,
611 },
612 HookError {
615 msg: String,
616 location: Location,
617 },
618 Budget {
620 breach: BudgetBreach,
621 location: Location,
622 },
623 IOError {
625 cause: std::io::Error,
626 },
627 QuotingRequired {
630 value: String, location: Location,
632 },
633
634 CannotBorrowTransformedString {
640 reason: TransformReason,
642 location: Location,
643 },
644
645 WithSnippet {
647 regions: Vec<CroppedRegion>,
652 crop_radius: usize,
653 error: Box<Error>,
654 },
655
656 #[cfg(feature = "garde")]
658 ValidationError {
659 report: garde::Report,
660 locations: PathMap,
661 },
662
663 #[cfg(feature = "garde")]
665 ValidationErrors {
666 errors: Vec<Error>,
667 },
668
669 #[cfg(feature = "validator")]
671 ValidatorError {
672 errors: ValidationErrors,
673 locations: PathMap,
674 },
675
676 #[cfg(feature = "validator")]
678 ValidatorErrors {
679 errors: Vec<Error>,
680 },
681}
682
683impl Error {
684 #[cold]
685 #[inline(never)]
686 pub(crate) fn with_snippet(self, text: &str, crop_radius: usize) -> Self {
687 let inner = match self {
690 Error::WithSnippet { error, .. } => *error,
691 other => other,
692 };
693
694 let text = text.strip_prefix('\u{FEFF}').unwrap_or(text);
696
697 fn push_region_for_location(
698 regions: &mut Vec<CroppedRegion>,
699 text: &str,
700 location: &Location,
701 mapping: crate::de_snipped::LineMapping,
702 crop_radius: usize,
703 ) {
704 if crop_radius == 0 || *location == Location::UNKNOWN {
705 return;
706 }
707 let (cropped, start_line) =
708 crate::de_snipped::crop_source_window(text, location, mapping, crop_radius);
709 if cropped.is_empty() {
710 return;
711 }
712 let lines = line_count_including_trailing_empty_line(cropped.as_str());
713 let end_line = start_line.saturating_add(lines.saturating_sub(1));
714 regions.push(CroppedRegion {
715 text: cropped,
716 start_line,
717 end_line,
718 });
719 }
720
721 let mut regions: Vec<CroppedRegion> = Vec::new();
722 let mapping = crate::de_snipped::LineMapping::Identity;
723
724 #[cfg(feature = "garde")]
727 if let Error::ValidationError { report, locations } = &inner {
728 for (path, _entry) in report.iter() {
729 let key = path_key_from_garde(path);
730 let (locs, _) = locations
731 .search(&key)
732 .unwrap_or((Locations::UNKNOWN, String::new()));
733 push_region_for_location(
734 &mut regions,
735 text,
736 &locs.reference_location,
737 mapping,
738 crop_radius,
739 );
740 if locs.defined_location != locs.reference_location {
741 push_region_for_location(
742 &mut regions,
743 text,
744 &locs.defined_location,
745 mapping,
746 crop_radius,
747 );
748 }
749 }
750 }
751 #[cfg(feature = "validator")]
752 if let Error::ValidatorError { errors, locations } = &inner {
753 for issue in collect_validator_issues(errors) {
754 let (locs, _) = locations
755 .search(&issue.path)
756 .unwrap_or((Locations::UNKNOWN, String::new()));
757 push_region_for_location(
758 &mut regions,
759 text,
760 &locs.reference_location,
761 mapping,
762 crop_radius,
763 );
764 if locs.defined_location != locs.reference_location {
765 push_region_for_location(
766 &mut regions,
767 text,
768 &locs.defined_location,
769 mapping,
770 crop_radius,
771 );
772 }
773 }
774 }
775
776 if regions.is_empty() {
779 if let Some(locs) = inner.locations() {
780 push_region_for_location(
781 &mut regions,
782 text,
783 &locs.reference_location,
784 mapping,
785 crop_radius,
786 );
787 if locs.defined_location != locs.reference_location {
788 push_region_for_location(
789 &mut regions,
790 text,
791 &locs.defined_location,
792 mapping,
793 crop_radius,
794 );
795 }
796 } else if let Some(loc) = inner.location() {
797 push_region_for_location(&mut regions, text, &loc, mapping, crop_radius);
798 }
799 }
800
801 Error::WithSnippet {
802 regions,
803 crop_radius,
804 error: Box::new(inner),
805 }
806 }
807
808 #[cold]
814 #[inline(never)]
815 pub(crate) fn with_snippet_offset(
816 self,
817 text: &str,
818 start_line: usize,
819 crop_radius: usize,
820 ) -> Self {
821 let inner = match self {
822 Error::WithSnippet { error, .. } => *error,
823 other => other,
824 };
825
826 let text = text.strip_prefix('\u{FEFF}').unwrap_or(text);
828
829 fn push_region_for_location(
830 regions: &mut Vec<CroppedRegion>,
831 text: &str,
832 location: &Location,
833 mapping: crate::de_snipped::LineMapping,
834 crop_radius: usize,
835 ) {
836 if crop_radius == 0 || *location == Location::UNKNOWN {
837 return;
838 }
839 let (cropped, region_start_line) =
840 crate::de_snipped::crop_source_window(text, location, mapping, crop_radius);
841 if cropped.is_empty() {
842 return;
843 }
844 let lines = line_count_including_trailing_empty_line(cropped.as_str());
845 let end_line = region_start_line.saturating_add(lines.saturating_sub(1));
846 regions.push(CroppedRegion {
847 text: cropped,
848 start_line: region_start_line,
849 end_line,
850 });
851 }
852
853 let mut regions: Vec<CroppedRegion> = Vec::new();
854 let mapping = crate::de_snipped::LineMapping::Offset { start_line };
855
856 #[cfg(feature = "garde")]
857 if let Error::ValidationError { report, locations } = &inner {
858 for (path, _entry) in report.iter() {
859 let key = path_key_from_garde(path);
860 let (locs, _) = locations
861 .search(&key)
862 .unwrap_or((Locations::UNKNOWN, String::new()));
863 push_region_for_location(
864 &mut regions,
865 text,
866 &locs.reference_location,
867 mapping,
868 crop_radius,
869 );
870 if locs.defined_location != locs.reference_location {
871 push_region_for_location(
872 &mut regions,
873 text,
874 &locs.defined_location,
875 mapping,
876 crop_radius,
877 );
878 }
879 }
880 }
881 #[cfg(feature = "validator")]
882 if let Error::ValidatorError { errors, locations } = &inner {
883 for issue in collect_validator_issues(errors) {
884 let (locs, _) = locations
885 .search(&issue.path)
886 .unwrap_or((Locations::UNKNOWN, String::new()));
887 push_region_for_location(
888 &mut regions,
889 text,
890 &locs.reference_location,
891 mapping,
892 crop_radius,
893 );
894 if locs.defined_location != locs.reference_location {
895 push_region_for_location(
896 &mut regions,
897 text,
898 &locs.defined_location,
899 mapping,
900 crop_radius,
901 );
902 }
903 }
904 }
905
906 if regions.is_empty() {
907 if let Some(locs) = inner.locations() {
908 push_region_for_location(
909 &mut regions,
910 text,
911 &locs.reference_location,
912 mapping,
913 crop_radius,
914 );
915 if locs.defined_location != locs.reference_location {
916 push_region_for_location(
917 &mut regions,
918 text,
919 &locs.defined_location,
920 mapping,
921 crop_radius,
922 );
923 }
924 } else if let Some(loc) = inner.location() {
925 push_region_for_location(&mut regions, text, &loc, mapping, crop_radius);
926 }
927 }
928
929 Error::WithSnippet {
930 regions,
931 crop_radius,
932 error: Box::new(inner),
933 }
934 }
935
936 pub fn without_snippet(&self) -> &Self {
938 match self {
939 Error::WithSnippet { error, .. } => error,
940 other => other,
941 }
942 }
943
944 pub fn render(&self) -> String {
950 self.render_with_options(RenderOptions::default())
951 }
952
953 pub fn render_with_formatter(&self, formatter: &dyn MessageFormatter) -> String {
955 self.render_with_options(RenderOptions {
956 formatter,
957 snippets: SnippetMode::Auto,
958 })
959 }
960
961 pub fn render_with_options(&self, options: RenderOptions<'_>) -> String {
963 struct RenderDisplay<'a> {
964 err: &'a Error,
965 options: RenderOptions<'a>,
966 }
967
968 impl fmt::Display for RenderDisplay<'_> {
969 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
970 fmt_error_rendered(f, self.err, self.options)
971 }
972 }
973
974 RenderDisplay { err: self, options }.to_string()
975 }
976
977 #[cold]
988 #[inline(never)]
989 pub(crate) fn msg<S: Into<String>>(s: S) -> Self {
990 Error::Message {
991 msg: s.into(),
992 location: Location::UNKNOWN,
993 }
994 }
995
996 #[cold]
1000 #[inline(never)]
1001 pub(crate) fn quoting_required(value: &str) -> Self {
1002 let location = Location::UNKNOWN;
1005 let value = if parse_yaml12_float::<f64>(value, location, SfTag::None, false).is_ok()
1006 || parse_int_signed::<i128>(value, "i128", location, false).is_ok()
1007 || parse_yaml11_bool(value).is_ok()
1008 || scalar_is_nullish(value, &ScalarStyle::Plain)
1009 {
1010 value.to_string()
1011 } else {
1012 String::new()
1013 };
1014 Error::QuotingRequired { value, location }
1015 }
1016
1017 #[cold]
1028 #[inline(never)]
1029 pub(crate) fn unexpected(what: &'static str) -> Self {
1030 Error::Unexpected {
1031 expected: what,
1032 location: Location::UNKNOWN,
1033 }
1034 }
1035
1036 #[cold]
1041 #[inline(never)]
1042 pub(crate) fn eof() -> Self {
1043 Error::Eof {
1044 location: Location::UNKNOWN,
1045 }
1046 }
1047
1048 #[cold]
1049 #[inline(never)]
1050 pub(crate) fn multiple_documents(hint: &'static str) -> Self {
1051 Error::MultipleDocuments {
1052 hint,
1053 location: Location::UNKNOWN,
1054 }
1055 }
1056
1057 #[cold]
1062 #[inline(never)]
1063 pub(crate) fn unknown_anchor() -> Self {
1064 Error::UnknownAnchor {
1065 location: Location::UNKNOWN,
1066 }
1067 }
1068
1069 #[cold]
1074 #[inline(never)]
1075 pub fn cannot_borrow_transformed(reason: TransformReason) -> Self {
1076 Error::CannotBorrowTransformedString {
1077 reason,
1078 location: Location::UNKNOWN,
1079 }
1080 }
1081
1082 #[cold]
1093 #[inline(never)]
1094 pub(crate) fn with_location(mut self, set_location: Location) -> Self {
1095 match &mut self {
1096 Error::Message { location, .. }
1097 | Error::ExternalMessage { location, .. }
1098 | Error::Eof { location }
1099 | Error::MultipleDocuments { location, .. }
1100 | Error::Unexpected { location, .. }
1101 | Error::MergeValueNotMapOrSeqOfMaps { location }
1102 | Error::InvalidBinaryBase64 { location }
1103 | Error::BinaryNotUtf8 { location }
1104 | Error::TaggedScalarCannotDeserializeIntoString { location }
1105 | Error::UnexpectedSequenceEnd { location }
1106 | Error::UnexpectedMappingEnd { location }
1107 | Error::InvalidBooleanStrict { location }
1108 | Error::InvalidCharNull { location }
1109 | Error::InvalidCharNotSingleScalar { location }
1110 | Error::NullIntoString { location }
1111 | Error::BytesNotSupportedMissingBinaryTag { location }
1112 | Error::UnexpectedValueForUnit { location }
1113 | Error::ExpectedEmptyMappingForUnitStruct { location }
1114 | Error::UnexpectedContainerEndWhileSkippingNode { location }
1115 | Error::InternalSeedReusedForMapKey { location }
1116 | Error::ValueRequestedBeforeKey { location }
1117 | Error::ExpectedStringKeyForExternallyTaggedEnum { location }
1118 | Error::ExternallyTaggedEnumExpectedScalarOrMapping { location }
1119 | Error::UnexpectedValueForUnitEnumVariant { location }
1120 | Error::AliasReplayCounterOverflow { location }
1121 | Error::AliasReplayLimitExceeded { location, .. }
1122 | Error::AliasExpansionLimitExceeded { location, .. }
1123 | Error::AliasReplayStackDepthExceeded { location, .. }
1124 | Error::FoldedBlockScalarMustIndentContent { location }
1125 | Error::InternalDepthUnderflow { location }
1126 | Error::InternalRecursionStackEmpty { location }
1127 | Error::RecursiveReferencesRequireWeakTypes { location }
1128 | Error::InvalidScalar { location, .. }
1129 | Error::SerdeInvalidType { location, .. }
1130 | Error::SerdeInvalidValue { location, .. }
1131 | Error::SerdeUnknownVariant { location, .. }
1132 | Error::SerdeUnknownField { location, .. }
1133 | Error::SerdeMissingField { location, .. }
1134 | Error::UnexpectedContainerEndWhileReadingKeyNode { location }
1135 | Error::DuplicateMappingKey { location, .. }
1136 | Error::TaggedEnumMismatch { location, .. }
1137 | Error::SerdeVariantId { location, .. }
1138 | Error::ExpectedMappingEndAfterEnumVariantValue { location }
1139 | Error::HookError { location, .. }
1140 | Error::ContainerEndMismatch { location, .. }
1141 | Error::UnknownAnchor { location, .. }
1142 | Error::QuotingRequired { location, .. }
1143 | Error::Budget { location, .. }
1144 | Error::CannotBorrowTransformedString { location, .. } => {
1145 *location = set_location;
1146 }
1147 Error::InvalidUtf8Input => {}
1148 Error::IOError { .. } => {} Error::AliasError { .. } => {
1150 }
1152 Error::WithSnippet { error, .. } => {
1153 let inner = *std::mem::replace(error, Box::new(Error::eof()));
1154 **error = inner.with_location(set_location);
1155 }
1156 #[cfg(feature = "garde")]
1157 Error::ValidationError { .. } => {
1158 }
1160 #[cfg(feature = "garde")]
1161 Error::ValidationErrors { .. } => {
1162 }
1164 #[cfg(feature = "validator")]
1165 Error::ValidatorError { .. } => {
1166 }
1168 #[cfg(feature = "validator")]
1169 Error::ValidatorErrors { .. } => {
1170 }
1172 }
1173 self
1174 }
1175
1176 pub fn location(&self) -> Option<Location> {
1184 match self {
1185 Error::Message { location, .. }
1186 | Error::ExternalMessage { location, .. }
1187 | Error::Eof { location }
1188 | Error::MultipleDocuments { location, .. }
1189 | Error::Unexpected { location, .. }
1190 | Error::MergeValueNotMapOrSeqOfMaps { location }
1191 | Error::InvalidBinaryBase64 { location }
1192 | Error::BinaryNotUtf8 { location }
1193 | Error::TaggedScalarCannotDeserializeIntoString { location }
1194 | Error::UnexpectedSequenceEnd { location }
1195 | Error::UnexpectedMappingEnd { location }
1196 | Error::InvalidBooleanStrict { location }
1197 | Error::InvalidCharNull { location }
1198 | Error::InvalidCharNotSingleScalar { location }
1199 | Error::NullIntoString { location }
1200 | Error::BytesNotSupportedMissingBinaryTag { location }
1201 | Error::UnexpectedValueForUnit { location }
1202 | Error::ExpectedEmptyMappingForUnitStruct { location }
1203 | Error::UnexpectedContainerEndWhileSkippingNode { location }
1204 | Error::InternalSeedReusedForMapKey { location }
1205 | Error::ValueRequestedBeforeKey { location }
1206 | Error::ExpectedStringKeyForExternallyTaggedEnum { location }
1207 | Error::ExternallyTaggedEnumExpectedScalarOrMapping { location }
1208 | Error::UnexpectedValueForUnitEnumVariant { location }
1209 | Error::AliasReplayCounterOverflow { location }
1210 | Error::AliasReplayLimitExceeded { location, .. }
1211 | Error::AliasExpansionLimitExceeded { location, .. }
1212 | Error::AliasReplayStackDepthExceeded { location, .. }
1213 | Error::FoldedBlockScalarMustIndentContent { location }
1214 | Error::InternalDepthUnderflow { location }
1215 | Error::InternalRecursionStackEmpty { location }
1216 | Error::RecursiveReferencesRequireWeakTypes { location }
1217 | Error::InvalidScalar { location, .. }
1218 | Error::SerdeInvalidType { location, .. }
1219 | Error::SerdeInvalidValue { location, .. }
1220 | Error::SerdeUnknownVariant { location, .. }
1221 | Error::SerdeUnknownField { location, .. }
1222 | Error::SerdeMissingField { location, .. }
1223 | Error::UnexpectedContainerEndWhileReadingKeyNode { location }
1224 | Error::DuplicateMappingKey { location, .. }
1225 | Error::TaggedEnumMismatch { location, .. }
1226 | Error::SerdeVariantId { location, .. }
1227 | Error::ExpectedMappingEndAfterEnumVariantValue { location }
1228 | Error::HookError { location, .. }
1229 | Error::ContainerEndMismatch { location, .. }
1230 | Error::UnknownAnchor { location, .. }
1231 | Error::QuotingRequired { location, .. }
1232 | Error::Budget { location, .. }
1233 | Error::CannotBorrowTransformedString { location, .. } => {
1234 if location != &Location::UNKNOWN {
1235 Some(*location)
1236 } else {
1237 None
1238 }
1239 }
1240 Error::InvalidUtf8Input => None,
1241 Error::IOError { cause: _ } => None,
1242 Error::AliasError { locations, .. } => Locations::primary_location(*locations),
1243 Error::WithSnippet { error, .. } => error.location(),
1244 #[cfg(feature = "garde")]
1245 Error::ValidationError { report, locations } => {
1246 report.iter().next().and_then(|(path, _)| {
1247 let key = path_key_from_garde(path);
1248 let (locs, _) = locations.search(&key)?;
1249 let loc = if locs.reference_location != Location::UNKNOWN {
1250 locs.reference_location
1251 } else {
1252 locs.defined_location
1253 };
1254 if loc != Location::UNKNOWN {
1255 Some(loc)
1256 } else {
1257 None
1258 }
1259 })
1260 }
1261 #[cfg(feature = "garde")]
1262 Error::ValidationErrors { errors } => errors.iter().find_map(|e| e.location()),
1263 #[cfg(feature = "validator")]
1264 Error::ValidatorError { errors, locations } => {
1265 collect_validator_issues(errors).first().and_then(|issue| {
1266 let (locs, _) = locations.search(&issue.path)?;
1267 let loc = if locs.reference_location != Location::UNKNOWN {
1268 locs.reference_location
1269 } else {
1270 locs.defined_location
1271 };
1272 if loc != Location::UNKNOWN {
1273 Some(loc)
1274 } else {
1275 None
1276 }
1277 })
1278 }
1279 #[cfg(feature = "validator")]
1280 Error::ValidatorErrors { errors } => errors.iter().find_map(|e| e.location()),
1281 }
1282 }
1283
1284 pub fn locations(&self) -> Option<Locations> {
1294 match self {
1295 Error::Message { location, .. }
1296 | Error::ExternalMessage { location, .. }
1297 | Error::Eof { location }
1298 | Error::MultipleDocuments { location, .. }
1299 | Error::Unexpected { location, .. }
1300 | Error::MergeValueNotMapOrSeqOfMaps { location }
1301 | Error::InvalidBinaryBase64 { location }
1302 | Error::BinaryNotUtf8 { location }
1303 | Error::TaggedScalarCannotDeserializeIntoString { location }
1304 | Error::UnexpectedSequenceEnd { location }
1305 | Error::UnexpectedMappingEnd { location }
1306 | Error::InvalidBooleanStrict { location }
1307 | Error::InvalidCharNull { location }
1308 | Error::InvalidCharNotSingleScalar { location }
1309 | Error::NullIntoString { location }
1310 | Error::BytesNotSupportedMissingBinaryTag { location }
1311 | Error::UnexpectedValueForUnit { location }
1312 | Error::ExpectedEmptyMappingForUnitStruct { location }
1313 | Error::UnexpectedContainerEndWhileSkippingNode { location }
1314 | Error::InternalSeedReusedForMapKey { location }
1315 | Error::ValueRequestedBeforeKey { location }
1316 | Error::ExpectedStringKeyForExternallyTaggedEnum { location }
1317 | Error::ExternallyTaggedEnumExpectedScalarOrMapping { location }
1318 | Error::UnexpectedValueForUnitEnumVariant { location }
1319 | Error::AliasReplayCounterOverflow { location }
1320 | Error::AliasReplayLimitExceeded { location, .. }
1321 | Error::AliasExpansionLimitExceeded { location, .. }
1322 | Error::AliasReplayStackDepthExceeded { location, .. }
1323 | Error::FoldedBlockScalarMustIndentContent { location }
1324 | Error::InternalDepthUnderflow { location }
1325 | Error::InternalRecursionStackEmpty { location }
1326 | Error::RecursiveReferencesRequireWeakTypes { location }
1327 | Error::InvalidScalar { location, .. }
1328 | Error::SerdeInvalidType { location, .. }
1329 | Error::SerdeInvalidValue { location, .. }
1330 | Error::SerdeUnknownVariant { location, .. }
1331 | Error::SerdeUnknownField { location, .. }
1332 | Error::SerdeMissingField { location, .. }
1333 | Error::UnexpectedContainerEndWhileReadingKeyNode { location }
1334 | Error::DuplicateMappingKey { location, .. }
1335 | Error::TaggedEnumMismatch { location, .. }
1336 | Error::SerdeVariantId { location, .. }
1337 | Error::ExpectedMappingEndAfterEnumVariantValue { location }
1338 | Error::HookError { location, .. }
1339 | Error::ContainerEndMismatch { location, .. }
1340 | Error::UnknownAnchor { location, .. }
1341 | Error::QuotingRequired { location, .. }
1342 | Error::Budget { location, .. }
1343 | Error::CannotBorrowTransformedString { location, .. } => Locations::same(location),
1344 Error::InvalidUtf8Input => None,
1345 Error::IOError { .. } => None,
1346 Error::AliasError { locations, .. } => Some(*locations),
1347 Error::WithSnippet { error, .. } => error.locations(),
1348 #[cfg(feature = "garde")]
1349 Error::ValidationError { report, locations } => {
1350 report.iter().next().and_then(|(path, _)| {
1351 let key = path_key_from_garde(path);
1352 search_locations_with_ancestor_fallback(locations, &key)
1353 })
1354 }
1355 #[cfg(feature = "garde")]
1356 Error::ValidationErrors { errors } => errors.first().and_then(Error::locations),
1357 #[cfg(feature = "validator")]
1358 Error::ValidatorError { errors, locations } => collect_validator_issues(errors)
1359 .first()
1360 .and_then(|issue| locations.search(&issue.path).map(|(locs, _)| locs)),
1361 #[cfg(feature = "validator")]
1362 Error::ValidatorErrors { errors } => errors.first().and_then(Error::locations),
1363 }
1364 }
1365
1366 #[cold]
1371 #[inline(never)]
1372 pub(crate) fn from_scan_error(err: ScanError) -> Self {
1373 use crate::location::SpanIndex;
1374 let mark = err.marker();
1375 let location =
1376 Location::new(mark.line(), mark.col() + 1).with_span(crate::location::Span {
1377 offset: mark.index() as SpanIndex,
1378 len: 1,
1379 byte_info: (0, 0),
1380 });
1381
1382 let info = err.info();
1390 if info.to_ascii_lowercase().contains("unknown anchor") {
1391 return Error::UnknownAnchor { location };
1392 }
1393
1394 Error::ExternalMessage {
1395 source: ExternalMessageSource::SaphyrParser,
1396 msg: info.to_owned(),
1397 code: None,
1398 params: Vec::new(),
1399 location,
1400 }
1401 }
1402}
1403
1404fn fmt_error_plain_with_formatter(
1405 f: &mut fmt::Formatter<'_>,
1406 err: &Error,
1407 formatter: &dyn MessageFormatter,
1408) -> fmt::Result {
1409 let err = err.without_snippet();
1410
1411 let msg = formatter.format_message(err);
1412
1413 #[cfg(feature = "garde")]
1417 if matches!(err, Error::ValidationError { .. }) {
1418 return write!(f, "{msg}");
1419 }
1420 #[cfg(feature = "validator")]
1421 if matches!(err, Error::ValidatorError { .. }) {
1422 return write!(f, "{msg}");
1423 }
1424
1425 if let Some(loc) = err.location() {
1426 fmt_with_location(f, formatter.localizer(), msg.as_ref(), &loc)?;
1427 } else {
1428 write!(f, "{msg}")?;
1429 }
1430
1431 #[cfg(feature = "garde")]
1432 if let Error::ValidationErrors { errors } = err {
1433 for err in errors {
1434 writeln!(f)?;
1435 writeln!(f)?;
1436 fmt_error_plain_with_formatter(f, err, formatter)?;
1437 }
1438 }
1439
1440 #[cfg(feature = "validator")]
1441 if let Error::ValidatorErrors { errors } = err {
1442 for err in errors {
1443 writeln!(f)?;
1444 writeln!(f)?;
1445 fmt_error_plain_with_formatter(f, err, formatter)?;
1446 }
1447 }
1448
1449 Ok(())
1450}
1451
1452fn pick_cropped_region<'a>(
1453 regions: &'a [CroppedRegion],
1454 location: &Location,
1455) -> Option<&'a CroppedRegion> {
1456 regions
1457 .iter()
1458 .find(|r| r.covers(location))
1459 .or_else(|| regions.first())
1460}
1461
1462fn fmt_error_rendered(
1463 f: &mut fmt::Formatter<'_>,
1464 err: &Error,
1465 options: RenderOptions<'_>,
1466) -> fmt::Result {
1467 if options.snippets == SnippetMode::Off {
1468 return fmt_error_plain_with_formatter(f, err, options.formatter);
1469 }
1470
1471 match err {
1472 #[cfg(feature = "garde")]
1473 Error::ValidationErrors { errors } => {
1474 let msg = options.formatter.format_message(err);
1475 if !msg.is_empty() {
1476 writeln!(f, "{}", msg)?;
1477 }
1478 let mut first = true;
1479 for err in errors {
1480 if !first {
1481 writeln!(f)?;
1482 writeln!(f)?;
1483 }
1484 first = false;
1485 fmt_error_rendered(f, err, options)?;
1486 }
1487 Ok(())
1488 }
1489
1490 #[cfg(feature = "validator")]
1491 Error::ValidatorErrors { errors } => {
1492 let msg = options.formatter.format_message(err);
1493 if !msg.is_empty() {
1494 writeln!(f, "{}", msg)?;
1495 }
1496 let mut first = true;
1497 for err in errors {
1498 if !first {
1499 writeln!(f)?;
1500 writeln!(f)?;
1501 }
1502 first = false;
1503 fmt_error_rendered(f, err, options)?;
1504 }
1505 Ok(())
1506 }
1507
1508 Error::WithSnippet {
1509 regions,
1510 crop_radius,
1511 error,
1512 } => {
1513 if *crop_radius == 0 {
1514 return fmt_error_plain_with_formatter(f, error, options.formatter);
1516 }
1517
1518 if regions.is_empty() {
1519 return fmt_error_plain_with_formatter(f, error, options.formatter);
1520 }
1521
1522 #[cfg(feature = "garde")]
1525 if let Error::ValidationError { report, locations } = error.as_ref() {
1526 return fmt_validation_error_with_snippets_offset(
1527 f,
1528 options.formatter.localizer(),
1529 report,
1530 locations,
1531 regions,
1532 *crop_radius,
1533 );
1534 }
1535 #[cfg(feature = "garde")]
1536 if let Error::ValidationErrors { errors } = error.as_ref() {
1537 let msg = options.formatter.format_message(error);
1538 if !msg.is_empty() {
1539 writeln!(f, "{}", msg)?;
1540 }
1541 let mut first = true;
1542 for err in errors {
1543 if !first {
1544 writeln!(f)?;
1545 writeln!(f)?;
1546 }
1547 first = false;
1548 fmt_error_with_snippets_offset(
1549 f,
1550 err,
1551 regions,
1552 *crop_radius,
1553 options.formatter,
1554 )?;
1555 }
1556 return Ok(());
1557 }
1558
1559 #[cfg(feature = "validator")]
1560 if let Error::ValidatorError { errors, locations } = error.as_ref() {
1561 return fmt_validator_error_with_snippets_offset(
1562 f,
1563 options.formatter.localizer(),
1564 errors,
1565 locations,
1566 regions,
1567 *crop_radius,
1568 );
1569 }
1570 #[cfg(feature = "validator")]
1571 if let Error::ValidatorErrors { errors } = error.as_ref() {
1572 let msg = options.formatter.format_message(error);
1573 if !msg.is_empty() {
1574 writeln!(f, "{}", msg)?;
1575 }
1576 let mut first = true;
1577 for err in errors {
1578 if !first {
1579 writeln!(f)?;
1580 writeln!(f)?;
1581 }
1582 first = false;
1583 fmt_error_with_snippets_offset(
1584 f,
1585 err,
1586 regions,
1587 *crop_radius,
1588 options.formatter,
1589 )?;
1590 }
1591 return Ok(());
1592 }
1593
1594 let Some(location) = error.location() else {
1597 return fmt_error_plain_with_formatter(f, error, options.formatter);
1598 };
1599 if location == Location::UNKNOWN {
1600 return fmt_error_plain_with_formatter(f, error, options.formatter);
1601 }
1602
1603 let l10n = options.formatter.localizer();
1604
1605 let region = match pick_cropped_region(regions, &location) {
1606 Some(r) => r,
1607 None => return fmt_error_plain_with_formatter(f, error, options.formatter),
1608 };
1609
1610 let dual_locations = error.locations().filter(|locs| {
1612 locs.reference_location != Location::UNKNOWN
1613 && locs.defined_location != Location::UNKNOWN
1614 && locs.reference_location != locs.defined_location
1615 });
1616
1617 let mut msg = options.formatter.format_message(error);
1618
1619 if dual_locations.is_some()
1623 && let Error::AliasError { locations, .. } = error.as_ref()
1624 {
1625 let suffix = l10n.alias_defined_at(locations.defined_location);
1626 if let Some(stripped) = msg.as_ref().strip_suffix(&suffix) {
1627 msg = Cow::Owned(stripped.to_string());
1628 }
1629 }
1630
1631 if let Some(locs) = dual_locations {
1632 let ref_loc = locs.reference_location;
1633 let def_loc = locs.defined_location;
1634
1635 let used_region = pick_cropped_region(regions, &ref_loc).unwrap_or(region);
1636 let label = l10n.value_used_here();
1637 let ctx = crate::de_snipped::Snippet::new(
1638 used_region.text.as_str(),
1639 label.as_ref(),
1640 *crop_radius,
1641 )
1642 .with_offset(used_region.start_line);
1643 ctx.fmt_or_fallback(f, Level::ERROR, l10n, msg.as_ref(), &ref_loc)?;
1644
1645 let def_region = pick_cropped_region(regions, &def_loc).unwrap_or(region);
1646 writeln!(f)?;
1647 writeln!(f, "{}", l10n.value_comes_from_the_anchor(def_loc))?;
1648 fmt_snippet_window_offset_or_fallback(
1649 f,
1650 l10n,
1651 &def_loc,
1652 def_region.text.as_str(),
1653 def_region.start_line,
1654 l10n.defined_window().as_ref(),
1655 *crop_radius,
1656 )?;
1657 Ok(())
1658 } else {
1659 let ctx =
1661 crate::de_snipped::Snippet::new(region.text.as_str(), "<input>", *crop_radius)
1662 .with_offset(region.start_line);
1663 ctx.fmt_or_fallback(f, Level::ERROR, l10n, msg.as_ref(), &location)
1664 }
1665 }
1666 _ => fmt_error_plain_with_formatter(f, err, options.formatter),
1667 }
1668}
1669
1670#[cfg(any(feature = "garde", feature = "validator"))]
1671fn search_locations_with_ancestor_fallback(
1672 locations: &PathMap,
1673 path: &PathKey,
1674) -> Option<Locations> {
1675 if let Some((locs, _)) = locations.search(path) {
1676 return Some(locs);
1677 }
1678
1679 let mut p = path.parent();
1680 while let Some(cur) = p {
1681 if let Some((locs, _)) = locations.search(&cur) {
1682 return Some(locs);
1683 }
1684 p = cur.parent();
1685 }
1686
1687 None
1688}
1689
1690impl fmt::Display for Error {
1691 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1692 fmt_error_rendered(f, self, RenderOptions::default())
1693 }
1694}
1695
1696#[cfg(feature = "garde")]
1697fn fmt_validation_error_with_snippets_offset(
1698 f: &mut fmt::Formatter<'_>,
1699 l10n: &dyn Localizer,
1700 report: &garde::Report,
1701 locations: &PathMap,
1702 regions: &[CroppedRegion],
1703 crop_radius: usize,
1704) -> fmt::Result {
1705 let mut first = true;
1706 for (path, entry) in report.iter() {
1707 if !first {
1708 writeln!(f)?;
1709 }
1710 first = false;
1711
1712 let path_key = path_key_from_garde(path);
1713 let original_leaf = path_key
1714 .leaf_string()
1715 .unwrap_or_else(|| l10n.root_path_label().into_owned());
1716
1717 let (locs, resolved_leaf) = locations
1718 .search(&path_key)
1719 .unwrap_or((Locations::UNKNOWN, original_leaf));
1720
1721 let ref_loc = locs.reference_location;
1722 let def_loc = locs.defined_location;
1723
1724 let resolved_path = format_path_with_resolved_leaf(&path_key, &resolved_leaf);
1725 let entry_raw = entry.to_string();
1726 let entry = l10n
1727 .override_external_message(ExternalMessage {
1728 source: ExternalMessageSource::Garde,
1729 original: entry_raw.as_str(),
1730 code: None,
1731 params: &[],
1732 })
1733 .unwrap_or(Cow::Borrowed(entry_raw.as_str()));
1734 let base_msg = l10n.validation_base_message(entry.as_ref(), &resolved_path);
1735
1736 match (ref_loc, def_loc) {
1737 (Location::UNKNOWN, Location::UNKNOWN) => {
1738 write!(f, "{base_msg}")?;
1739 }
1740 (r, d) if r != Location::UNKNOWN && (d == Location::UNKNOWN || d == r) => {
1741 let label = l10n.defined();
1742 if let Some(region) = pick_cropped_region(regions, &r) {
1743 let ctx = crate::de_snipped::Snippet::new(
1744 region.text.as_str(),
1745 label.as_ref(),
1746 crop_radius,
1747 )
1748 .with_offset(region.start_line);
1749 ctx.fmt_or_fallback(f, Level::ERROR, l10n, &base_msg, &r)?;
1750 } else {
1751 fmt_with_location(f, l10n, &base_msg, &r)?;
1752 }
1753 }
1754 (r, d) if r == Location::UNKNOWN && d != Location::UNKNOWN => {
1755 let label = l10n.defined_here();
1756 if let Some(region) = pick_cropped_region(regions, &d) {
1757 let ctx = crate::de_snipped::Snippet::new(
1758 region.text.as_str(),
1759 label.as_ref(),
1760 crop_radius,
1761 )
1762 .with_offset(region.start_line);
1763 ctx.fmt_or_fallback(f, Level::ERROR, l10n, &base_msg, &d)?;
1764 } else {
1765 fmt_with_location(f, l10n, &base_msg, &d)?;
1766 }
1767 }
1768 (r, d) => {
1769 let label = l10n.value_used_here();
1770 let invalid_here = l10n.invalid_here(&base_msg);
1771 if let Some(region) = pick_cropped_region(regions, &r) {
1772 let ctx = crate::de_snipped::Snippet::new(
1773 region.text.as_str(),
1774 label.as_ref(),
1775 crop_radius,
1776 )
1777 .with_offset(region.start_line);
1778 ctx.fmt_or_fallback(f, Level::ERROR, l10n, &invalid_here, &r)?;
1779 } else {
1780 fmt_with_location(f, l10n, &invalid_here, &r)?;
1781 }
1782 writeln!(f)?;
1783 writeln!(f, "{}", l10n.value_comes_from_the_anchor(d))?;
1784 if let Some(region) = pick_cropped_region(regions, &d) {
1785 crate::de_snipped::fmt_snippet_window_offset_or_fallback(
1786 f,
1787 l10n,
1788 &d,
1789 region.text.as_str(),
1790 region.start_line,
1791 l10n.defined_window().as_ref(),
1792 crop_radius,
1793 )?;
1794 } else {
1795 fmt_with_location(f, l10n, l10n.defined_window().as_ref(), &d)?;
1796 }
1797 }
1798 }
1799 }
1800 Ok(())
1801}
1802
1803#[cfg(feature = "validator")]
1804fn fmt_validator_error_with_snippets_offset(
1805 f: &mut fmt::Formatter<'_>,
1806 l10n: &dyn Localizer,
1807 errors: &ValidationErrors,
1808 locations: &PathMap,
1809 regions: &[CroppedRegion],
1810 crop_radius: usize,
1811) -> fmt::Result {
1812 let entries = collect_validator_issues(errors);
1813 let mut first = true;
1814
1815 for issue in entries {
1816 if !first {
1817 writeln!(f)?;
1818 }
1819 first = false;
1820
1821 let original_leaf = issue
1822 .path
1823 .leaf_string()
1824 .unwrap_or_else(|| l10n.root_path_label().into_owned());
1825 let (locs, resolved_leaf) = locations
1826 .search(&issue.path)
1827 .unwrap_or((Locations::UNKNOWN, original_leaf));
1828
1829 let resolved_path = format_path_with_resolved_leaf(&issue.path, &resolved_leaf);
1830 let entry = issue.display_entry_overridden(l10n, ExternalMessageSource::Validator);
1831 let base_msg = l10n.validation_base_message(&entry, &resolved_path);
1832
1833 match (locs.reference_location, locs.defined_location) {
1834 (Location::UNKNOWN, Location::UNKNOWN) => {
1835 write!(f, "{base_msg}")?;
1836 }
1837 (r, d) if r != Location::UNKNOWN && (d == Location::UNKNOWN || d == r) => {
1838 let label = l10n.defined();
1839 if let Some(region) = pick_cropped_region(regions, &r) {
1840 let ctx = crate::de_snipped::Snippet::new(
1841 region.text.as_str(),
1842 label.as_ref(),
1843 crop_radius,
1844 )
1845 .with_offset(region.start_line);
1846 ctx.fmt_or_fallback(f, Level::ERROR, l10n, &base_msg, &r)?;
1847 } else {
1848 fmt_with_location(f, l10n, &base_msg, &r)?;
1849 }
1850 }
1851 (r, d) if r == Location::UNKNOWN && d != Location::UNKNOWN => {
1852 let label = l10n.defined_here();
1853 if let Some(region) = pick_cropped_region(regions, &d) {
1854 let ctx = crate::de_snipped::Snippet::new(
1855 region.text.as_str(),
1856 label.as_ref(),
1857 crop_radius,
1858 )
1859 .with_offset(region.start_line);
1860 ctx.fmt_or_fallback(f, Level::ERROR, l10n, &base_msg, &d)?;
1861 } else {
1862 fmt_with_location(f, l10n, &base_msg, &d)?;
1863 }
1864 }
1865 (r, d) => {
1866 let label = l10n.value_used_here();
1867 let invalid_here = l10n.invalid_here(&base_msg);
1868 if let Some(region) = pick_cropped_region(regions, &r) {
1869 let ctx = crate::de_snipped::Snippet::new(
1870 region.text.as_str(),
1871 label.as_ref(),
1872 crop_radius,
1873 )
1874 .with_offset(region.start_line);
1875 ctx.fmt_or_fallback(f, Level::ERROR, l10n, &invalid_here, &r)?;
1876 } else {
1877 fmt_with_location(f, l10n, &invalid_here, &r)?;
1878 }
1879 writeln!(f)?;
1880 writeln!(f, "{}", l10n.value_comes_from_the_anchor(d))?;
1881 if let Some(region) = pick_cropped_region(regions, &d) {
1882 crate::de_snipped::fmt_snippet_window_offset_or_fallback(
1883 f,
1884 l10n,
1885 &d,
1886 region.text.as_str(),
1887 region.start_line,
1888 l10n.defined_window().as_ref(),
1889 crop_radius,
1890 )?;
1891 } else {
1892 fmt_with_location(f, l10n, l10n.defined_window().as_ref(), &d)?;
1893 }
1894 }
1895 }
1896 }
1897
1898 Ok(())
1899}
1900
1901#[cfg(any(feature = "garde", feature = "validator"))]
1902fn fmt_error_with_snippets_offset(
1903 f: &mut fmt::Formatter<'_>,
1904 err: &Error,
1905 regions: &[CroppedRegion],
1906 crop_radius: usize,
1907 formatter: &dyn MessageFormatter,
1908) -> fmt::Result {
1909 if crop_radius == 0 {
1910 return fmt_error_plain_with_formatter(f, err, formatter);
1911 }
1912
1913 if let Error::WithSnippet { .. } = err {
1915 return fmt_error_rendered(f, err, RenderOptions::new(formatter));
1916 }
1917
1918 #[cfg(feature = "garde")]
1919 if let Error::ValidationError { report, locations } = err {
1920 return fmt_validation_error_with_snippets_offset(
1921 f,
1922 formatter.localizer(),
1923 report,
1924 locations,
1925 regions,
1926 crop_radius,
1927 );
1928 }
1929
1930 #[cfg(feature = "validator")]
1931 if let Error::ValidatorError { errors, locations } = err {
1932 return fmt_validator_error_with_snippets_offset(
1933 f,
1934 formatter.localizer(),
1935 errors,
1936 locations,
1937 regions,
1938 crop_radius,
1939 );
1940 }
1941
1942 let msg = formatter.format_message(err);
1943 let Some(location) = err.location() else {
1944 return write!(f, "{msg}");
1945 };
1946 if location == Location::UNKNOWN {
1947 return write!(f, "{msg}");
1948 }
1949
1950 let Some(region) = pick_cropped_region(regions, &location) else {
1951 return fmt_with_location(f, formatter.localizer(), msg.as_ref(), &location);
1952 };
1953 let ctx = crate::de_snipped::Snippet::new(region.text.as_str(), "<input>", crop_radius)
1954 .with_offset(region.start_line);
1955 ctx.fmt_or_fallback(
1956 f,
1957 Level::ERROR,
1958 formatter.localizer(),
1959 msg.as_ref(),
1960 &location,
1961 )
1962}
1963
1964#[cfg(feature = "validator")]
1965pub(crate) fn collect_validator_issues(errors: &ValidationErrors) -> Vec<ValidationIssue> {
1966 let mut out = Vec::new();
1967 let root = PathKey::empty();
1968 collect_validator_issues_inner(errors, &root, &mut out);
1969 out
1970}
1971
1972#[cfg(feature = "validator")]
1973fn collect_validator_issues_inner(
1974 errors: &ValidationErrors,
1975 path: &PathKey,
1976 out: &mut Vec<ValidationIssue>,
1977) {
1978 for (field, kind) in errors.errors() {
1979 let field_path = path.clone().join(field.as_ref());
1980 match kind {
1981 ValidationErrorsKind::Field(entries) => {
1982 for entry in entries {
1983 let mut params = Vec::new();
1984 for (k, v) in &entry.params {
1985 params.push((k.to_string(), v.to_string()));
1986 }
1987
1988 out.push(ValidationIssue {
1989 path: field_path.clone(),
1990 code: entry.code.to_string(),
1991 message: entry.message.as_ref().map(|m| m.to_string()),
1992 params,
1993 });
1994 }
1995 }
1996 ValidationErrorsKind::Struct(inner) => {
1997 collect_validator_issues_inner(inner, &field_path, out);
1998 }
1999 ValidationErrorsKind::List(list) => {
2000 for (idx, inner) in list {
2001 let index_path = field_path.clone().join(*idx);
2002 collect_validator_issues_inner(inner, &index_path, out);
2003 }
2004 }
2005 }
2006 }
2007}
2008
2009#[cfg(feature = "garde")]
2010pub(crate) fn collect_garde_issues(report: &garde::Report) -> Vec<ValidationIssue> {
2011 let mut out = Vec::new();
2012 for (path, entry) in report.iter() {
2013 out.push(ValidationIssue {
2014 path: path_key_from_garde(path),
2015 code: "garde".to_string(),
2016 message: Some(entry.message().to_string()),
2017 params: Vec::new(),
2018 });
2019 }
2020 out
2021}
2022impl std::error::Error for Error {}
2023
2024fn maybe_attach_fallback_location(mut err: Error) -> Error {
2026 let loc = MISSING_FIELD_FALLBACK.with(|c| c.get());
2027 if let Some(loc) = loc
2028 && loc != Location::UNKNOWN
2029 {
2030 err = err.with_location(loc);
2031 }
2032 err
2033}
2034
2035impl de::Error for Error {
2036 fn custom<T: fmt::Display>(msg: T) -> Self {
2037 Error::msg(msg.to_string())
2041 }
2042
2043 fn invalid_type(unexp: de::Unexpected, exp: &dyn de::Expected) -> Self {
2044 maybe_attach_fallback_location(Error::SerdeInvalidType {
2046 unexpected: unexp.to_string(),
2047 expected: exp.to_string(),
2048 location: Location::UNKNOWN,
2049 })
2050 }
2051
2052 fn invalid_value(unexp: de::Unexpected, exp: &dyn de::Expected) -> Self {
2053 maybe_attach_fallback_location(Error::SerdeInvalidValue {
2054 unexpected: unexp.to_string(),
2055 expected: exp.to_string(),
2056 location: Location::UNKNOWN,
2057 })
2058 }
2059
2060 fn unknown_variant(variant: &str, expected: &'static [&'static str]) -> Self {
2061 maybe_attach_fallback_location(Error::SerdeUnknownVariant {
2062 variant: variant.to_owned(),
2063 expected: expected.to_vec(),
2064 location: Location::UNKNOWN,
2065 })
2066 }
2067
2068 fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
2069 maybe_attach_fallback_location(Error::SerdeUnknownField {
2070 field: field.to_owned(),
2071 expected: expected.to_vec(),
2072 location: Location::UNKNOWN,
2073 })
2074 }
2075
2076 fn missing_field(field: &'static str) -> Self {
2077 maybe_attach_fallback_location(Error::SerdeMissingField {
2078 field,
2079 location: Location::UNKNOWN,
2080 })
2081 }
2082}
2083
2084#[cold]
2094#[inline(never)]
2095fn fmt_with_location(
2096 f: &mut fmt::Formatter<'_>,
2097 l10n: &dyn Localizer,
2098 msg: &str,
2099 location: &Location,
2100) -> fmt::Result {
2101 let out = l10n.attach_location(Cow::Borrowed(msg), *location);
2102 write!(f, "{out}")
2103}
2104
2105#[cold]
2116#[inline(never)]
2117pub(crate) fn budget_error(breach: BudgetBreach) -> Error {
2118 Error::Budget {
2119 breach,
2120 location: Location::UNKNOWN,
2121 }
2122}
2123
2124#[cfg(test)]
2125mod tests {
2126 use super::*;
2127
2128 #[test]
2129 fn locations_for_basic_error_duplicates_location() {
2130 let l = Location::new(3, 7);
2131 let err = Error::Message {
2132 msg: "x".to_owned(),
2133 location: l,
2134 };
2135 assert_eq!(
2136 err.locations(),
2137 Some(Locations {
2138 reference_location: l,
2139 defined_location: l,
2140 })
2141 );
2142 }
2143
2144 #[test]
2145 fn locations_for_io_error_is_unknown() {
2146 let err = Error::IOError {
2147 cause: std::io::Error::other("x"),
2148 };
2149 assert_eq!(err.locations(), None);
2150 }
2151
2152 #[test]
2153 fn alias_error_returns_both_locations() {
2154 let ref_loc = Location::new(5, 10);
2155 let def_loc = Location::new(2, 3);
2156 let err = Error::AliasError {
2157 msg: "test error".to_owned(),
2158 locations: Locations {
2159 reference_location: ref_loc,
2160 defined_location: def_loc,
2161 },
2162 };
2163
2164 assert_eq!(err.location(), Some(ref_loc));
2166
2167 assert_eq!(
2169 err.locations(),
2170 Some(Locations {
2171 reference_location: ref_loc,
2172 defined_location: def_loc,
2173 })
2174 );
2175 }
2176
2177 #[test]
2178 fn alias_error_display_shows_both_locations() {
2179 let ref_loc = Location::new(5, 10);
2180 let def_loc = Location::new(2, 3);
2181 let err = Error::AliasError {
2182 msg: "invalid value".to_owned(),
2183 locations: Locations {
2184 reference_location: ref_loc,
2185 defined_location: def_loc,
2186 },
2187 };
2188
2189 let display = err.to_string();
2190 assert!(display.contains("invalid value"));
2191 assert!(display.contains("line 5"));
2192 assert!(display.contains("column 10"));
2193 assert!(display.contains("line 2"));
2194 assert!(display.contains("column 3"));
2195 }
2196
2197 #[test]
2198 fn alias_error_display_with_same_locations() {
2199 let loc = Location::new(3, 7);
2200 let err = Error::AliasError {
2201 msg: "test".to_owned(),
2202 locations: Locations {
2203 reference_location: loc,
2204 defined_location: loc,
2205 },
2206 };
2207
2208 let display = err.to_string();
2209 assert!(display.contains("line 3"));
2211 assert!(display.contains("column 7"));
2212 assert!(!display.contains("defined at"));
2214 }
2215
2216 #[test]
2217 fn with_snippet_counts_trailing_empty_line_for_end_line() {
2218 let text = "a\n";
2220 let err = Error::Message {
2221 msg: "x".to_owned(),
2222 location: Location::new(2, 1),
2223 };
2224
2225 let wrapped = err.with_snippet(text, 50);
2226 let Error::WithSnippet { regions, .. } = wrapped else {
2227 panic!("expected WithSnippet wrapper");
2228 };
2229 assert_eq!(regions.len(), 1);
2230 assert_eq!(regions[0].start_line, 1);
2231 assert_eq!(regions[0].end_line, 2);
2232 }
2233
2234 #[test]
2235 fn with_snippet_offset_counts_trailing_empty_line_for_end_line() {
2236 let text = "a\n";
2238 let err = Error::Message {
2239 msg: "x".to_owned(),
2240 location: Location::new(11, 1),
2241 };
2242
2243 let wrapped = err.with_snippet_offset(text, 10, 50);
2244 let Error::WithSnippet { regions, .. } = wrapped else {
2245 panic!("expected WithSnippet wrapper");
2246 };
2247 assert_eq!(regions.len(), 1);
2248 assert_eq!(regions[0].start_line, 10);
2249 assert_eq!(regions[0].end_line, 11);
2250 }
2251
2252 #[cfg(feature = "validator")]
2253 #[test]
2254 fn locations_for_validator_error_uses_first_entry() {
2255 use validator::Validate;
2256
2257 #[derive(Debug, Validate)]
2258 struct Cfg {
2259 #[validate(length(min = 2))]
2260 second_string: String,
2261 }
2262
2263 let cfg = Cfg {
2264 second_string: "x".to_owned(),
2265 };
2266 let errors = cfg.validate().expect_err("validation error expected");
2267
2268 let referenced_loc = Location::new(3, 15);
2269 let defined_loc = Location::new(2, 18);
2270
2271 let mut locations = PathMap::new();
2272 locations.insert(
2273 PathKey::empty().join("secondString"),
2274 Locations {
2275 reference_location: referenced_loc,
2276 defined_location: defined_loc,
2277 },
2278 );
2279
2280 let err = Error::ValidatorError { errors, locations };
2281 assert_eq!(
2282 err.locations(),
2283 Some(Locations {
2284 reference_location: referenced_loc,
2285 defined_location: defined_loc,
2286 })
2287 );
2288 }
2289
2290 #[test]
2291 fn nested_snippet_preserves_custom_formatter() {
2292 struct Custom;
2293 impl MessageFormatter for Custom {
2294 fn localizer(&self) -> &dyn Localizer {
2295 &DEFAULT_ENGLISH_LOCALIZER
2296 }
2297 fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
2298 match err {
2299 Error::Message { msg, .. } => Cow::Owned(format!("CUSTOM: {}", msg.as_str())),
2300 _ => Cow::Borrowed(""),
2301 }
2302 }
2303 }
2304 let loc = Location::new(1, 1);
2305 let base = Error::Message {
2306 msg: "original".to_string(),
2307 location: loc,
2308 };
2309 let text = "input";
2310 let start_line = 1;
2311 let radius = 1;
2312 let inner = base.with_snippet_offset(text, start_line, radius);
2313 let outer = inner.with_snippet_offset(text, start_line, radius);
2314 let rendered = outer.render_with_options(RenderOptions::new(&Custom));
2315 assert!(rendered.contains("CUSTOM: original"));
2316 }
2317
2318 #[test]
2319 fn alias_error_dual_snippet_rendering() {
2320 let yaml = r#"config:
2322 anchor: &myval 42
2323 other: stuff
2324 more: data
2325 use_it: *myval
2326"#;
2327 let ref_loc = Location::new(5, 11);
2329 let def_loc = Location::new(2, 11);
2331
2332 let err = Error::AliasError {
2333 msg: "invalid value type".to_owned(),
2334 locations: Locations {
2335 reference_location: ref_loc,
2336 defined_location: def_loc,
2337 },
2338 };
2339
2340 let wrapped = err.with_snippet(yaml, 5);
2342 let rendered = wrapped.render();
2343
2344 assert!(
2346 rendered.contains("invalid value type"),
2347 "rendered: {}",
2348 rendered
2349 );
2350
2351 assert!(
2354 !rendered.contains("(defined at line"),
2355 "did not expect alias defined-at suffix when secondary window is present: {}",
2356 rendered
2357 );
2358 assert!(
2360 rendered.contains("the value is used here") || rendered.contains("use_it"),
2361 "rendered should show reference location context: {}",
2362 rendered
2363 );
2364 assert!(
2366 rendered.contains("defined here") || rendered.contains("anchor"),
2367 "rendered should show defined location context: {}",
2368 rendered
2369 );
2370 assert!(
2372 rendered.contains("5") || rendered.contains("use_it"),
2373 "rendered should reference line 5: {}",
2374 rendered
2375 );
2376 assert!(
2377 rendered.contains("2") || rendered.contains("anchor"),
2378 "rendered should reference line 2: {}",
2379 rendered
2380 );
2381 }
2382
2383 #[test]
2384 fn alias_error_same_location_single_snippet() {
2385 let yaml = "value: &anchor 42\n";
2386 let loc = Location::new(1, 8);
2387
2388 let err = Error::AliasError {
2389 msg: "test error".to_owned(),
2390 locations: Locations {
2391 reference_location: loc,
2392 defined_location: loc,
2393 },
2394 };
2395
2396 let wrapped = err.with_snippet(yaml, 5);
2397 let rendered = wrapped.render();
2398
2399 assert!(rendered.contains("test error"), "rendered: {}", rendered);
2401 assert!(
2403 !rendered.contains("defined here"),
2404 "should not show 'defined here' when locations are same: {}",
2405 rendered
2406 );
2407 assert!(
2408 !rendered.contains("the value is used here"),
2409 "should not show 'value used here' when locations are same: {}",
2410 rendered
2411 );
2412 }
2413}