Skip to content

Commit

Permalink
Allow to define custom expectation message for type with `#[serde(exp…
Browse files Browse the repository at this point in the history
…ecting = "...")]`

Closes #1883
  • Loading branch information
Mingun committed Oct 22, 2020
1 parent 23c14e5 commit 104ad9a
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 5 deletions.
6 changes: 4 additions & 2 deletions serde/src/private/de.rs
Expand Up @@ -824,15 +824,17 @@ mod content {
/// Not public API.
pub struct TaggedContentVisitor<'de, T> {
tag_name: &'static str,
expecting: &'static str,
value: PhantomData<TaggedContent<'de, T>>,
}

impl<'de, T> TaggedContentVisitor<'de, T> {
/// Visitor for the content of an internally tagged enum with the given
/// tag name.
pub fn new(name: &'static str) -> Self {
pub fn new(name: &'static str, expecting: &'static str) -> Self {
TaggedContentVisitor {
tag_name: name,
expecting: expecting,
value: PhantomData,
}
}
Expand Down Expand Up @@ -861,7 +863,7 @@ mod content {
type Value = TaggedContent<'de, T>;

fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("internally tagged enum")
fmt.write_str(self.expecting)
}

fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
Expand Down
22 changes: 19 additions & 3 deletions serde_derive/src/de.rs
Expand Up @@ -399,6 +399,7 @@ fn deserialize_unit_struct(params: &Parameters, cattrs: &attr::Container) -> Fra
let type_name = cattrs.name().deserialize_name();

let expecting = format!("unit struct {}", params.type_name());
let expecting = cattrs.expecting().unwrap_or(&expecting);

quote_block! {
struct __Visitor;
Expand Down Expand Up @@ -456,6 +457,7 @@ fn deserialize_tuple(
Some(variant_ident) => format!("tuple variant {}::{}", params.type_name(), variant_ident),
None => format!("tuple struct {}", params.type_name()),
};
let expecting = cattrs.expecting().unwrap_or(&expecting);

let nfields = fields.len();

Expand Down Expand Up @@ -542,6 +544,7 @@ fn deserialize_tuple_in_place(
Some(variant_ident) => format!("tuple variant {}::{}", params.type_name(), variant_ident),
None => format!("tuple struct {}", params.type_name()),
};
let expecting = cattrs.expecting().unwrap_or(&expecting);

let nfields = fields.len();

Expand Down Expand Up @@ -630,6 +633,7 @@ fn deserialize_seq(
} else {
format!("{} with {} elements", expecting, deserialized_count)
};
let expecting = cattrs.expecting().unwrap_or(&expecting);

let mut index_in_seq = 0_usize;
let let_values = vars.clone().zip(fields).map(|(var, field)| {
Expand Down Expand Up @@ -732,6 +736,7 @@ fn deserialize_seq_in_place(
} else {
format!("{} with {} elements", expecting, deserialized_count)
};
let expecting = cattrs.expecting().unwrap_or(&expecting);

let mut index_in_seq = 0usize;
let write_values = fields.iter().map(|field| {
Expand Down Expand Up @@ -907,6 +912,7 @@ fn deserialize_struct(
Some(variant_ident) => format!("struct variant {}::{}", params.type_name(), variant_ident),
None => format!("struct {}", params.type_name()),
};
let expecting = cattrs.expecting().unwrap_or(&expecting);

let visit_seq = Stmts(deserialize_seq(
&type_path, params, fields, true, cattrs, &expecting,
Expand Down Expand Up @@ -1048,6 +1054,7 @@ fn deserialize_struct_in_place(
Some(variant_ident) => format!("struct variant {}::{}", params.type_name(), variant_ident),
None => format!("struct {}", params.type_name()),
};
let expecting = cattrs.expecting().unwrap_or(&expecting);

let visit_seq = Stmts(deserialize_seq_in_place(params, fields, cattrs, &expecting));

Expand Down Expand Up @@ -1200,6 +1207,7 @@ fn deserialize_externally_tagged_enum(

let type_name = cattrs.name().deserialize_name();
let expecting = format!("enum {}", params.type_name());
let expecting = cattrs.expecting().unwrap_or(&expecting);

let (variants_stmt, variant_visitor) = prepare_enum_variant_enum(variants, cattrs);

Expand Down Expand Up @@ -1309,14 +1317,17 @@ fn deserialize_internally_tagged_enum(
}
});

let expecting = format!("internally tagged enum {}", params.type_name());
let expecting = cattrs.expecting().unwrap_or(&expecting);

quote_block! {
#variant_visitor

#variants_stmt

let __tagged = try!(_serde::Deserializer::deserialize_any(
__deserializer,
_serde::private::de::TaggedContentVisitor::<__Field>::new(#tag)));
_serde::private::de::TaggedContentVisitor::<__Field>::new(#tag, #expecting)));

match __tagged.tag {
#(#variant_arms)*
Expand Down Expand Up @@ -1359,6 +1370,7 @@ fn deserialize_adjacently_tagged_enum(
.collect();

let expecting = format!("adjacently tagged enum {}", params.type_name());
let expecting = cattrs.expecting().unwrap_or(&expecting);
let type_name = cattrs.name().deserialize_name();
let deny_unknown_fields = cattrs.deny_unknown_fields();

Expand Down Expand Up @@ -1648,6 +1660,7 @@ fn deserialize_untagged_enum(
"data did not match any variant of untagged enum {}",
params.type_name()
);
let fallthrough_msg = cattrs.expecting().unwrap_or(&fallthrough_msg);

quote_block! {
let __content = try!(<_serde::private::de::Content as _serde::Deserialize>::deserialize(__deserializer));
Expand Down Expand Up @@ -1905,6 +1918,7 @@ fn deserialize_generated_identifier(
is_variant,
fallthrough,
!is_variant && cattrs.has_flatten(),
None,
));

let lifetime = if !is_variant && cattrs.has_flatten() {
Expand Down Expand Up @@ -2012,6 +2026,7 @@ fn deserialize_custom_identifier(
is_variant,
fallthrough,
false,
cattrs.expecting(),
));

quote_block! {
Expand Down Expand Up @@ -2042,6 +2057,7 @@ fn deserialize_identifier(
is_variant: bool,
fallthrough: Option<TokenStream>,
collect_other_fields: bool,
expecting: Option<&str>,
) -> Fragment {
let mut flat_fields = Vec::new();
for (_, ident, aliases) in fields {
Expand All @@ -2066,11 +2082,11 @@ fn deserialize_identifier(
.map(|(_, ident, _)| quote!(#this::#ident))
.collect();

let expecting = if is_variant {
let expecting = expecting.unwrap_or(if is_variant {
"variant identifier"
} else {
"field identifier"
};
});

let index_expecting = if is_variant { "variant" } else { "field" };

Expand Down
17 changes: 17 additions & 0 deletions serde_derive/src/internals/attr.rs
Expand Up @@ -223,6 +223,8 @@ pub struct Container {
has_flatten: bool,
serde_path: Option<syn::Path>,
is_packed: bool,
/// Error message generated when type can't be deserialized
expecting: Option<String>,
}

/// Styles of representing an enum.
Expand Down Expand Up @@ -305,6 +307,7 @@ impl Container {
let mut field_identifier = BoolAttr::none(cx, FIELD_IDENTIFIER);
let mut variant_identifier = BoolAttr::none(cx, VARIANT_IDENTIFIER);
let mut serde_path = Attr::none(cx, CRATE);
let mut expecting = Attr::none(cx, EXPECTING);

for meta_item in item
.attrs
Expand Down Expand Up @@ -575,6 +578,13 @@ impl Container {
}
}

// Parse `#[serde(expecting = "a message")]`
Meta(NameValue(m)) if m.path == EXPECTING => {
if let Ok(s) = get_lit_str(cx, EXPECTING, &m.lit) {
expecting.set(&m.path, s.value());
}
}

Meta(meta_item) => {
let path = meta_item
.path()
Expand Down Expand Up @@ -627,6 +637,7 @@ impl Container {
has_flatten: false,
serde_path: serde_path.get(),
is_packed,
expecting: expecting.get(),
}
}

Expand Down Expand Up @@ -702,6 +713,12 @@ impl Container {
self.custom_serde_path()
.map_or_else(|| Cow::Owned(parse_quote!(_serde)), Cow::Borrowed)
}

/// Error message generated when type can't be deserialized.
/// If `None`, default message will be used
pub fn expecting(&self) -> Option<&str> {
self.expecting.as_ref().map(String::as_ref)
}
}

fn decide_tag(
Expand Down
1 change: 1 addition & 0 deletions serde_derive/src/internals/symbol.rs
Expand Up @@ -35,6 +35,7 @@ pub const TRY_FROM: Symbol = Symbol("try_from");
pub const UNTAGGED: Symbol = Symbol("untagged");
pub const VARIANT_IDENTIFIER: Symbol = Symbol("variant_identifier");
pub const WITH: Symbol = Symbol("with");
pub const EXPECTING: Symbol = Symbol("expecting");

impl PartialEq<Symbol> for Ident {
fn eq(&self, word: &Symbol) -> bool {
Expand Down

0 comments on commit 104ad9a

Please sign in to comment.