Skip to content

Commit

Permalink
Collect errors when deserializing untagged enums
Browse files Browse the repository at this point in the history
This also adds a verbose-debug feature that enables the new behavior.

Previously:

> data did not match any variant of untagged enum ReferenceOr at line 6 column 4

Now, with `verbose-debug` feature enabled:

> data did not match any variant of untagged enum `ReferenceOr`
> 	- attempted to deserialize `Reference` but failed with: missing field `$ref`
> 	- attempted to deserialize `Item` but failed with: invalid value: string "lol", expected expected format `\dXX` at line 6 column 4

cf. serde-rs#773

Enable verbose-debug unconditionally
  • Loading branch information
killercup authored and jonasbb committed Jun 11, 2021
1 parent 7b84089 commit f50bdd7
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 9 deletions.
33 changes: 26 additions & 7 deletions serde_derive/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1649,38 +1649,57 @@ fn deserialize_untagged_enum(
.iter()
.filter(|variant| !variant.attrs.skip_deserializing())
.map(|variant| {
Expr(deserialize_untagged_variant(
let de_any = Expr(deserialize_untagged_variant(
params,
variant,
cattrs,
quote!(
_serde::__private::de::ContentRefDeserializer::<__D::Error>::new(&__content)
),
))
});
));

let variant_name = variant.ident.to_string();
quote! {
_serde::__private::Result::map_err(
#de_any,
|e| -> __D::Error {
_serde::de::Error::custom(
format!("attempted to deserialize `{}` but failed with: {}", #variant_name, e.to_string())
)
},
)
}
});
// TODO this message could be better by saving the errors from the failed
// attempts. The heuristic used by TOML was to count the number of fields
// processed before an error, and use the error that happened after the
// largest number of fields. I'm not sure I like that. Maybe it would be
// better to save all the errors and combine them into one message that
// explains why none of the variants matched.
let fallthrough_msg = format!(
"data did not match any variant of untagged enum {}",
"data did not match any variant of untagged enum `{}`",
params.type_name()
);
let fallthrough_msg = cattrs.expecting().unwrap_or(&fallthrough_msg);
let has_custom_err_msg = cattrs.expecting().is_some();

quote_block! {
let __content = try!(<_serde::__private::de::Content as _serde::Deserialize>::deserialize(__deserializer));
let mut fallthrough_msg = #fallthrough_msg.to_string();

#(
if let _serde::__private::Ok(__ok) = #attempts {
return _serde::__private::Ok(__ok);
match #attempts {
_serde::__private::Ok(__ok) => return _serde::__private::Ok(__ok),
_serde::__private::Err(__err) => {
if !#has_custom_err_msg {
fallthrough_msg.push_str("\n\t- ");
fallthrough_msg.push_str(&__err.to_string());
}
}
}
)*

_serde::__private::Err(_serde::de::Error::custom(#fallthrough_msg))
_serde::__private::Err(_serde::de::Error::custom(fallthrough_msg))
}
}

Expand Down
16 changes: 14 additions & 2 deletions test_suite/tests/test_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,13 @@ fn test_untagged_enum() {

assert_de_tokens_error::<Untagged>(
&[Token::Tuple { len: 1 }, Token::U8(1), Token::TupleEnd],
"data did not match any variant of untagged enum Untagged",
"data did not match any variant of untagged enum `Untagged`
\t- attempted to deserialize `A` but failed with: invalid type: sequence, expected struct variant Untagged::A
\t- attempted to deserialize `B` but failed with: invalid type: sequence, expected struct variant Untagged::B
\t- attempted to deserialize `C` but failed with: invalid type: sequence, expected unit variant Untagged::C
\t- attempted to deserialize `D` but failed with: invalid type: sequence, expected u8
\t- attempted to deserialize `E` but failed with: invalid type: sequence, expected a string
\t- attempted to deserialize `F` but failed with: invalid length 1, expected tuple variant Untagged::F with 2 elements"
);

assert_de_tokens_error::<Untagged>(
Expand All @@ -668,7 +674,13 @@ fn test_untagged_enum() {
Token::U8(3),
Token::TupleEnd,
],
"data did not match any variant of untagged enum Untagged",
"data did not match any variant of untagged enum `Untagged`
\t- attempted to deserialize `A` but failed with: invalid type: sequence, expected struct variant Untagged::A
\t- attempted to deserialize `B` but failed with: invalid type: sequence, expected struct variant Untagged::B
\t- attempted to deserialize `C` but failed with: invalid type: sequence, expected unit variant Untagged::C
\t- attempted to deserialize `D` but failed with: invalid type: sequence, expected u8
\t- attempted to deserialize `E` but failed with: invalid type: sequence, expected a string
\t- attempted to deserialize `F` but failed with: invalid length 3, expected 2 elements in sequence"
);
}

Expand Down

0 comments on commit f50bdd7

Please sign in to comment.