Attempted support for in_place deserialization for structs as map

This commit is contained in:
Armin Ronacher 2018-03-18 18:22:06 +01:00
parent f1af2dc5ab
commit 61b167be9a
3 changed files with 138 additions and 26 deletions

View File

@ -255,13 +255,6 @@ macro_rules! declare_error_trait {
}
}
/// Raised when a `Deserialize` struct type recieved a field with an
/// unrecognized name but the names are not actually known because of
/// flattening.
fn unknown_field_in_flattened_structure(field: &str) -> Self {
Error::custom(format_args!("unknown field `{}`", field))
}
/// Raised when a `Deserialize` struct type expected to receive a required
/// field with a particular name but that field was not present in the
/// input.

View File

@ -831,7 +831,7 @@ fn deserialize_struct(
}
};
let all_skipped = fields.iter().all(|field| field.attrs.skip_deserializing());
let all_skipped = !cattrs.has_flatten() && fields.iter().all(|field| field.attrs.skip_deserializing());
let visitor_var = if all_skipped {
quote!(_)
} else {
@ -892,6 +892,10 @@ fn deserialize_struct_in_place(
deserializer: Option<Tokens>,
) -> Fragment {
let is_enum = variant_ident.is_some();
let as_map = deserializer.is_none() && !is_enum && match cattrs.repr() {
attr::ContainerRepr::Struct | attr::ContainerRepr::Auto => false,
attr::ContainerRepr::Map => true,
};
let this = &params.this;
let (de_impl_generics, de_ty_generics, ty_generics, where_clause) =
@ -905,10 +909,14 @@ fn deserialize_struct_in_place(
let visit_seq = Stmts(deserialize_seq_in_place(params, fields, cattrs));
let (field_visitor, fields_stmt, visit_map) =
deserialize_struct_in_place_visitor(params, fields, cattrs);
let (field_visitor, fields_stmt, visit_map) = if as_map {
deserialize_struct_as_map_in_place_visitor(params, fields, cattrs)
} else {
deserialize_struct_as_struct_in_place_visitor(params, fields, cattrs)
};
let field_visitor = Stmts(field_visitor);
let fields_stmt = Stmts(fields_stmt);
let fields_stmt = fields_stmt.map(Stmts);
let visit_map = Stmts(visit_map);
let visitor_expr = quote! {
@ -925,6 +933,10 @@ fn deserialize_struct_in_place(
quote! {
_serde::de::VariantAccess::struct_variant(__variant, FIELDS, #visitor_expr)
}
} else if as_map {
quote! {
_serde::Deserializer::deserialize_map(__deserializer, #visitor_expr)
}
} else {
let type_name = cattrs.name().deserialize_name();
quote! {
@ -932,20 +944,24 @@ fn deserialize_struct_in_place(
}
};
let all_skipped = fields.iter().all(|field| field.attrs.skip_deserializing());
let all_skipped = !cattrs.has_flatten() && fields.iter().all(|field| field.attrs.skip_deserializing());
let visitor_var = if all_skipped {
quote!(_)
} else {
quote!(mut __seq)
};
let visit_seq = quote! {
let visit_seq = if as_map {
None
} else {
Some(quote! {
#[inline]
fn visit_seq<__A>(self, #visitor_var: __A) -> _serde::export::Result<Self::Value, __A::Error>
where __A: _serde::de::SeqAccess<#delife>
{
#visit_seq
}
})
};
let in_place_impl_generics = de_impl_generics.in_place();
@ -2081,7 +2097,7 @@ fn deserialize_map(
})
};
let all_skipped = fields.iter().all(|field| field.attrs.skip_deserializing());
let all_skipped = !cattrs.has_flatten() && fields.iter().all(|field| field.attrs.skip_deserializing());
let match_keys = if cattrs.deny_unknown_fields() && all_skipped {
quote! {
// FIXME: Once we drop support for Rust 1.15:
@ -2132,7 +2148,7 @@ fn deserialize_map(
Some(quote! {
if let Some(Some((__key, _))) = __collect.into_iter().filter(|x| x.is_some()).next() {
return _serde::export::Err(
_serde::de::Error::unknown_field_in_flattened_structure(&__key));
_serde::de::Error::custom(format_args!("unknown field `{}`", &__key)));
}
})
} else {
@ -2191,11 +2207,11 @@ fn deserialize_map(
}
#[cfg(feature = "deserialize_in_place")]
fn deserialize_struct_in_place_visitor(
fn deserialize_struct_as_struct_in_place_visitor(
params: &Parameters,
fields: &[Field],
cattrs: &attr::Container,
) -> (Fragment, Fragment, Fragment) {
) -> (Fragment, Option<Fragment>, Fragment) {
let field_names_idents: Vec<_> = fields
.iter()
.enumerate()
@ -2214,7 +2230,27 @@ fn deserialize_struct_in_place_visitor(
let visit_map = deserialize_map_in_place(params, fields, cattrs);
(field_visitor, fields_stmt, visit_map)
(field_visitor, Some(fields_stmt), visit_map)
}
#[cfg(feature = "deserialize_in_place")]
fn deserialize_struct_as_map_in_place_visitor(
params: &Parameters,
fields: &[Field],
cattrs: &attr::Container,
) -> (Fragment, Option<Fragment>, Fragment) {
let field_names_idents: Vec<_> = fields
.iter()
.enumerate()
.filter(|&(_, field)| !field.attrs.skip_deserializing())
.map(|(i, field)| (field.attrs.name().deserialize_name(), field_i(i)))
.collect();
let field_visitor = deserialize_generated_identifier(&field_names_idents, cattrs, false, true);
let visit_map = deserialize_map_in_place(params, fields, cattrs);
(field_visitor, None, visit_map)
}
#[cfg(feature = "deserialize_in_place")]
@ -2240,6 +2276,15 @@ fn deserialize_map_in_place(
}
});
// Collect contents for flatten fields into a buffer
let let_collect = if cattrs.has_flatten() {
Some(quote! {
let mut __collect = Vec::<Option<(String, _serde::private::de::Content)>>::new();
})
} else {
None
};
// Match arms to extract a value for a field.
let value_arms_from = fields_names
.iter()
@ -2274,7 +2319,13 @@ fn deserialize_map_in_place(
});
// Visit ignored values to consume them
let ignored_arm = if cattrs.deny_unknown_fields() {
let ignored_arm = if cattrs.has_flatten() {
Some(quote! {
__Field::__other(__name) => {
__collect.push(Some((__name, try!(_serde::de::MapAccess::next_value(&mut __map)))));
}
})
} else if cattrs.deny_unknown_fields() {
None
} else {
Some(quote! {
@ -2282,7 +2333,7 @@ fn deserialize_map_in_place(
})
};
let all_skipped = fields.iter().all(|field| field.attrs.skip_deserializing());
let all_skipped = !cattrs.has_flatten() && fields.iter().all(|field| field.attrs.skip_deserializing());
let match_keys = if cattrs.deny_unknown_fields() && all_skipped {
quote! {
@ -2346,15 +2397,45 @@ fn deserialize_map_in_place(
}
};
let extract_collected = fields_names
.iter()
.filter(|&&(field, _)| field.attrs.flatten())
.map(|&(field, ref name)| {
let field_ty = field.ty;
quote! {
let #name: #field_ty = try!(_serde::de::Deserialize::deserialize(
_serde::private::de::FlatMapDeserializer(
&mut __collect,
_serde::export::PhantomData)));
}
});
let collected_deny_unknown_fields = if cattrs.has_flatten() && cattrs.deny_unknown_fields() {
Some(quote! {
if let Some(Some((__key, _))) = __collect.into_iter().filter(|x| x.is_some()).next() {
return _serde::export::Err(
_serde::de::Error::custom(format_args!("unknown field `{}`", &__key)));
}
})
} else {
None
};
quote_block! {
#(#let_flags)*
#let_collect
#match_keys
#let_default
#(#check_flags)*
#(#extract_collected)*
#collected_deny_unknown_fields
_serde::export::Ok(())
}
}

View File

@ -1493,3 +1493,41 @@ fn test_flatten_struct_tag_content_enum_newtype() {
],
);
}
#[test]
fn test_unknown_field_in_flatten() {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(repr = "map", deny_unknown_fields)]
struct Outer {
dummy: String,
#[serde(flatten)]
inner: Inner,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Inner {
foo: HashMap<String, u32>,
}
assert_de_tokens_error::<Outer>(
&[
Token::Struct {
name: "Outer",
len: 1,
},
Token::Str("dummy"),
Token::Str("23"),
Token::Str("foo"),
Token::Map { len: None },
Token::Str("a"),
Token::U32(1),
Token::Str("b"),
Token::U32(2),
Token::MapEnd,
Token::Str("bar"),
Token::U32(23),
Token::StructEnd,
],
"unknown field `bar`",
);
}