1use crate::Location;
2use crate::de_error::{Error, MessageFormatter, UserMessageFormatter};
3use crate::localizer::{ExternalMessage, Localizer};
4
5use std::borrow::Cow;
6
7#[cfg(any(feature = "garde", feature = "validator"))]
8use crate::{
9 Locations, localizer::ExternalMessageSource, path_map::format_path_with_resolved_leaf,
10};
11
12#[derive(Debug, Default, Clone, Copy)]
18pub struct DefaultMessageFormatter;
19
20pub type DeveloperMessageFormatter = DefaultMessageFormatter;
22
23fn default_format_message<'a>(formatter: &dyn MessageFormatter, err: &'a Error) -> Cow<'a, str> {
24 match err {
25 Error::WithSnippet { error, .. } => default_format_message(formatter, error),
26 Error::ExternalMessage {
27 source,
28 msg,
29 code,
30 params,
31 ..
32 } => {
33 let l10n = formatter.localizer();
34 l10n.override_external_message(ExternalMessage {
35 source: *source,
36 original: msg.as_str(),
37 code: code.as_deref(),
38 params,
39 })
40 .unwrap_or(Cow::Borrowed(msg.as_str()))
41 }
42 Error::Message { msg, .. }
43 | Error::HookError { msg, .. }
44 | Error::SerdeVariantId { msg, .. } => Cow::Borrowed(msg.as_str()),
45 Error::UnresolvedProperty { name, .. } => Cow::Owned(format!("missing property `{name}`")),
46 Error::InvalidPropertyName { name, .. } => Cow::Owned(format!("Invalid name: '{name}'")),
47 Error::Eof { .. } => Cow::Borrowed("unexpected end of input"),
48 Error::MultipleDocuments { hint, .. } => {
49 Cow::Owned(format!("multiple YAML documents detected; {hint}"))
50 }
51 Error::Unexpected { expected, .. } => {
52 Cow::Owned(format!("unexpected event: expected {expected}"))
53 }
54 Error::MergeValueNotMapOrSeqOfMaps { .. } => {
55 Cow::Borrowed("YAML merge value must be mapping or sequence of mappings")
56 }
57 Error::InvalidBinaryBase64 { .. } => Cow::Borrowed("invalid !!binary base64"),
58 Error::InvalidUtf8Input => Cow::Borrowed("input is not valid UTF-8"),
59 Error::BinaryNotUtf8 { .. } => Cow::Borrowed(
60 "!!binary scalar is not valid UTF-8 so cannot be stored into string. \
61 If you just use !!binary for documentation/annotation, set ignore_binary_tag_for_string in Options",
62 ),
63 Error::TaggedScalarCannotDeserializeIntoString { .. } => {
64 Cow::Borrowed("cannot deserialize tagged scalar into string")
65 }
66 Error::UnexpectedSequenceEnd { .. } => Cow::Borrowed("unexpected sequence end"),
67 Error::UnexpectedMappingEnd { .. } => Cow::Borrowed("unexpected mapping end"),
68 Error::InvalidBooleanStrict { .. } => {
69 Cow::Borrowed("invalid boolean (strict mode expects true/false)")
70 }
71 Error::InvalidCharNull { .. } => {
72 Cow::Borrowed("invalid char: cannot deserialize null; use Option<char>")
73 }
74 Error::InvalidCharNotSingleScalar { .. } => {
75 Cow::Borrowed("invalid char: expected a single Unicode scalar value")
76 }
77 Error::NullIntoString { .. } => {
78 Cow::Borrowed("cannot deserialize null into string; use Option<String>")
79 }
80 Error::BytesNotSupportedMissingBinaryTag { .. } => {
81 Cow::Borrowed("bytes not supported (missing !!binary tag)")
82 }
83 Error::UnexpectedValueForUnit { .. } => Cow::Borrowed("unexpected value for unit"),
84 Error::ExpectedEmptyMappingForUnitStruct { .. } => {
85 Cow::Borrowed("expected empty mapping for unit struct")
86 }
87 Error::UnexpectedContainerEndWhileSkippingNode { .. } => {
88 Cow::Borrowed("unexpected container end while skipping node")
89 }
90 Error::InternalSeedReusedForMapKey { .. } => {
91 Cow::Borrowed("internal error: seed reused for map key")
92 }
93 Error::ValueRequestedBeforeKey { .. } => Cow::Borrowed("value requested before key"),
94 Error::ExpectedStringKeyForExternallyTaggedEnum { .. } => {
95 Cow::Borrowed("expected string key for externally tagged enum")
96 }
97 Error::ExternallyTaggedEnumExpectedScalarOrMapping { .. } => {
98 Cow::Borrowed("externally tagged enum expected scalar or mapping")
99 }
100 Error::UnexpectedValueForUnitEnumVariant { .. } => {
101 Cow::Borrowed("unexpected value for unit enum variant")
102 }
103 Error::AliasReplayCounterOverflow { .. } => Cow::Borrowed("alias replay counter overflow"),
104 Error::AliasReplayLimitExceeded {
105 total_replayed_events,
106 max_total_replayed_events,
107 ..
108 } => Cow::Owned(format!(
109 "alias replay limit exceeded: total_replayed_events={total_replayed_events} > {max_total_replayed_events}"
110 )),
111 Error::AliasExpansionLimitExceeded {
112 anchor_id,
113 expansions,
114 max_expansions_per_anchor,
115 ..
116 } => Cow::Owned(format!(
117 "alias expansion limit exceeded for anchor id {anchor_id}: {expansions} > {max_expansions_per_anchor}"
118 )),
119 Error::AliasReplayStackDepthExceeded {
120 depth, max_depth, ..
121 } => Cow::Owned(format!(
122 "alias replay stack depth exceeded: depth={depth} > {max_depth}"
123 )),
124 Error::FoldedBlockScalarMustIndentContent { .. } => {
125 Cow::Borrowed("folded block scalars must indent their content")
126 }
127 Error::InternalDepthUnderflow { .. } => Cow::Borrowed("internal depth underflow"),
128 Error::InternalRecursionStackEmpty { .. } => {
129 Cow::Borrowed("internal recursion stack empty")
130 }
131 Error::RecursiveReferencesRequireWeakTypes { .. } => {
132 Cow::Borrowed("recursive references require weak recursion types")
133 }
134 Error::InvalidScalar { ty, .. } => Cow::Owned(format!("invalid {ty}")),
135 Error::SerdeInvalidType {
136 unexpected,
137 expected,
138 ..
139 } => Cow::Owned(format!("invalid type: {unexpected}, expected {expected}")),
140 Error::SerdeInvalidValue {
141 unexpected,
142 expected,
143 ..
144 } => Cow::Owned(format!("invalid value: {unexpected}, expected {expected}")),
145 Error::SerdeUnknownVariant {
146 variant, expected, ..
147 } => Cow::Owned(format!(
148 "unknown variant `{variant}`, expected one of {}",
149 expected.join(", ")
150 )),
151 Error::SerdeUnknownField {
152 field, expected, ..
153 } => Cow::Owned(format!(
154 "unknown field `{field}`, expected one of {}",
155 expected.join(", ")
156 )),
157 Error::SerdeMissingField { field, .. } => Cow::Owned(format!("missing field `{field}`")),
158 Error::UnexpectedContainerEndWhileReadingKeyNode { .. } => {
159 Cow::Borrowed("unexpected container end while reading key")
160 }
161 Error::DuplicateMappingKey { key, .. } => match key {
162 Some(k) => Cow::Owned(format!(
163 "duplicate mapping key: {k}, set DuplicateKeyPolicy in Options if acceptable"
164 )),
165 None => Cow::Borrowed(
166 "duplicate mapping key, set DuplicateKeyPolicy in Options if acceptable",
167 ),
168 },
169 Error::TaggedEnumMismatch { tagged, target, .. } => Cow::Owned(format!(
170 "tagged enum `{tagged}` does not match target enum `{target}`",
171 )),
172 Error::ExpectedMappingEndAfterEnumVariantValue { .. } => {
173 Cow::Borrowed("expected end of mapping after enum variant value")
174 }
175 Error::ContainerEndMismatch { .. } => Cow::Borrowed("list or mapping end with no start"),
176 Error::UnknownAnchor { .. } => Cow::Borrowed("alias references unknown anchor"),
177 Error::CyclicInclude { id, stack, .. } => {
178 let mut full_msg = format!("cyclic include detected: {id}");
179 if !stack.is_empty() {
180 full_msg.push_str("\nwhile processing include from ");
181 full_msg.push_str(&stack.join(" -> "));
182 }
183 Cow::Owned(full_msg)
184 }
185 Error::UnsupportedIncludeForm { .. } => {
186 Cow::Borrowed("!include currently only supports the scalar form: !include <path>")
187 }
188 Error::ResolverError {
189 target,
190 error,
191 stack,
192 ..
193 } => {
194 let mut full_msg = format!("failed to resolve include {target:?}");
195 if !stack.is_empty() {
196 full_msg.push_str("\nwhile processing include from ");
197 full_msg.push_str(&stack.join(" -> "));
198 }
199 full_msg.push('\n');
200 let msg = match error {
201 crate::input_source::IncludeResolveError::Io(e) => e.to_string(),
202 crate::input_source::IncludeResolveError::Message(m) => m.clone(),
203 crate::input_source::IncludeResolveError::SizeLimitExceeded(size, limit) => {
204 format!("include size {size} bytes exceeds remaining size limit {limit} bytes")
205 }
206 crate::input_source::IncludeResolveError::FileInclude(problem) => {
207 match &**problem {
208 crate::input_source::ResolveProblem::ResolveFailed {
209 spec,
210 base_dir,
211 err,
212 } => {
213 format!(
214 "failed to resolve include '{}' from '{}': {}",
215 spec, base_dir, err
216 )
217 }
218 crate::input_source::ResolveProblem::TargetNotRegularFile { target } => {
219 format!("include target '{}' is not a regular file", target)
220 }
221 crate::input_source::ResolveProblem::TargetIsRootFile { spec } => {
222 format!(
223 "include target '{}' resolves to the configured root file itself",
224 spec
225 )
226 }
227 crate::input_source::ResolveProblem::ParentIdNotAbsoluteCanonical {
228 parent_id,
229 } => {
230 format!(
231 "SafeFileResolver expected parent include id to be an absolute canonical path, got '{}'",
232 parent_id
233 )
234 }
235 crate::input_source::ResolveProblem::ParentResolveFailed {
236 parent_id,
237 from_name,
238 err,
239 } => {
240 format!(
241 "failed to resolve parent include source '{}' (from '{}'): {}",
242 parent_id, from_name, err
243 )
244 }
245 crate::input_source::ResolveProblem::ParentNotRegularFile { parent } => {
246 format!("include parent '{}' is not a regular file", parent)
247 }
248 crate::input_source::ResolveProblem::ParentHasNoDirectory { parent } => {
249 format!(
250 "include parent '{}' does not have a parent directory",
251 parent
252 )
253 }
254 crate::input_source::ResolveProblem::ResolvesOutsideRoot { spec, root } => {
255 format!(
256 "include '{}' resolves outside the configured root '{}'",
257 spec, root
258 )
259 }
260 crate::input_source::ResolveProblem::TraversesSymlink { spec } => {
261 format!(
262 "include '{}' traverses a symlink, which is disabled by policy",
263 spec
264 )
265 }
266 crate::input_source::ResolveProblem::AbsolutePathNotAllowed { spec } => {
267 format!("absolute include paths are not allowed: {}", spec)
268 }
269 crate::input_source::ResolveProblem::EmptyPath => {
270 "include path must not be empty".to_string()
271 }
272 crate::input_source::ResolveProblem::InvalidExtension { spec } => {
273 format!(
274 "include target '{}' does not have a valid YAML extension (.yml or .yaml)",
275 spec
276 )
277 }
278 crate::input_source::ResolveProblem::HiddenFile { spec } => {
279 format!(
280 "include target '{}' is a hidden file, which is not allowed",
281 spec
282 )
283 }
284 crate::input_source::ResolveProblem::EmptyFragment => {
285 "include fragment must not be empty".to_string()
286 }
287 crate::input_source::ResolveProblem::FragmentContainsHash { spec } => {
288 format!("include fragment must not contain '#': {}", spec)
289 }
290 }
291 }
292 };
293 full_msg.push_str(&msg);
294 Cow::Owned(full_msg)
295 }
296 Error::Budget { breach, .. } => Cow::Owned(format!("budget breached: {breach:?}")),
297 Error::QuotingRequired { value, .. } => {
298 Cow::Owned(format!("The string value [{value}] must be quoted"))
299 }
300 Error::CannotBorrowTransformedString { reason, .. } => Cow::Owned(format!(
301 "input does not contain value verbatim so cannot deserialize into &str ({reason}); use String or Cow<str> instead",
302 )),
303 Error::IndentationError {
304 required, actual, ..
305 } => Cow::Owned(format!(
306 "indentation error: expected {required}, found {actual} spaces"
307 )),
308 Error::IOError { cause } => Cow::Owned(format!("IO error: {cause}")),
309 Error::AliasError { msg, locations } => {
310 let l10n = formatter.localizer();
311 let ref_loc = locations.reference_location;
312 let def_loc = locations.defined_location;
313 match (ref_loc, def_loc) {
314 (Location::UNKNOWN, Location::UNKNOWN) => Cow::Borrowed(msg.as_str()),
315 (r, d) if r != Location::UNKNOWN && (d == Location::UNKNOWN || d == r) => {
316 Cow::Borrowed(msg.as_str())
317 }
318 (_r, d) => Cow::Owned(format!("{msg}{}", l10n.alias_defined_at(d))),
319 }
320 }
321
322 #[cfg(feature = "garde")]
323 Error::ValidationError { issues, locations } => {
324 let l10n = formatter.localizer();
325
326 let mut lines = Vec::with_capacity(issues.len());
327 for issue in issues {
328 let entry = issue.display_entry_overridden(l10n, ExternalMessageSource::Garde);
329 let path_key = &issue.path;
330 let original_leaf = path_key
331 .leaf_string()
332 .unwrap_or_else(|| l10n.root_path_label().into_owned());
333
334 let (locs, resolved_leaf) = locations
335 .search(path_key)
336 .unwrap_or((Locations::UNKNOWN, original_leaf));
337
338 let loc = if locs.reference_location != Location::UNKNOWN {
339 locs.reference_location
340 } else {
341 locs.defined_location
342 };
343
344 let resolved_path = format_path_with_resolved_leaf(path_key, &resolved_leaf);
345
346 lines.push(l10n.validation_issue_line(
347 &resolved_path,
348 &entry,
349 (loc != Location::UNKNOWN).then_some(loc),
350 ));
351 }
352 Cow::Owned(l10n.join_validation_issues(&lines))
353 }
354 #[cfg(feature = "garde")]
355 Error::ValidationErrors { errors } => Cow::Owned(format!(
356 "validation failed for {} document(s)",
357 errors.len()
358 )),
359 #[cfg(feature = "validator")]
360 Error::ValidatorError { issues, locations } => {
361 let l10n = formatter.localizer();
362
363 let mut lines = Vec::with_capacity(issues.len());
364 for issue in issues {
365 let entry = issue.display_entry_overridden(l10n, ExternalMessageSource::Validator);
366 let path_key = &issue.path;
367 let original_leaf = path_key
368 .leaf_string()
369 .unwrap_or_else(|| l10n.root_path_label().into_owned());
370
371 let (locs, resolved_leaf) = locations
372 .search(path_key)
373 .unwrap_or((Locations::UNKNOWN, original_leaf));
374
375 let loc = if locs.reference_location != Location::UNKNOWN {
376 locs.reference_location
377 } else {
378 locs.defined_location
379 };
380
381 let resolved_path = format_path_with_resolved_leaf(path_key, &resolved_leaf);
382
383 lines.push(l10n.validation_issue_line(
384 &resolved_path,
385 &entry,
386 (loc != Location::UNKNOWN).then_some(loc),
387 ));
388 }
389 Cow::Owned(l10n.join_validation_issues(&lines))
390 }
391 #[cfg(feature = "validator")]
392 Error::ValidatorErrors { errors } => Cow::Owned(format!(
393 "validation failed for {} document(s)",
394 errors.len()
395 )),
396 }
397}
398
399impl MessageFormatter for DefaultMessageFormatter {
400 fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
401 default_format_message(self, err)
402 }
403}
404
405pub struct DefaultMessageFormatterWithLocalizer<'a> {
406 localizer: &'a dyn Localizer,
407}
408
409impl MessageFormatter for DefaultMessageFormatterWithLocalizer<'_> {
410 fn localizer(&self) -> &dyn Localizer {
411 self.localizer
412 }
413
414 fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
415 default_format_message(self, err)
416 }
417}
418
419impl DefaultMessageFormatter {
420 pub fn with_localizer<'a>(
426 &self,
427 localizer: &'a dyn Localizer,
428 ) -> DefaultMessageFormatterWithLocalizer<'a> {
429 DefaultMessageFormatterWithLocalizer { localizer }
430 }
431}
432
433fn user_format_message<'a>(formatter: &dyn MessageFormatter, err: &'a Error) -> Cow<'a, str> {
434 if let Error::WithSnippet { error, .. } = err {
435 return user_format_message(formatter, error);
436 }
437
438 match err {
439 Error::WithSnippet { .. } => unreachable!(),
441
442 Error::Eof { .. } => Cow::Borrowed("unexpected end of file"),
443 Error::MultipleDocuments { .. } => {
444 Cow::Borrowed("only single YAML document expected but multiple found")
445 }
446 Error::InvalidUtf8Input => Cow::Borrowed("YAML parser input is not valid UTF-8"),
447 Error::BinaryNotUtf8 { .. } => {
448 Cow::Borrowed("!!binary scalar is not valid UTF-8 so cannot be stored into string.")
449 }
450 Error::InvalidBooleanStrict { .. } => {
451 Cow::Borrowed("invalid boolean (true or false expected)")
452 }
453 Error::NullIntoString { .. } | Error::InvalidCharNull { .. } => {
454 Cow::Borrowed("null is not allowed here")
455 }
456 Error::InvalidCharNotSingleScalar { .. } => {
457 Cow::Borrowed("only single character allowed here")
458 }
459 Error::BytesNotSupportedMissingBinaryTag { .. } => Cow::Borrowed("missing !!binary tag"),
460 Error::ExpectedEmptyMappingForUnitStruct { .. } => {
461 Cow::Borrowed("expected empty mapping here")
462 }
463 Error::UnexpectedContainerEndWhileSkippingNode { .. } => {
464 Cow::Borrowed("unexpected container end")
465 }
466 Error::AliasReplayCounterOverflow { .. } => {
467 Cow::Borrowed("YAML document too large or too complex")
468 }
469 Error::AliasReplayLimitExceeded {
470 total_replayed_events,
471 max_total_replayed_events,
472 ..
473 } => Cow::Owned(format!(
474 "YAML document too large or too complex: total_replayed_events={total_replayed_events} > {max_total_replayed_events}"
475 )),
476 Error::AliasExpansionLimitExceeded {
477 anchor_id,
478 expansions,
479 max_expansions_per_anchor,
480 ..
481 } => Cow::Owned(format!(
482 "YAML document too large or too complex: anchor id {anchor_id}: {expansions} > {max_expansions_per_anchor}"
483 )),
484 Error::AliasReplayStackDepthExceeded {
485 depth, max_depth, ..
486 } => Cow::Owned(format!(
487 "YAML document too large or too complex: depth={depth} > {max_depth}"
488 )),
489 Error::UnknownAnchor { .. } => Cow::Borrowed("reference to unknown value"),
490 Error::CyclicInclude { .. } => Cow::Borrowed("cyclic include detected"),
491 Error::UnsupportedIncludeForm { .. } => {
492 Cow::Borrowed("!include currently only supports the scalar form: !include <path>")
493 }
494 Error::ResolverError { .. } => Cow::Borrowed("failed to resolve include"),
495 Error::RecursiveReferencesRequireWeakTypes { .. } => {
496 Cow::Borrowed("Recursive reference not allowed here")
497 }
498 Error::DuplicateMappingKey { key, .. } => match key {
499 Some(k) => Cow::Owned(format!("duplicate mapping key: {k} not allowed here")),
500 None => Cow::Borrowed("duplicate mapping key not allowed here"),
501 },
502 Error::QuotingRequired { .. } => Cow::Borrowed("value requires quoting"),
503 Error::Budget { breach, .. } => Cow::Owned(format!(
504 "YAML document too large or too complex: limits breached: {breach:?}"
505 )),
506 Error::CannotBorrowTransformedString { .. } => {
507 Cow::Borrowed("Only single string with no escape sequences is allowed here")
508 }
509 Error::IndentationError {
510 required, actual, ..
511 } => Cow::Owned(format!(
512 "incorrect indentation: expected {required}, found {actual} spaces"
513 )),
514
515 _ => default_format_message(formatter, err),
517 }
518}
519
520impl MessageFormatter for UserMessageFormatter {
521 fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
522 user_format_message(self, err)
523 }
524}
525
526pub struct UserMessageFormatterWithLocalizer<'a> {
527 localizer: &'a dyn Localizer,
528}
529
530impl MessageFormatter for UserMessageFormatterWithLocalizer<'_> {
531 fn localizer(&self) -> &dyn Localizer {
532 self.localizer
533 }
534
535 fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
536 user_format_message(self, err)
537 }
538}
539
540impl UserMessageFormatter {
541 pub fn with_localizer<'a>(
547 &self,
548 localizer: &'a dyn Localizer,
549 ) -> UserMessageFormatterWithLocalizer<'a> {
550 UserMessageFormatterWithLocalizer { localizer }
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557 use crate::Location;
558 use crate::de_error::{Error, MessageFormatter, TransformReason};
559 use crate::location::Locations;
560
561 fn loc() -> Location {
562 Location::UNKNOWN
563 }
564
565 #[test]
570 fn default_with_snippet_delegates() {
571 let formatter = DefaultMessageFormatter;
572 let inner = Error::Eof { location: loc() };
573 let err = Error::WithSnippet {
574 regions: vec![],
575 crop_radius: 3,
576 error: Box::new(inner),
577 };
578 assert_eq!(formatter.format_message(&err), "unexpected end of input");
579 }
580
581 #[test]
582 fn default_hook_error() {
583 let formatter = DefaultMessageFormatter;
584 let err = Error::HookError {
585 msg: "hook msg".to_owned(),
586 location: loc(),
587 };
588 assert_eq!(formatter.format_message(&err), "hook msg");
589 }
590
591 #[test]
592 fn default_serde_variant_id() {
593 let formatter = DefaultMessageFormatter;
594 let err = Error::SerdeVariantId {
595 msg: "variant id msg".to_owned(),
596 location: loc(),
597 };
598 assert_eq!(formatter.format_message(&err), "variant id msg");
599 }
600
601 #[test]
602 fn default_invalid_binary_base64() {
603 let formatter = DefaultMessageFormatter;
604 let err = Error::InvalidBinaryBase64 { location: loc() };
605 assert_eq!(formatter.format_message(&err), "invalid !!binary base64");
606 }
607
608 #[test]
609 fn default_unexpected_sequence_end() {
610 let formatter = DefaultMessageFormatter;
611 let err = Error::UnexpectedSequenceEnd { location: loc() };
612 assert_eq!(formatter.format_message(&err), "unexpected sequence end");
613 }
614
615 #[test]
616 fn default_unexpected_mapping_end() {
617 let formatter = DefaultMessageFormatter;
618 let err = Error::UnexpectedMappingEnd { location: loc() };
619 assert_eq!(formatter.format_message(&err), "unexpected mapping end");
620 }
621
622 #[test]
623 fn default_unexpected_container_end_while_skipping() {
624 let formatter = DefaultMessageFormatter;
625 let err = Error::UnexpectedContainerEndWhileSkippingNode { location: loc() };
626 assert_eq!(
627 formatter.format_message(&err),
628 "unexpected container end while skipping node"
629 );
630 }
631
632 #[test]
633 fn default_internal_seed_reused() {
634 let formatter = DefaultMessageFormatter;
635 let err = Error::InternalSeedReusedForMapKey { location: loc() };
636 assert_eq!(
637 formatter.format_message(&err),
638 "internal error: seed reused for map key"
639 );
640 }
641
642 #[test]
643 fn default_value_requested_before_key() {
644 let formatter = DefaultMessageFormatter;
645 let err = Error::ValueRequestedBeforeKey { location: loc() };
646 assert_eq!(formatter.format_message(&err), "value requested before key");
647 }
648
649 #[test]
650 fn default_alias_replay_counter_overflow() {
651 let formatter = DefaultMessageFormatter;
652 let err = Error::AliasReplayCounterOverflow { location: loc() };
653 assert_eq!(
654 formatter.format_message(&err),
655 "alias replay counter overflow"
656 );
657 }
658
659 #[test]
660 fn default_folded_block_scalar() {
661 let formatter = DefaultMessageFormatter;
662 let err = Error::FoldedBlockScalarMustIndentContent { location: loc() };
663 assert_eq!(
664 formatter.format_message(&err),
665 "folded block scalars must indent their content"
666 );
667 }
668
669 #[test]
670 fn default_internal_depth_underflow() {
671 let formatter = DefaultMessageFormatter;
672 let err = Error::InternalDepthUnderflow { location: loc() };
673 assert_eq!(formatter.format_message(&err), "internal depth underflow");
674 }
675
676 #[test]
677 fn default_internal_recursion_stack_empty() {
678 let formatter = DefaultMessageFormatter;
679 let err = Error::InternalRecursionStackEmpty { location: loc() };
680 assert_eq!(
681 formatter.format_message(&err),
682 "internal recursion stack empty"
683 );
684 }
685
686 #[test]
687 fn default_recursive_references_require_weak_types() {
688 let formatter = DefaultMessageFormatter;
689 let err = Error::RecursiveReferencesRequireWeakTypes { location: loc() };
690 assert_eq!(
691 formatter.format_message(&err),
692 "recursive references require weak recursion types"
693 );
694 }
695
696 #[test]
697 fn default_serde_invalid_value() {
698 let formatter = DefaultMessageFormatter;
699 let err = Error::SerdeInvalidValue {
700 unexpected: "null".to_owned(),
701 expected: "string".to_owned(),
702 location: loc(),
703 };
704 let msg = formatter.format_message(&err);
705 assert!(msg.contains("invalid value"), "got: {msg}");
706 assert!(msg.contains("null"), "got: {msg}");
707 assert!(msg.contains("string"), "got: {msg}");
708 }
709
710 #[test]
711 fn default_serde_unknown_variant() {
712 let formatter = DefaultMessageFormatter;
713 let err = Error::SerdeUnknownVariant {
714 variant: "foo".to_owned(),
715 expected: vec!["bar", "baz"],
716 location: loc(),
717 };
718 let msg = formatter.format_message(&err);
719 assert!(msg.contains("unknown variant"), "got: {msg}");
720 assert!(msg.contains("foo"), "got: {msg}");
721 }
722
723 #[test]
724 fn default_serde_unknown_field() {
725 let formatter = DefaultMessageFormatter;
726 let err = Error::SerdeUnknownField {
727 field: "xyz".to_owned(),
728 expected: vec!["a", "b"],
729 location: loc(),
730 };
731 let msg = formatter.format_message(&err);
732 assert!(msg.contains("unknown field"), "got: {msg}");
733 assert!(msg.contains("xyz"), "got: {msg}");
734 }
735
736 #[test]
737 fn default_unexpected_container_end_while_reading_key() {
738 let formatter = DefaultMessageFormatter;
739 let err = Error::UnexpectedContainerEndWhileReadingKeyNode { location: loc() };
740 assert_eq!(
741 formatter.format_message(&err),
742 "unexpected container end while reading key"
743 );
744 }
745
746 #[test]
747 fn default_expected_mapping_end_after_enum_variant() {
748 let formatter = DefaultMessageFormatter;
749 let err = Error::ExpectedMappingEndAfterEnumVariantValue { location: loc() };
750 assert_eq!(
751 formatter.format_message(&err),
752 "expected end of mapping after enum variant value"
753 );
754 }
755
756 #[test]
757 fn default_container_end_mismatch() {
758 let formatter = DefaultMessageFormatter;
759 let err = Error::ContainerEndMismatch { location: loc() };
760 assert_eq!(
761 formatter.format_message(&err),
762 "list or mapping end with no start"
763 );
764 }
765
766 #[test]
767 fn default_io_error() {
768 let formatter = DefaultMessageFormatter;
769 let err = Error::IOError {
770 cause: std::io::Error::other("disk full"),
771 };
772 let msg = formatter.format_message(&err);
773 assert!(msg.contains("IO error"), "got: {msg}");
774 assert!(msg.contains("disk full"), "got: {msg}");
775 }
776
777 #[test]
778 fn default_unresolved_property() {
779 let formatter = DefaultMessageFormatter;
780 let err = Error::UnresolvedProperty {
781 name: "MISSING".to_owned(),
782 location: loc(),
783 };
784 assert_eq!(formatter.format_message(&err), "missing property `MISSING`");
785 }
786
787 #[test]
788 fn default_invalid_property_name() {
789 let formatter = DefaultMessageFormatter;
790 let err = Error::InvalidPropertyName {
791 name: "${ab-cd}".to_owned(),
792 location: loc(),
793 };
794 assert_eq!(formatter.format_message(&err), "Invalid name: '${ab-cd}'");
795 }
796
797 #[test]
798 fn default_alias_error_both_unknown() {
799 let formatter = DefaultMessageFormatter;
800 let err = Error::AliasError {
801 msg: "alias msg".to_owned(),
802 locations: Locations::UNKNOWN,
803 };
804 assert_eq!(formatter.format_message(&err), "alias msg");
805 }
806
807 #[test]
808 fn default_alias_error_ref_known_def_unknown() {
809 let formatter = DefaultMessageFormatter;
810 let ref_loc = Location::new(1, 0);
811 let err = Error::AliasError {
812 msg: "alias msg".to_owned(),
813 locations: Locations {
814 reference_location: ref_loc,
815 defined_location: Location::UNKNOWN,
816 },
817 };
818 assert_eq!(formatter.format_message(&err), "alias msg");
820 }
821
822 #[test]
823 fn default_alias_error_both_known_different() {
824 let formatter = DefaultMessageFormatter;
825 let ref_loc = Location::new(1, 0);
826 let def_loc = Location::new(5, 0);
827 let err = Error::AliasError {
828 msg: "alias msg".to_owned(),
829 locations: Locations {
830 reference_location: ref_loc,
831 defined_location: def_loc,
832 },
833 };
834 let msg = formatter.format_message(&err);
836 assert!(msg.starts_with("alias msg"), "got: {msg}");
837 }
838
839 #[test]
844 fn user_with_snippet_delegates() {
845 let formatter = UserMessageFormatter;
846 let inner = Error::Eof { location: loc() };
847 let err = Error::WithSnippet {
848 regions: vec![],
849 crop_radius: 3,
850 error: Box::new(inner),
851 };
852 assert_eq!(formatter.format_message(&err), "unexpected end of file");
853 }
854
855 #[test]
856 fn user_eof() {
857 let formatter = UserMessageFormatter;
858 let err = Error::Eof { location: loc() };
859 assert_eq!(formatter.format_message(&err), "unexpected end of file");
860 }
861
862 #[test]
863 fn user_multiple_documents() {
864 let formatter = UserMessageFormatter;
865 let err = Error::MultipleDocuments {
866 hint: "use from_str_multidoc",
867 location: loc(),
868 };
869 assert_eq!(
870 formatter.format_message(&err),
871 "only single YAML document expected but multiple found"
872 );
873 }
874
875 #[test]
876 fn user_invalid_utf8_input() {
877 let formatter = UserMessageFormatter;
878 let err = Error::InvalidUtf8Input;
879 assert_eq!(
880 formatter.format_message(&err),
881 "YAML parser input is not valid UTF-8"
882 );
883 }
884
885 #[test]
886 fn user_binary_not_utf8() {
887 let formatter = UserMessageFormatter;
888 let err = Error::BinaryNotUtf8 { location: loc() };
889 assert!(formatter.format_message(&err).contains("!!binary"));
890 }
891
892 #[test]
893 fn user_invalid_boolean_strict() {
894 let formatter = UserMessageFormatter;
895 let err = Error::InvalidBooleanStrict { location: loc() };
896 assert_eq!(
897 formatter.format_message(&err),
898 "invalid boolean (true or false expected)"
899 );
900 }
901
902 #[test]
903 fn user_null_into_string() {
904 let formatter = UserMessageFormatter;
905 let err = Error::NullIntoString { location: loc() };
906 assert_eq!(formatter.format_message(&err), "null is not allowed here");
907 }
908
909 #[test]
910 fn user_invalid_char_null() {
911 let formatter = UserMessageFormatter;
912 let err = Error::InvalidCharNull { location: loc() };
913 assert_eq!(formatter.format_message(&err), "null is not allowed here");
914 }
915
916 #[test]
917 fn user_invalid_char_not_single_scalar() {
918 let formatter = UserMessageFormatter;
919 let err = Error::InvalidCharNotSingleScalar { location: loc() };
920 assert_eq!(
921 formatter.format_message(&err),
922 "only single character allowed here"
923 );
924 }
925
926 #[test]
927 fn user_bytes_not_supported_missing_binary_tag() {
928 let formatter = UserMessageFormatter;
929 let err = Error::BytesNotSupportedMissingBinaryTag { location: loc() };
930 assert_eq!(formatter.format_message(&err), "missing !!binary tag");
931 }
932
933 #[test]
934 fn user_expected_empty_mapping_for_unit_struct() {
935 let formatter = UserMessageFormatter;
936 let err = Error::ExpectedEmptyMappingForUnitStruct { location: loc() };
937 assert_eq!(
938 formatter.format_message(&err),
939 "expected empty mapping here"
940 );
941 }
942
943 #[test]
944 fn user_unexpected_container_end_while_skipping() {
945 let formatter = UserMessageFormatter;
946 let err = Error::UnexpectedContainerEndWhileSkippingNode { location: loc() };
947 assert_eq!(formatter.format_message(&err), "unexpected container end");
948 }
949
950 #[test]
951 fn user_alias_replay_counter_overflow() {
952 let formatter = UserMessageFormatter;
953 let err = Error::AliasReplayCounterOverflow { location: loc() };
954 assert_eq!(
955 formatter.format_message(&err),
956 "YAML document too large or too complex"
957 );
958 }
959
960 #[test]
961 fn user_alias_replay_limit_exceeded() {
962 let formatter = UserMessageFormatter;
963 let err = Error::AliasReplayLimitExceeded {
964 total_replayed_events: 1000,
965 max_total_replayed_events: 500,
966 location: loc(),
967 };
968 let msg = formatter.format_message(&err);
969 assert!(msg.contains("too large or too complex"), "got: {msg}");
970 assert!(msg.contains("1000"), "got: {msg}");
971 }
972
973 #[test]
974 fn user_alias_expansion_limit_exceeded() {
975 let formatter = UserMessageFormatter;
976 let err = Error::AliasExpansionLimitExceeded {
977 anchor_id: 7,
978 expansions: 200,
979 max_expansions_per_anchor: 100,
980 location: loc(),
981 };
982 let msg = formatter.format_message(&err);
983 assert!(msg.contains("too large or too complex"), "got: {msg}");
984 assert!(msg.contains("7"), "got: {msg}");
985 }
986
987 #[test]
988 fn user_alias_replay_stack_depth_exceeded() {
989 let formatter = UserMessageFormatter;
990 let err = Error::AliasReplayStackDepthExceeded {
991 depth: 50,
992 max_depth: 20,
993 location: loc(),
994 };
995 let msg = formatter.format_message(&err);
996 assert!(msg.contains("too large or too complex"), "got: {msg}");
997 assert!(msg.contains("50"), "got: {msg}");
998 }
999
1000 #[test]
1001 fn user_unknown_anchor() {
1002 let formatter = UserMessageFormatter;
1003 let err = Error::UnknownAnchor { location: loc() };
1004 assert_eq!(formatter.format_message(&err), "reference to unknown value");
1005 }
1006
1007 #[test]
1008 fn user_recursive_references_require_weak_types() {
1009 let formatter = UserMessageFormatter;
1010 let err = Error::RecursiveReferencesRequireWeakTypes { location: loc() };
1011 assert_eq!(
1012 formatter.format_message(&err),
1013 "Recursive reference not allowed here"
1014 );
1015 }
1016
1017 #[test]
1018 fn user_duplicate_mapping_key_with_key() {
1019 let formatter = UserMessageFormatter;
1020 let err = Error::DuplicateMappingKey {
1021 key: Some("mykey".to_owned()),
1022 location: loc(),
1023 };
1024 let msg = formatter.format_message(&err);
1025 assert!(msg.contains("mykey"), "got: {msg}");
1026 assert!(msg.contains("duplicate"), "got: {msg}");
1027 }
1028
1029 #[test]
1030 fn user_duplicate_mapping_key_without_key() {
1031 let formatter = UserMessageFormatter;
1032 let err = Error::DuplicateMappingKey {
1033 key: None,
1034 location: loc(),
1035 };
1036 let msg = formatter.format_message(&err);
1037 assert!(msg.contains("duplicate"), "got: {msg}");
1038 }
1039
1040 #[test]
1041 fn user_quoting_required() {
1042 let formatter = UserMessageFormatter;
1043 let err = Error::QuotingRequired {
1044 value: "yes".to_owned(),
1045 location: loc(),
1046 };
1047 assert_eq!(formatter.format_message(&err), "value requires quoting");
1048 }
1049
1050 #[test]
1051 fn user_budget() {
1052 use crate::budget::BudgetBreach;
1053 let formatter = UserMessageFormatter;
1054 let err = Error::Budget {
1055 breach: BudgetBreach::Events { events: 9999 },
1056 location: loc(),
1057 };
1058 let msg = formatter.format_message(&err);
1059 assert!(msg.contains("too large or too complex"), "got: {msg}");
1060 }
1061
1062 #[test]
1063 fn user_cannot_borrow_transformed_string() {
1064 let formatter = UserMessageFormatter;
1065 let err = Error::CannotBorrowTransformedString {
1066 reason: TransformReason::EscapeSequence,
1067 location: loc(),
1068 };
1069 assert_eq!(
1070 formatter.format_message(&err),
1071 "Only single string with no escape sequences is allowed here"
1072 );
1073 }
1074
1075 #[test]
1076 fn user_falls_through_to_default_for_unhandled() {
1077 let formatter = UserMessageFormatter;
1079 let err = Error::SerdeInvalidType {
1080 unexpected: "seq".to_owned(),
1081 expected: "map".to_owned(),
1082 location: loc(),
1083 };
1084 let msg = formatter.format_message(&err);
1085 assert!(msg.contains("invalid type"), "got: {msg}");
1086 }
1087
1088 #[test]
1093 fn user_with_localizer_delegates() {
1094 use crate::localizer::DefaultEnglishLocalizer;
1095 let localizer = DefaultEnglishLocalizer;
1096 let formatter = UserMessageFormatter.with_localizer(&localizer);
1097 let err = Error::Eof { location: loc() };
1098 assert_eq!(formatter.format_message(&err), "unexpected end of file");
1099 }
1100
1101 #[test]
1106 fn default_with_localizer_delegates() {
1107 use crate::localizer::DefaultEnglishLocalizer;
1108 let localizer = DefaultEnglishLocalizer;
1109 let formatter = DefaultMessageFormatter.with_localizer(&localizer);
1110 let err = Error::Eof { location: loc() };
1111 assert_eq!(formatter.format_message(&err), "unexpected end of input");
1112 }
1113}