use crate::Location;
use crate::de_error::{Error, MessageFormatter, UserMessageFormatter};
use crate::localizer::{ExternalMessage, Localizer};
use std::borrow::Cow;
#[cfg(any(feature = "garde", feature = "validator"))]
use crate::{
Locations, localizer::ExternalMessageSource, path_map::format_path_with_resolved_leaf,
};
#[derive(Debug, Default, Clone, Copy)]
pub struct DefaultMessageFormatter;
pub type DeveloperMessageFormatter = DefaultMessageFormatter;
fn default_format_message<'a>(formatter: &dyn MessageFormatter, err: &'a Error) -> Cow<'a, str> {
match err {
Error::WithSnippet { error, .. } => default_format_message(formatter, error),
Error::ExternalMessage {
source,
msg,
code,
params,
..
} => {
let l10n = formatter.localizer();
l10n.override_external_message(ExternalMessage {
source: *source,
original: msg.as_str(),
code: code.as_deref(),
params,
})
.unwrap_or(Cow::Borrowed(msg.as_str()))
}
Error::Message { msg, .. }
| Error::HookError { msg, .. }
| Error::SerdeVariantId { msg, .. } => Cow::Borrowed(msg.as_str()),
Error::UnresolvedProperty { name, .. } => Cow::Owned(format!("missing property `{name}`")),
Error::InvalidPropertyName { name, .. } => Cow::Owned(format!("Invalid name: '{name}'")),
Error::Eof { .. } => Cow::Borrowed("unexpected end of input"),
Error::MultipleDocuments { hint, .. } => {
Cow::Owned(format!("multiple YAML documents detected; {hint}"))
}
Error::Unexpected { expected, .. } => {
Cow::Owned(format!("unexpected event: expected {expected}"))
}
Error::MergeValueNotMapOrSeqOfMaps { .. } => {
Cow::Borrowed("YAML merge value must be mapping or sequence of mappings")
}
Error::InvalidBinaryBase64 { .. } => Cow::Borrowed("invalid !!binary base64"),
Error::InvalidUtf8Input => Cow::Borrowed("input is not valid UTF-8"),
Error::BinaryNotUtf8 { .. } => Cow::Borrowed(
"!!binary scalar is not valid UTF-8 so cannot be stored into string. \
If you just use !!binary for documentation/annotation, set ignore_binary_tag_for_string in Options",
),
Error::TaggedScalarCannotDeserializeIntoString { .. } => {
Cow::Borrowed("cannot deserialize tagged scalar into string")
}
Error::UnexpectedSequenceEnd { .. } => Cow::Borrowed("unexpected sequence end"),
Error::UnexpectedMappingEnd { .. } => Cow::Borrowed("unexpected mapping end"),
Error::InvalidBooleanStrict { .. } => {
Cow::Borrowed("invalid boolean (strict mode expects true/false)")
}
Error::InvalidCharNull { .. } => {
Cow::Borrowed("invalid char: cannot deserialize null; use Option<char>")
}
Error::InvalidCharNotSingleScalar { .. } => {
Cow::Borrowed("invalid char: expected a single Unicode scalar value")
}
Error::NullIntoString { .. } => {
Cow::Borrowed("cannot deserialize null into string; use Option<String>")
}
Error::BytesNotSupportedMissingBinaryTag { .. } => {
Cow::Borrowed("bytes not supported (missing !!binary tag)")
}
Error::UnexpectedValueForUnit { .. } => Cow::Borrowed("unexpected value for unit"),
Error::ExpectedEmptyMappingForUnitStruct { .. } => {
Cow::Borrowed("expected empty mapping for unit struct")
}
Error::UnexpectedContainerEndWhileSkippingNode { .. } => {
Cow::Borrowed("unexpected container end while skipping node")
}
Error::InternalSeedReusedForMapKey { .. } => {
Cow::Borrowed("internal error: seed reused for map key")
}
Error::ValueRequestedBeforeKey { .. } => Cow::Borrowed("value requested before key"),
Error::ExpectedStringKeyForExternallyTaggedEnum { .. } => {
Cow::Borrowed("expected string key for externally tagged enum")
}
Error::ExternallyTaggedEnumExpectedScalarOrMapping { .. } => {
Cow::Borrowed("externally tagged enum expected scalar or mapping")
}
Error::UnexpectedValueForUnitEnumVariant { .. } => {
Cow::Borrowed("unexpected value for unit enum variant")
}
Error::AliasReplayCounterOverflow { .. } => Cow::Borrowed("alias replay counter overflow"),
Error::AliasReplayLimitExceeded {
total_replayed_events,
max_total_replayed_events,
..
} => Cow::Owned(format!(
"alias replay limit exceeded: total_replayed_events={total_replayed_events} > {max_total_replayed_events}"
)),
Error::AliasExpansionLimitExceeded {
anchor_id,
expansions,
max_expansions_per_anchor,
..
} => Cow::Owned(format!(
"alias expansion limit exceeded for anchor id {anchor_id}: {expansions} > {max_expansions_per_anchor}"
)),
Error::AliasReplayStackDepthExceeded {
depth, max_depth, ..
} => Cow::Owned(format!(
"alias replay stack depth exceeded: depth={depth} > {max_depth}"
)),
Error::FoldedBlockScalarMustIndentContent { .. } => {
Cow::Borrowed("folded block scalars must indent their content")
}
Error::InternalDepthUnderflow { .. } => Cow::Borrowed("internal depth underflow"),
Error::InternalRecursionStackEmpty { .. } => {
Cow::Borrowed("internal recursion stack empty")
}
Error::RecursiveReferencesRequireWeakTypes { .. } => {
Cow::Borrowed("recursive references require weak recursion types")
}
Error::InvalidScalar { ty, .. } => Cow::Owned(format!("invalid {ty}")),
Error::SerdeInvalidType {
unexpected,
expected,
..
} => Cow::Owned(format!("invalid type: {unexpected}, expected {expected}")),
Error::SerdeInvalidValue {
unexpected,
expected,
..
} => Cow::Owned(format!("invalid value: {unexpected}, expected {expected}")),
Error::SerdeUnknownVariant {
variant, expected, ..
} => Cow::Owned(format!(
"unknown variant `{variant}`, expected one of {}",
expected.join(", ")
)),
Error::SerdeUnknownField {
field, expected, ..
} => Cow::Owned(format!(
"unknown field `{field}`, expected one of {}",
expected.join(", ")
)),
Error::SerdeMissingField { field, .. } => Cow::Owned(format!("missing field `{field}`")),
Error::UnexpectedContainerEndWhileReadingKeyNode { .. } => {
Cow::Borrowed("unexpected container end while reading key")
}
Error::DuplicateMappingKey { key, .. } => match key {
Some(k) => Cow::Owned(format!(
"duplicate mapping key: {k}, set DuplicateKeyPolicy in Options if acceptable"
)),
None => Cow::Borrowed(
"duplicate mapping key, set DuplicateKeyPolicy in Options if acceptable",
),
},
Error::TaggedEnumMismatch { tagged, target, .. } => Cow::Owned(format!(
"tagged enum `{tagged}` does not match target enum `{target}`",
)),
Error::ExpectedMappingEndAfterEnumVariantValue { .. } => {
Cow::Borrowed("expected end of mapping after enum variant value")
}
Error::ContainerEndMismatch { .. } => Cow::Borrowed("list or mapping end with no start"),
Error::UnknownAnchor { .. } => Cow::Borrowed("alias references unknown anchor"),
Error::CyclicInclude { id, stack, .. } => {
let mut full_msg = format!("cyclic include detected: {id}");
if !stack.is_empty() {
full_msg.push_str("\nwhile processing include from ");
full_msg.push_str(&stack.join(" -> "));
}
Cow::Owned(full_msg)
}
Error::UnsupportedIncludeForm { .. } => {
Cow::Borrowed("!include currently only supports the scalar form: !include <path>")
}
Error::ResolverError {
target,
error,
stack,
..
} => {
let mut full_msg = format!("failed to resolve include {target:?}");
if !stack.is_empty() {
full_msg.push_str("\nwhile processing include from ");
full_msg.push_str(&stack.join(" -> "));
}
full_msg.push('\n');
let msg = match error {
crate::input_source::IncludeResolveError::Io(e) => e.to_string(),
crate::input_source::IncludeResolveError::Message(m) => m.clone(),
crate::input_source::IncludeResolveError::SizeLimitExceeded(size, limit) => {
format!("include size {size} bytes exceeds remaining size limit {limit} bytes")
}
crate::input_source::IncludeResolveError::FileInclude(problem) => {
match &**problem {
crate::input_source::ResolveProblem::ResolveFailed {
spec,
base_dir,
err,
} => {
format!(
"failed to resolve include '{}' from '{}': {}",
spec, base_dir, err
)
}
crate::input_source::ResolveProblem::TargetNotRegularFile { target } => {
format!("include target '{}' is not a regular file", target)
}
crate::input_source::ResolveProblem::TargetIsRootFile { spec } => {
format!(
"include target '{}' resolves to the configured root file itself",
spec
)
}
crate::input_source::ResolveProblem::ParentIdNotAbsoluteCanonical {
parent_id,
} => {
format!(
"SafeFileResolver expected parent include id to be an absolute canonical path, got '{}'",
parent_id
)
}
crate::input_source::ResolveProblem::ParentResolveFailed {
parent_id,
from_name,
err,
} => {
format!(
"failed to resolve parent include source '{}' (from '{}'): {}",
parent_id, from_name, err
)
}
crate::input_source::ResolveProblem::ParentNotRegularFile { parent } => {
format!("include parent '{}' is not a regular file", parent)
}
crate::input_source::ResolveProblem::ParentHasNoDirectory { parent } => {
format!(
"include parent '{}' does not have a parent directory",
parent
)
}
crate::input_source::ResolveProblem::ResolvesOutsideRoot { spec, root } => {
format!(
"include '{}' resolves outside the configured root '{}'",
spec, root
)
}
crate::input_source::ResolveProblem::TraversesSymlink { spec } => {
format!(
"include '{}' traverses a symlink, which is disabled by policy",
spec
)
}
crate::input_source::ResolveProblem::AbsolutePathNotAllowed { spec } => {
format!("absolute include paths are not allowed: {}", spec)
}
crate::input_source::ResolveProblem::EmptyPath => {
"include path must not be empty".to_string()
}
crate::input_source::ResolveProblem::InvalidExtension { spec } => {
format!(
"include target '{}' does not have a valid YAML extension (.yml or .yaml)",
spec
)
}
crate::input_source::ResolveProblem::HiddenFile { spec } => {
format!(
"include target '{}' is a hidden file, which is not allowed",
spec
)
}
crate::input_source::ResolveProblem::EmptyFragment => {
"include fragment must not be empty".to_string()
}
crate::input_source::ResolveProblem::FragmentContainsHash { spec } => {
format!("include fragment must not contain '#': {}", spec)
}
}
}
};
full_msg.push_str(&msg);
Cow::Owned(full_msg)
}
Error::Budget { breach, .. } => Cow::Owned(format!("budget breached: {breach:?}")),
Error::QuotingRequired { value, .. } => {
Cow::Owned(format!("The string value [{value}] must be quoted"))
}
Error::CannotBorrowTransformedString { reason, .. } => Cow::Owned(format!(
"input does not contain value verbatim so cannot deserialize into &str ({reason}); use String or Cow<str> instead",
)),
Error::IndentationError {
required, actual, ..
} => Cow::Owned(format!(
"indentation error: expected {required}, found {actual} spaces"
)),
Error::IOError { cause } => Cow::Owned(format!("IO error: {cause}")),
Error::AliasError { msg, locations } => {
let l10n = formatter.localizer();
let ref_loc = locations.reference_location;
let def_loc = locations.defined_location;
match (ref_loc, def_loc) {
(Location::UNKNOWN, Location::UNKNOWN) => Cow::Borrowed(msg.as_str()),
(r, d) if r != Location::UNKNOWN && (d == Location::UNKNOWN || d == r) => {
Cow::Borrowed(msg.as_str())
}
(_r, d) => Cow::Owned(format!("{msg}{}", l10n.alias_defined_at(d))),
}
}
#[cfg(feature = "garde")]
Error::ValidationError { issues, locations } => {
let l10n = formatter.localizer();
let mut lines = Vec::with_capacity(issues.len());
for issue in issues {
let entry = issue.display_entry_overridden(l10n, ExternalMessageSource::Garde);
let path_key = &issue.path;
let original_leaf = path_key
.leaf_string()
.unwrap_or_else(|| l10n.root_path_label().into_owned());
let (locs, resolved_leaf) = locations
.search(path_key)
.unwrap_or((Locations::UNKNOWN, original_leaf));
let loc = if locs.reference_location != Location::UNKNOWN {
locs.reference_location
} else {
locs.defined_location
};
let resolved_path = format_path_with_resolved_leaf(path_key, &resolved_leaf);
lines.push(l10n.validation_issue_line(
&resolved_path,
&entry,
(loc != Location::UNKNOWN).then_some(loc),
));
}
Cow::Owned(l10n.join_validation_issues(&lines))
}
#[cfg(feature = "garde")]
Error::ValidationErrors { errors } => Cow::Owned(format!(
"validation failed for {} document(s)",
errors.len()
)),
#[cfg(feature = "validator")]
Error::ValidatorError { issues, locations } => {
let l10n = formatter.localizer();
let mut lines = Vec::with_capacity(issues.len());
for issue in issues {
let entry = issue.display_entry_overridden(l10n, ExternalMessageSource::Validator);
let path_key = &issue.path;
let original_leaf = path_key
.leaf_string()
.unwrap_or_else(|| l10n.root_path_label().into_owned());
let (locs, resolved_leaf) = locations
.search(path_key)
.unwrap_or((Locations::UNKNOWN, original_leaf));
let loc = if locs.reference_location != Location::UNKNOWN {
locs.reference_location
} else {
locs.defined_location
};
let resolved_path = format_path_with_resolved_leaf(path_key, &resolved_leaf);
lines.push(l10n.validation_issue_line(
&resolved_path,
&entry,
(loc != Location::UNKNOWN).then_some(loc),
));
}
Cow::Owned(l10n.join_validation_issues(&lines))
}
#[cfg(feature = "validator")]
Error::ValidatorErrors { errors } => Cow::Owned(format!(
"validation failed for {} document(s)",
errors.len()
)),
}
}
impl MessageFormatter for DefaultMessageFormatter {
fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
default_format_message(self, err)
}
}
pub struct DefaultMessageFormatterWithLocalizer<'a> {
localizer: &'a dyn Localizer,
}
impl MessageFormatter for DefaultMessageFormatterWithLocalizer<'_> {
fn localizer(&self) -> &dyn Localizer {
self.localizer
}
fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
default_format_message(self, err)
}
}
impl DefaultMessageFormatter {
pub fn with_localizer<'a>(
&self,
localizer: &'a dyn Localizer,
) -> DefaultMessageFormatterWithLocalizer<'a> {
DefaultMessageFormatterWithLocalizer { localizer }
}
}
fn user_format_message<'a>(formatter: &dyn MessageFormatter, err: &'a Error) -> Cow<'a, str> {
if let Error::WithSnippet { error, .. } = err {
return user_format_message(formatter, error);
}
match err {
Error::WithSnippet { .. } => unreachable!(),
Error::Eof { .. } => Cow::Borrowed("unexpected end of file"),
Error::MultipleDocuments { .. } => {
Cow::Borrowed("only single YAML document expected but multiple found")
}
Error::InvalidUtf8Input => Cow::Borrowed("YAML parser input is not valid UTF-8"),
Error::BinaryNotUtf8 { .. } => {
Cow::Borrowed("!!binary scalar is not valid UTF-8 so cannot be stored into string.")
}
Error::InvalidBooleanStrict { .. } => {
Cow::Borrowed("invalid boolean (true or false expected)")
}
Error::NullIntoString { .. } | Error::InvalidCharNull { .. } => {
Cow::Borrowed("null is not allowed here")
}
Error::InvalidCharNotSingleScalar { .. } => {
Cow::Borrowed("only single character allowed here")
}
Error::BytesNotSupportedMissingBinaryTag { .. } => Cow::Borrowed("missing !!binary tag"),
Error::ExpectedEmptyMappingForUnitStruct { .. } => {
Cow::Borrowed("expected empty mapping here")
}
Error::UnexpectedContainerEndWhileSkippingNode { .. } => {
Cow::Borrowed("unexpected container end")
}
Error::AliasReplayCounterOverflow { .. } => {
Cow::Borrowed("YAML document too large or too complex")
}
Error::AliasReplayLimitExceeded {
total_replayed_events,
max_total_replayed_events,
..
} => Cow::Owned(format!(
"YAML document too large or too complex: total_replayed_events={total_replayed_events} > {max_total_replayed_events}"
)),
Error::AliasExpansionLimitExceeded {
anchor_id,
expansions,
max_expansions_per_anchor,
..
} => Cow::Owned(format!(
"YAML document too large or too complex: anchor id {anchor_id}: {expansions} > {max_expansions_per_anchor}"
)),
Error::AliasReplayStackDepthExceeded {
depth, max_depth, ..
} => Cow::Owned(format!(
"YAML document too large or too complex: depth={depth} > {max_depth}"
)),
Error::UnknownAnchor { .. } => Cow::Borrowed("reference to unknown value"),
Error::CyclicInclude { .. } => Cow::Borrowed("cyclic include detected"),
Error::UnsupportedIncludeForm { .. } => {
Cow::Borrowed("!include currently only supports the scalar form: !include <path>")
}
Error::ResolverError { .. } => Cow::Borrowed("failed to resolve include"),
Error::RecursiveReferencesRequireWeakTypes { .. } => {
Cow::Borrowed("Recursive reference not allowed here")
}
Error::DuplicateMappingKey { key, .. } => match key {
Some(k) => Cow::Owned(format!("duplicate mapping key: {k} not allowed here")),
None => Cow::Borrowed("duplicate mapping key not allowed here"),
},
Error::QuotingRequired { .. } => Cow::Borrowed("value requires quoting"),
Error::Budget { breach, .. } => Cow::Owned(format!(
"YAML document too large or too complex: limits breached: {breach:?}"
)),
Error::CannotBorrowTransformedString { .. } => {
Cow::Borrowed("Only single string with no escape sequences is allowed here")
}
Error::IndentationError {
required, actual, ..
} => Cow::Owned(format!(
"incorrect indentation: expected {required}, found {actual} spaces"
)),
_ => default_format_message(formatter, err),
}
}
impl MessageFormatter for UserMessageFormatter {
fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
user_format_message(self, err)
}
}
pub struct UserMessageFormatterWithLocalizer<'a> {
localizer: &'a dyn Localizer,
}
impl MessageFormatter for UserMessageFormatterWithLocalizer<'_> {
fn localizer(&self) -> &dyn Localizer {
self.localizer
}
fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
user_format_message(self, err)
}
}
impl UserMessageFormatter {
pub fn with_localizer<'a>(
&self,
localizer: &'a dyn Localizer,
) -> UserMessageFormatterWithLocalizer<'a> {
UserMessageFormatterWithLocalizer { localizer }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Location;
use crate::de_error::{Error, MessageFormatter, TransformReason};
use crate::location::Locations;
fn loc() -> Location {
Location::UNKNOWN
}
#[test]
fn default_with_snippet_delegates() {
let formatter = DefaultMessageFormatter;
let inner = Error::Eof { location: loc() };
let err = Error::WithSnippet {
regions: vec![],
crop_radius: 3,
error: Box::new(inner),
};
assert_eq!(formatter.format_message(&err), "unexpected end of input");
}
#[test]
fn default_hook_error() {
let formatter = DefaultMessageFormatter;
let err = Error::HookError {
msg: "hook msg".to_owned(),
location: loc(),
};
assert_eq!(formatter.format_message(&err), "hook msg");
}
#[test]
fn default_serde_variant_id() {
let formatter = DefaultMessageFormatter;
let err = Error::SerdeVariantId {
msg: "variant id msg".to_owned(),
location: loc(),
};
assert_eq!(formatter.format_message(&err), "variant id msg");
}
#[test]
fn default_invalid_binary_base64() {
let formatter = DefaultMessageFormatter;
let err = Error::InvalidBinaryBase64 { location: loc() };
assert_eq!(formatter.format_message(&err), "invalid !!binary base64");
}
#[test]
fn default_unexpected_sequence_end() {
let formatter = DefaultMessageFormatter;
let err = Error::UnexpectedSequenceEnd { location: loc() };
assert_eq!(formatter.format_message(&err), "unexpected sequence end");
}
#[test]
fn default_unexpected_mapping_end() {
let formatter = DefaultMessageFormatter;
let err = Error::UnexpectedMappingEnd { location: loc() };
assert_eq!(formatter.format_message(&err), "unexpected mapping end");
}
#[test]
fn default_unexpected_container_end_while_skipping() {
let formatter = DefaultMessageFormatter;
let err = Error::UnexpectedContainerEndWhileSkippingNode { location: loc() };
assert_eq!(
formatter.format_message(&err),
"unexpected container end while skipping node"
);
}
#[test]
fn default_internal_seed_reused() {
let formatter = DefaultMessageFormatter;
let err = Error::InternalSeedReusedForMapKey { location: loc() };
assert_eq!(
formatter.format_message(&err),
"internal error: seed reused for map key"
);
}
#[test]
fn default_value_requested_before_key() {
let formatter = DefaultMessageFormatter;
let err = Error::ValueRequestedBeforeKey { location: loc() };
assert_eq!(formatter.format_message(&err), "value requested before key");
}
#[test]
fn default_alias_replay_counter_overflow() {
let formatter = DefaultMessageFormatter;
let err = Error::AliasReplayCounterOverflow { location: loc() };
assert_eq!(
formatter.format_message(&err),
"alias replay counter overflow"
);
}
#[test]
fn default_folded_block_scalar() {
let formatter = DefaultMessageFormatter;
let err = Error::FoldedBlockScalarMustIndentContent { location: loc() };
assert_eq!(
formatter.format_message(&err),
"folded block scalars must indent their content"
);
}
#[test]
fn default_internal_depth_underflow() {
let formatter = DefaultMessageFormatter;
let err = Error::InternalDepthUnderflow { location: loc() };
assert_eq!(formatter.format_message(&err), "internal depth underflow");
}
#[test]
fn default_internal_recursion_stack_empty() {
let formatter = DefaultMessageFormatter;
let err = Error::InternalRecursionStackEmpty { location: loc() };
assert_eq!(
formatter.format_message(&err),
"internal recursion stack empty"
);
}
#[test]
fn default_recursive_references_require_weak_types() {
let formatter = DefaultMessageFormatter;
let err = Error::RecursiveReferencesRequireWeakTypes { location: loc() };
assert_eq!(
formatter.format_message(&err),
"recursive references require weak recursion types"
);
}
#[test]
fn default_serde_invalid_value() {
let formatter = DefaultMessageFormatter;
let err = Error::SerdeInvalidValue {
unexpected: "null".to_owned(),
expected: "string".to_owned(),
location: loc(),
};
let msg = formatter.format_message(&err);
assert!(msg.contains("invalid value"), "got: {msg}");
assert!(msg.contains("null"), "got: {msg}");
assert!(msg.contains("string"), "got: {msg}");
}
#[test]
fn default_serde_unknown_variant() {
let formatter = DefaultMessageFormatter;
let err = Error::SerdeUnknownVariant {
variant: "foo".to_owned(),
expected: vec!["bar", "baz"],
location: loc(),
};
let msg = formatter.format_message(&err);
assert!(msg.contains("unknown variant"), "got: {msg}");
assert!(msg.contains("foo"), "got: {msg}");
}
#[test]
fn default_serde_unknown_field() {
let formatter = DefaultMessageFormatter;
let err = Error::SerdeUnknownField {
field: "xyz".to_owned(),
expected: vec!["a", "b"],
location: loc(),
};
let msg = formatter.format_message(&err);
assert!(msg.contains("unknown field"), "got: {msg}");
assert!(msg.contains("xyz"), "got: {msg}");
}
#[test]
fn default_unexpected_container_end_while_reading_key() {
let formatter = DefaultMessageFormatter;
let err = Error::UnexpectedContainerEndWhileReadingKeyNode { location: loc() };
assert_eq!(
formatter.format_message(&err),
"unexpected container end while reading key"
);
}
#[test]
fn default_expected_mapping_end_after_enum_variant() {
let formatter = DefaultMessageFormatter;
let err = Error::ExpectedMappingEndAfterEnumVariantValue { location: loc() };
assert_eq!(
formatter.format_message(&err),
"expected end of mapping after enum variant value"
);
}
#[test]
fn default_container_end_mismatch() {
let formatter = DefaultMessageFormatter;
let err = Error::ContainerEndMismatch { location: loc() };
assert_eq!(
formatter.format_message(&err),
"list or mapping end with no start"
);
}
#[test]
fn default_io_error() {
let formatter = DefaultMessageFormatter;
let err = Error::IOError {
cause: std::io::Error::other("disk full"),
};
let msg = formatter.format_message(&err);
assert!(msg.contains("IO error"), "got: {msg}");
assert!(msg.contains("disk full"), "got: {msg}");
}
#[test]
fn default_unresolved_property() {
let formatter = DefaultMessageFormatter;
let err = Error::UnresolvedProperty {
name: "MISSING".to_owned(),
location: loc(),
};
assert_eq!(formatter.format_message(&err), "missing property `MISSING`");
}
#[test]
fn default_invalid_property_name() {
let formatter = DefaultMessageFormatter;
let err = Error::InvalidPropertyName {
name: "${ab-cd}".to_owned(),
location: loc(),
};
assert_eq!(formatter.format_message(&err), "Invalid name: '${ab-cd}'");
}
#[test]
fn default_alias_error_both_unknown() {
let formatter = DefaultMessageFormatter;
let err = Error::AliasError {
msg: "alias msg".to_owned(),
locations: Locations::UNKNOWN,
};
assert_eq!(formatter.format_message(&err), "alias msg");
}
#[test]
fn default_alias_error_ref_known_def_unknown() {
let formatter = DefaultMessageFormatter;
let ref_loc = Location::new(1, 0);
let err = Error::AliasError {
msg: "alias msg".to_owned(),
locations: Locations {
reference_location: ref_loc,
defined_location: Location::UNKNOWN,
},
};
assert_eq!(formatter.format_message(&err), "alias msg");
}
#[test]
fn default_alias_error_both_known_different() {
let formatter = DefaultMessageFormatter;
let ref_loc = Location::new(1, 0);
let def_loc = Location::new(5, 0);
let err = Error::AliasError {
msg: "alias msg".to_owned(),
locations: Locations {
reference_location: ref_loc,
defined_location: def_loc,
},
};
let msg = formatter.format_message(&err);
assert!(msg.starts_with("alias msg"), "got: {msg}");
}
#[test]
fn user_with_snippet_delegates() {
let formatter = UserMessageFormatter;
let inner = Error::Eof { location: loc() };
let err = Error::WithSnippet {
regions: vec![],
crop_radius: 3,
error: Box::new(inner),
};
assert_eq!(formatter.format_message(&err), "unexpected end of file");
}
#[test]
fn user_eof() {
let formatter = UserMessageFormatter;
let err = Error::Eof { location: loc() };
assert_eq!(formatter.format_message(&err), "unexpected end of file");
}
#[test]
fn user_multiple_documents() {
let formatter = UserMessageFormatter;
let err = Error::MultipleDocuments {
hint: "use from_str_multidoc",
location: loc(),
};
assert_eq!(
formatter.format_message(&err),
"only single YAML document expected but multiple found"
);
}
#[test]
fn user_invalid_utf8_input() {
let formatter = UserMessageFormatter;
let err = Error::InvalidUtf8Input;
assert_eq!(
formatter.format_message(&err),
"YAML parser input is not valid UTF-8"
);
}
#[test]
fn user_binary_not_utf8() {
let formatter = UserMessageFormatter;
let err = Error::BinaryNotUtf8 { location: loc() };
assert!(formatter.format_message(&err).contains("!!binary"));
}
#[test]
fn user_invalid_boolean_strict() {
let formatter = UserMessageFormatter;
let err = Error::InvalidBooleanStrict { location: loc() };
assert_eq!(
formatter.format_message(&err),
"invalid boolean (true or false expected)"
);
}
#[test]
fn user_null_into_string() {
let formatter = UserMessageFormatter;
let err = Error::NullIntoString { location: loc() };
assert_eq!(formatter.format_message(&err), "null is not allowed here");
}
#[test]
fn user_invalid_char_null() {
let formatter = UserMessageFormatter;
let err = Error::InvalidCharNull { location: loc() };
assert_eq!(formatter.format_message(&err), "null is not allowed here");
}
#[test]
fn user_invalid_char_not_single_scalar() {
let formatter = UserMessageFormatter;
let err = Error::InvalidCharNotSingleScalar { location: loc() };
assert_eq!(
formatter.format_message(&err),
"only single character allowed here"
);
}
#[test]
fn user_bytes_not_supported_missing_binary_tag() {
let formatter = UserMessageFormatter;
let err = Error::BytesNotSupportedMissingBinaryTag { location: loc() };
assert_eq!(formatter.format_message(&err), "missing !!binary tag");
}
#[test]
fn user_expected_empty_mapping_for_unit_struct() {
let formatter = UserMessageFormatter;
let err = Error::ExpectedEmptyMappingForUnitStruct { location: loc() };
assert_eq!(
formatter.format_message(&err),
"expected empty mapping here"
);
}
#[test]
fn user_unexpected_container_end_while_skipping() {
let formatter = UserMessageFormatter;
let err = Error::UnexpectedContainerEndWhileSkippingNode { location: loc() };
assert_eq!(formatter.format_message(&err), "unexpected container end");
}
#[test]
fn user_alias_replay_counter_overflow() {
let formatter = UserMessageFormatter;
let err = Error::AliasReplayCounterOverflow { location: loc() };
assert_eq!(
formatter.format_message(&err),
"YAML document too large or too complex"
);
}
#[test]
fn user_alias_replay_limit_exceeded() {
let formatter = UserMessageFormatter;
let err = Error::AliasReplayLimitExceeded {
total_replayed_events: 1000,
max_total_replayed_events: 500,
location: loc(),
};
let msg = formatter.format_message(&err);
assert!(msg.contains("too large or too complex"), "got: {msg}");
assert!(msg.contains("1000"), "got: {msg}");
}
#[test]
fn user_alias_expansion_limit_exceeded() {
let formatter = UserMessageFormatter;
let err = Error::AliasExpansionLimitExceeded {
anchor_id: 7,
expansions: 200,
max_expansions_per_anchor: 100,
location: loc(),
};
let msg = formatter.format_message(&err);
assert!(msg.contains("too large or too complex"), "got: {msg}");
assert!(msg.contains("7"), "got: {msg}");
}
#[test]
fn user_alias_replay_stack_depth_exceeded() {
let formatter = UserMessageFormatter;
let err = Error::AliasReplayStackDepthExceeded {
depth: 50,
max_depth: 20,
location: loc(),
};
let msg = formatter.format_message(&err);
assert!(msg.contains("too large or too complex"), "got: {msg}");
assert!(msg.contains("50"), "got: {msg}");
}
#[test]
fn user_unknown_anchor() {
let formatter = UserMessageFormatter;
let err = Error::UnknownAnchor { location: loc() };
assert_eq!(formatter.format_message(&err), "reference to unknown value");
}
#[test]
fn user_recursive_references_require_weak_types() {
let formatter = UserMessageFormatter;
let err = Error::RecursiveReferencesRequireWeakTypes { location: loc() };
assert_eq!(
formatter.format_message(&err),
"Recursive reference not allowed here"
);
}
#[test]
fn user_duplicate_mapping_key_with_key() {
let formatter = UserMessageFormatter;
let err = Error::DuplicateMappingKey {
key: Some("mykey".to_owned()),
location: loc(),
};
let msg = formatter.format_message(&err);
assert!(msg.contains("mykey"), "got: {msg}");
assert!(msg.contains("duplicate"), "got: {msg}");
}
#[test]
fn user_duplicate_mapping_key_without_key() {
let formatter = UserMessageFormatter;
let err = Error::DuplicateMappingKey {
key: None,
location: loc(),
};
let msg = formatter.format_message(&err);
assert!(msg.contains("duplicate"), "got: {msg}");
}
#[test]
fn user_quoting_required() {
let formatter = UserMessageFormatter;
let err = Error::QuotingRequired {
value: "yes".to_owned(),
location: loc(),
};
assert_eq!(formatter.format_message(&err), "value requires quoting");
}
#[test]
fn user_budget() {
use crate::budget::BudgetBreach;
let formatter = UserMessageFormatter;
let err = Error::Budget {
breach: BudgetBreach::Events { events: 9999 },
location: loc(),
};
let msg = formatter.format_message(&err);
assert!(msg.contains("too large or too complex"), "got: {msg}");
}
#[test]
fn user_cannot_borrow_transformed_string() {
let formatter = UserMessageFormatter;
let err = Error::CannotBorrowTransformedString {
reason: TransformReason::EscapeSequence,
location: loc(),
};
assert_eq!(
formatter.format_message(&err),
"Only single string with no escape sequences is allowed here"
);
}
#[test]
fn user_falls_through_to_default_for_unhandled() {
let formatter = UserMessageFormatter;
let err = Error::SerdeInvalidType {
unexpected: "seq".to_owned(),
expected: "map".to_owned(),
location: loc(),
};
let msg = formatter.format_message(&err);
assert!(msg.contains("invalid type"), "got: {msg}");
}
#[test]
fn user_with_localizer_delegates() {
use crate::localizer::DefaultEnglishLocalizer;
let localizer = DefaultEnglishLocalizer;
let formatter = UserMessageFormatter.with_localizer(&localizer);
let err = Error::Eof { location: loc() };
assert_eq!(formatter.format_message(&err), "unexpected end of file");
}
#[test]
fn default_with_localizer_delegates() {
use crate::localizer::DefaultEnglishLocalizer;
let localizer = DefaultEnglishLocalizer;
let formatter = DefaultMessageFormatter.with_localizer(&localizer);
let err = Error::Eof { location: loc() };
assert_eq!(formatter.format_message(&err), "unexpected end of input");
}
}