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