1use crate::de_error::{Error, MessageFormatter, UserMessageFormatter};
2use crate::localizer::{ExternalMessage, Localizer};
3use crate::Location;
4
5use std::borrow::Cow;
6
7#[cfg(any(feature = "garde", feature = "validator"))]
8use crate::localizer::ExternalMessageSource;
9
10#[cfg(any(feature = "garde", feature = "validator"))]
11use crate::path_map::format_path_with_resolved_leaf;
12
13#[cfg(any(feature = "garde", feature = "validator"))]
14use crate::Locations;
15
16#[cfg(feature = "garde")]
17use crate::de_error::collect_garde_issues;
18
19#[cfg(feature = "validator")]
20use crate::de_error::collect_validator_issues;
21
22#[derive(Debug, Default, Clone, Copy)]
28pub struct DefaultMessageFormatter;
29
30pub type DeveloperMessageFormatter = DefaultMessageFormatter;
32
33fn default_format_message<'a>(formatter: &dyn MessageFormatter, err: &'a Error) -> Cow<'a, str> {
34 match err {
35 Error::WithSnippet { error, .. } => default_format_message(formatter, error),
36 Error::ExternalMessage {
37 source,
38 msg,
39 code,
40 params,
41 ..
42 } => {
43 let l10n = formatter.localizer();
44 l10n.override_external_message(ExternalMessage {
45 source: *source,
46 original: msg.as_str(),
47 code: code.as_deref(),
48 params,
49 })
50 .unwrap_or(Cow::Borrowed(msg.as_str()))
51 }
52 Error::Message { msg, .. }
53 | Error::HookError { msg, .. }
54 | Error::SerdeVariantId { msg, .. } => Cow::Borrowed(msg.as_str()),
55 Error::Eof { .. } => Cow::Borrowed("unexpected end of input"),
56 Error::MultipleDocuments { hint, .. } => {
57 Cow::Owned(format!("multiple YAML documents detected; {hint}"))
58 }
59 Error::Unexpected { expected, .. } => {
60 Cow::Owned(format!("unexpected event: expected {expected}"))
61 }
62 Error::MergeValueNotMapOrSeqOfMaps { .. } => {
63 Cow::Borrowed("YAML merge value must be mapping or sequence of mappings")
64 }
65 Error::InvalidBinaryBase64 { .. } => Cow::Borrowed("invalid !!binary base64"),
66 Error::InvalidUtf8Input => Cow::Borrowed("input is not valid UTF-8"),
67 Error::BinaryNotUtf8 { .. } => Cow::Borrowed(
68 "!!binary scalar is not valid UTF-8 so cannot be stored into string. \
69 If you just use !!binary for documentation/annotation, set ignore_binary_tag_for_string in Options",
70 ),
71 Error::TaggedScalarCannotDeserializeIntoString { .. } => {
72 Cow::Borrowed("cannot deserialize tagged scalar into string")
73 }
74 Error::UnexpectedSequenceEnd { .. } => Cow::Borrowed("unexpected sequence end"),
75 Error::UnexpectedMappingEnd { .. } => Cow::Borrowed("unexpected mapping end"),
76 Error::InvalidBooleanStrict { .. } => {
77 Cow::Borrowed("invalid boolean (strict mode expects true/false)")
78 }
79 Error::InvalidCharNull { .. } => {
80 Cow::Borrowed("invalid char: cannot deserialize null; use Option<char>")
81 }
82 Error::InvalidCharNotSingleScalar { .. } => {
83 Cow::Borrowed("invalid char: expected a single Unicode scalar value")
84 }
85 Error::NullIntoString { .. } => {
86 Cow::Borrowed("cannot deserialize null into string; use Option<String>")
87 }
88 Error::BytesNotSupportedMissingBinaryTag { .. } => {
89 Cow::Borrowed("bytes not supported (missing !!binary tag)")
90 }
91 Error::UnexpectedValueForUnit { .. } => Cow::Borrowed("unexpected value for unit"),
92 Error::ExpectedEmptyMappingForUnitStruct { .. } => {
93 Cow::Borrowed("expected empty mapping for unit struct")
94 }
95 Error::UnexpectedContainerEndWhileSkippingNode { .. } => {
96 Cow::Borrowed("unexpected container end while skipping node")
97 }
98 Error::InternalSeedReusedForMapKey { .. } => {
99 Cow::Borrowed("internal error: seed reused for map key")
100 }
101 Error::ValueRequestedBeforeKey { .. } => Cow::Borrowed("value requested before key"),
102 Error::ExpectedStringKeyForExternallyTaggedEnum { .. } => {
103 Cow::Borrowed("expected string key for externally tagged enum")
104 }
105 Error::ExternallyTaggedEnumExpectedScalarOrMapping { .. } => {
106 Cow::Borrowed("externally tagged enum expected scalar or mapping")
107 }
108 Error::UnexpectedValueForUnitEnumVariant { .. } => {
109 Cow::Borrowed("unexpected value for unit enum variant")
110 }
111 Error::AliasReplayCounterOverflow { .. } => Cow::Borrowed("alias replay counter overflow"),
112 Error::AliasReplayLimitExceeded {
113 total_replayed_events,
114 max_total_replayed_events,
115 ..
116 } => Cow::Owned(format!(
117 "alias replay limit exceeded: total_replayed_events={total_replayed_events} > {max_total_replayed_events}"
118 )),
119 Error::AliasExpansionLimitExceeded {
120 anchor_id,
121 expansions,
122 max_expansions_per_anchor,
123 ..
124 } => Cow::Owned(format!(
125 "alias expansion limit exceeded for anchor id {anchor_id}: {expansions} > {max_expansions_per_anchor}"
126 )),
127 Error::AliasReplayStackDepthExceeded {
128 depth,
129 max_depth,
130 ..
131 } => Cow::Owned(format!(
132 "alias replay stack depth exceeded: depth={depth} > {max_depth}"
133 )),
134 Error::FoldedBlockScalarMustIndentContent { .. } => {
135 Cow::Borrowed("folded block scalars must indent their content")
136 }
137 Error::InternalDepthUnderflow { .. } => Cow::Borrowed("internal depth underflow"),
138 Error::InternalRecursionStackEmpty { .. } => {
139 Cow::Borrowed("internal recursion stack empty")
140 }
141 Error::RecursiveReferencesRequireWeakTypes { .. } => {
142 Cow::Borrowed("recursive references require weak recursion types")
143 }
144 Error::InvalidScalar { ty, .. } => Cow::Owned(format!("invalid {ty}")),
145 Error::SerdeInvalidType {
146 unexpected,
147 expected,
148 ..
149 } => Cow::Owned(format!("invalid type: {unexpected}, expected {expected}")),
150 Error::SerdeInvalidValue {
151 unexpected,
152 expected,
153 ..
154 } => Cow::Owned(format!("invalid value: {unexpected}, expected {expected}")),
155 Error::SerdeUnknownVariant {
156 variant,
157 expected,
158 ..
159 } => Cow::Owned(format!(
160 "unknown variant `{variant}`, expected one of {}",
161 expected.join(", ")
162 )),
163 Error::SerdeUnknownField {
164 field,
165 expected,
166 ..
167 } => Cow::Owned(format!(
168 "unknown field `{field}`, expected one of {}",
169 expected.join(", ")
170 )),
171 Error::SerdeMissingField { field, .. } => Cow::Owned(format!("missing field `{field}`")),
172 Error::UnexpectedContainerEndWhileReadingKeyNode { .. } => {
173 Cow::Borrowed("unexpected container end while reading key")
174 }
175 Error::DuplicateMappingKey { key, .. } => match key {
176 Some(k) => Cow::Owned(format!(
177 "duplicate mapping key: {k}, set DuplicateKeyPolicy in Options if acceptable"
178 )),
179 None => Cow::Borrowed(
180 "duplicate mapping key, set DuplicateKeyPolicy in Options if acceptable",
181 ),
182 },
183 Error::TaggedEnumMismatch { tagged, target, .. } => Cow::Owned(format!(
184 "tagged enum `{tagged}` does not match target enum `{target}`",
185 )),
186 Error::ExpectedMappingEndAfterEnumVariantValue { .. } => {
187 Cow::Borrowed("expected end of mapping after enum variant value")
188 }
189 Error::ContainerEndMismatch { .. } => Cow::Borrowed("list or mapping end with no start"),
190 Error::UnknownAnchor { .. } => Cow::Borrowed("alias references unknown anchor"),
191 Error::Budget { breach, .. } => Cow::Owned(format!("budget breached: {breach:?}")),
192 Error::QuotingRequired { value, .. } => {
193 Cow::Owned(format!("The string value [{value}] must be quoted"))
194 }
195 Error::CannotBorrowTransformedString { reason, .. } => Cow::Owned(format!(
196 "input does not contain value verbatim so cannot deserialize into &str ({reason}); use String or Cow<str> instead",
197 )),
198 Error::IOError { cause } => Cow::Owned(format!("IO error: {cause}")),
199 Error::AliasError { msg, locations } => {
200 let l10n = formatter.localizer();
201 let ref_loc = locations.reference_location;
202 let def_loc = locations.defined_location;
203 match (ref_loc, def_loc) {
204 (Location::UNKNOWN, Location::UNKNOWN) => Cow::Borrowed(msg.as_str()),
205 (r, d) if r != Location::UNKNOWN && (d == Location::UNKNOWN || d == r) => {
206 Cow::Borrowed(msg.as_str())
207 }
208 (_r, d) => Cow::Owned(format!("{msg}{}", l10n.alias_defined_at(d))),
209 }
210 }
211
212 #[cfg(feature = "garde")]
213 Error::ValidationError { report, locations } => {
214 let l10n = formatter.localizer();
215
216 let issues = collect_garde_issues(report);
217 let mut lines = Vec::with_capacity(issues.len());
218 for issue in issues {
219 let entry = issue.display_entry_overridden(l10n, ExternalMessageSource::Garde);
220 let path_key = issue.path;
221 let original_leaf = path_key
222 .leaf_string()
223 .unwrap_or_else(|| l10n.root_path_label().into_owned());
224
225 let (locs, resolved_leaf) = locations
226 .search(&path_key)
227 .unwrap_or((Locations::UNKNOWN, original_leaf));
228
229 let loc = if locs.reference_location != Location::UNKNOWN {
230 locs.reference_location
231 } else {
232 locs.defined_location
233 };
234
235 let resolved_path = format_path_with_resolved_leaf(&path_key, &resolved_leaf);
236
237 lines.push(l10n.validation_issue_line(
238 &resolved_path,
239 &entry,
240 (loc != Location::UNKNOWN).then_some(loc),
241 ));
242 }
243 Cow::Owned(l10n.join_validation_issues(&lines))
244 }
245 #[cfg(feature = "garde")]
246 Error::ValidationErrors { errors } => {
247 Cow::Owned(format!("validation failed for {} document(s)", errors.len()))
248 }
249 #[cfg(feature = "validator")]
250 Error::ValidatorError { errors, locations } => {
251 let l10n = formatter.localizer();
252
253 let issues = collect_validator_issues(errors);
254 let mut lines = Vec::with_capacity(issues.len());
255 for issue in issues {
256 let entry = issue.display_entry_overridden(l10n, ExternalMessageSource::Validator);
257 let path_key = issue.path;
258 let original_leaf = path_key
259 .leaf_string()
260 .unwrap_or_else(|| l10n.root_path_label().into_owned());
261
262 let (locs, resolved_leaf) = locations
263 .search(&path_key)
264 .unwrap_or((Locations::UNKNOWN, original_leaf));
265
266 let loc = if locs.reference_location != Location::UNKNOWN {
267 locs.reference_location
268 } else {
269 locs.defined_location
270 };
271
272 let resolved_path = format_path_with_resolved_leaf(&path_key, &resolved_leaf);
273
274 lines.push(l10n.validation_issue_line(
275 &resolved_path,
276 &entry,
277 (loc != Location::UNKNOWN).then_some(loc),
278 ));
279 }
280 Cow::Owned(l10n.join_validation_issues(&lines))
281 }
282 #[cfg(feature = "validator")]
283 Error::ValidatorErrors { errors } => {
284 Cow::Owned(format!("validation failed for {} document(s)", errors.len()))
285 }
286 }
287}
288
289impl MessageFormatter for DefaultMessageFormatter {
290 fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
291 default_format_message(self, err)
292 }
293}
294
295pub struct DefaultMessageFormatterWithLocalizer<'a> {
296 localizer: &'a dyn Localizer,
297}
298
299impl MessageFormatter for DefaultMessageFormatterWithLocalizer<'_> {
300 fn localizer(&self) -> &dyn Localizer {
301 self.localizer
302 }
303
304 fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
305 default_format_message(self, err)
306 }
307}
308
309impl DefaultMessageFormatter {
310 pub fn with_localizer<'a>(
316 &self,
317 localizer: &'a dyn Localizer,
318 ) -> DefaultMessageFormatterWithLocalizer<'a> {
319 DefaultMessageFormatterWithLocalizer { localizer }
320 }
321}
322
323fn user_format_message<'a>(formatter: &dyn MessageFormatter, err: &'a Error) -> Cow<'a, str> {
324 if let Error::WithSnippet { error, .. } = err {
325 return user_format_message(formatter, error);
326 }
327
328 match err {
329 Error::WithSnippet { .. } => unreachable!(),
331
332 Error::Eof { .. } => Cow::Borrowed("unexpected end of file"),
333 Error::MultipleDocuments { .. } => {
334 Cow::Borrowed("only single YAML document expected but multiple found")
335 }
336 Error::InvalidUtf8Input => Cow::Borrowed("YAML parser input is not valid UTF-8"),
337 Error::BinaryNotUtf8 { .. } => {
338 Cow::Borrowed("!!binary scalar is not valid UTF-8 so cannot be stored into string.")
339 }
340 Error::InvalidBooleanStrict { .. } => Cow::Borrowed("invalid boolean (true or false expected)"),
341 Error::NullIntoString { .. } | Error::InvalidCharNull { .. } => Cow::Borrowed("null is not allowed here"),
342 Error::InvalidCharNotSingleScalar { .. } => Cow::Borrowed("only single character allowed here"),
343 Error::BytesNotSupportedMissingBinaryTag { .. } => Cow::Borrowed("missing !!binary tag"),
344 Error::ExpectedEmptyMappingForUnitStruct { .. } => Cow::Borrowed("expected empty mapping here"),
345 Error::UnexpectedContainerEndWhileSkippingNode { .. } => Cow::Borrowed("unexpected container end"),
346 Error::AliasReplayCounterOverflow { .. } => Cow::Borrowed("YAML document too large or too complex"),
347 Error::AliasReplayLimitExceeded {
348 total_replayed_events,
349 max_total_replayed_events,
350 ..
351 } => Cow::Owned(format!(
352 "YAML document too large or too complex: total_replayed_events={total_replayed_events} > {max_total_replayed_events}"
353 )),
354 Error::AliasExpansionLimitExceeded {
355 anchor_id,
356 expansions,
357 max_expansions_per_anchor,
358 ..
359 } => Cow::Owned(format!(
360 "YAML document too large or too complex: anchor id {anchor_id}: {expansions} > {max_expansions_per_anchor}"
361 )),
362 Error::AliasReplayStackDepthExceeded { depth, max_depth, .. } => Cow::Owned(format!(
363 "YAML document too large or too complex: depth={depth} > {max_depth}"
364 )),
365 Error::UnknownAnchor { .. } => Cow::Borrowed("reference to unknown value"),
366 Error::RecursiveReferencesRequireWeakTypes { .. } => Cow::Borrowed("Recursive reference not allowed here"),
367 Error::DuplicateMappingKey { key, .. } => match key {
368 Some(k) => Cow::Owned(format!("duplicate mapping key: {k} not allowed here")),
369 None => Cow::Borrowed("duplicate mapping key not allowed here"),
370 },
371 Error::QuotingRequired { .. } => Cow::Borrowed("value requires quoting"),
372 Error::Budget { breach, .. } => Cow::Owned(format!(
373 "YAML document too large or too complex: limits breached: {breach:?}"
374 )),
375 Error::CannotBorrowTransformedString { .. } => {
376 Cow::Borrowed("Only single string with no escape sequences is allowed here")
377 }
378
379 _ => default_format_message(formatter, err),
381 }
382}
383
384impl MessageFormatter for UserMessageFormatter {
385 fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
386 user_format_message(self, err)
387 }
388}
389
390pub struct UserMessageFormatterWithLocalizer<'a> {
391 localizer: &'a dyn Localizer,
392}
393
394impl MessageFormatter for UserMessageFormatterWithLocalizer<'_> {
395 fn localizer(&self) -> &dyn Localizer {
396 self.localizer
397 }
398
399 fn format_message<'a>(&self, err: &'a Error) -> Cow<'a, str> {
400 user_format_message(self, err)
401 }
402}
403
404impl UserMessageFormatter {
405 pub fn with_localizer<'a>(
411 &self,
412 localizer: &'a dyn Localizer,
413 ) -> UserMessageFormatterWithLocalizer<'a> {
414 UserMessageFormatterWithLocalizer { localizer }
415 }
416}
417