Merge pull request #771 from elliottslaughter:internally_content_tagged

This commit is contained in:
David Tolnay 2017-02-19 16:08:18 -08:00
commit 535ab1e04b
No known key found for this signature in database
GPG Key ID: F9BA143B95FF6D82
4 changed files with 247 additions and 18 deletions

View File

@ -110,7 +110,14 @@ pub enum EnumTag {
/// ```json
/// {"type": "variant1", "key1": "value1", "key2": "value2"}
/// ```
Internal(String),
Internal { tag: String },
/// `#[serde(tag = "t", content = "c")]`
///
/// ```json
/// {"t": "variant1", "c": {"key1": "value1", "key2": "value2"}}
/// ```
Adjacent { tag: String, content: String },
/// `#[serde(untagged)]`
///
@ -130,6 +137,7 @@ impl Item {
let mut de_bound = Attr::none(cx, "bound");
let mut untagged = BoolAttr::none(cx, "untagged");
let mut internal_tag = Attr::none(cx, "tag");
let mut content = Attr::none(cx, "content");
for meta_items in item.attrs.iter().filter_map(get_serde_meta_items) {
for meta_item in meta_items {
@ -198,6 +206,20 @@ impl Item {
}
}
// Parse `#[serde(content = "c")]`
MetaItem(NameValue(ref name, ref lit)) if name == "content" => {
if let Ok(s) = get_string_from_lit(cx, name.as_ref(), name.as_ref(), lit) {
match item.body {
syn::Body::Enum(_) => {
content.set(s);
}
syn::Body::Struct(_) => {
cx.error("#[serde(content = \"...\")] can only be used on enums")
}
}
}
}
MetaItem(ref meta_item) => {
cx.error(format!("unknown serde container attribute `{}`",
meta_item.name()));
@ -210,10 +232,10 @@ impl Item {
}
}
let tag = match (untagged.get(), internal_tag.get()) {
(false, None) => EnumTag::External,
(true, None) => EnumTag::None,
(false, Some(tag)) => {
let tag = match (untagged.get(), internal_tag.get(), content.get()) {
(false, None, None) => EnumTag::External,
(true, None, None) => EnumTag::None,
(false, Some(tag), None) => {
// Check that there are no tuple variants.
if let syn::Body::Enum(ref variants) = item.body {
for variant in variants {
@ -230,12 +252,27 @@ impl Item {
}
}
}
EnumTag::Internal(tag)
EnumTag::Internal { tag: tag }
}
(true, Some(_)) => {
(true, Some(_), None) => {
cx.error("enum cannot be both untagged and internally tagged");
EnumTag::External // doesn't matter, will error
}
(false, None, Some(_)) => {
cx.error("#[serde(tag = \"...\", content = \"...\")] must be used together");
EnumTag::External
}
(true, None, Some(_)) => {
cx.error("untagged enum cannot have #[serde(content = \"...\")]");
EnumTag::External
}
(false, Some(tag), Some(content)) => {
EnumTag::Adjacent { tag: tag, content: content }
}
(true, Some(_), Some(_)) => {
cx.error("untagged enum cannot have #[serde(tag = \"...\", content = \"...\")]");
EnumTag::External
}
};
Item {

View File

@ -494,7 +494,7 @@ fn deserialize_item_enum(type_ident: &syn::Ident,
attr::EnumTag::External => {
deserialize_externally_tagged_enum(type_ident, impl_generics, ty, variants, item_attrs)
}
attr::EnumTag::Internal(ref tag) => {
attr::EnumTag::Internal { ref tag } => {
deserialize_internally_tagged_enum(type_ident,
impl_generics,
ty,
@ -502,6 +502,7 @@ fn deserialize_item_enum(type_ident: &syn::Ident,
item_attrs,
tag)
}
attr::EnumTag::Adjacent { .. } => unimplemented!(),
attr::EnumTag::None => {
deserialize_untagged_enum(type_ident, impl_generics, ty, variants, item_attrs)
}

View File

@ -266,7 +266,7 @@ fn serialize_variant(type_ident: &syn::Ident,
variant_index,
item_attrs)
}
attr::EnumTag::Internal(ref tag) => {
attr::EnumTag::Internal { ref tag } => {
serialize_internally_tagged_variant(type_ident.as_ref(),
variant_ident.as_ref(),
generics,
@ -275,6 +275,9 @@ fn serialize_variant(type_ident: &syn::Ident,
item_attrs,
tag)
}
attr::EnumTag::Adjacent { ref tag, ref content } => {
serialize_adjacently_tagged_variant(generics, ty, variant, item_attrs, tag, content)
}
attr::EnumTag::None => serialize_untagged_variant(generics, ty, variant, item_attrs),
};
@ -343,7 +346,7 @@ fn serialize_externally_tagged_variant(generics: &syn::Generics,
generics,
ty,
&variant.fields,
item_attrs);
&type_name);
quote! {
{ #block }
@ -399,7 +402,7 @@ fn serialize_internally_tagged_variant(type_ident: &str,
generics,
ty,
&variant.fields,
item_attrs);
&type_name);
quote! {
{ #block }
@ -409,6 +412,110 @@ fn serialize_internally_tagged_variant(type_ident: &str,
}
}
fn serialize_adjacently_tagged_variant(generics: &syn::Generics,
ty: syn::Ty,
variant: &Variant,
item_attrs: &attr::Item,
tag: &str,
content: &str)
-> Tokens {
let type_name = item_attrs.name().serialize_name();
let variant_name = variant.attrs.name().serialize_name();
let inner = match variant.style {
Style::Unit => {
return quote!({
let mut __struct = try!(_serde::Serializer::serialize_struct(
_serializer, #type_name, 1));
try!(_serde::ser::SerializeStruct::serialize_field(
&mut __struct, #tag, #variant_name));
_serde::ser::SerializeStruct::end(__struct)
});
}
Style::Newtype => {
let field = &variant.fields[0];
let mut field_expr = quote!(__simple_value);
if let Some(path) = field.attrs.serialize_with() {
field_expr = wrap_serialize_with(&ty, generics, field.ty, path, field_expr);
}
quote! {
_serde::Serialize::serialize(#field_expr, _serializer)
}
}
Style::Tuple => {
serialize_tuple_variant(TupleVariant::Untagged,
generics,
ty.clone(),
&variant.fields)
}
Style::Struct => {
serialize_struct_variant(StructVariant::Untagged,
generics,
ty.clone(),
&variant.fields,
&variant_name)
}
};
let fields_ty = variant.fields.iter().map(|f| &f.ty);
let ref fields_ident: Vec<_> = match variant.style {
Style::Unit => unreachable!(),
Style::Newtype => vec![Ident::new("__simple_value")],
Style::Tuple => {
(0..variant.fields.len())
.map(|i| Ident::new(format!("__field{}", i)))
.collect()
}
Style::Struct => {
variant.fields
.iter()
.map(|f| f.ident.clone().expect("struct variant has unnamed fields"))
.collect()
}
};
let where_clause = &generics.where_clause;
let wrapper_generics = aster::from_generics(generics.clone())
.add_lifetime_bound("'__a")
.lifetime_name("'__a")
.build();
let wrapper_ty = aster::path()
.segment("__AdjacentlyTagged")
.with_generics(wrapper_generics.clone())
.build()
.build();
quote!({
struct __AdjacentlyTagged #wrapper_generics #where_clause {
data: (#(&'__a #fields_ty,)*),
phantom: _serde::export::PhantomData<#ty>,
}
impl #wrapper_generics _serde::Serialize for #wrapper_ty #where_clause {
fn serialize<__S>(&self, _serializer: __S) -> _serde::export::Result<__S::Ok, __S::Error>
where __S: _serde::Serializer
{
let (#(#fields_ident,)*) = self.data;
#inner
}
}
let mut __struct = try!(_serde::Serializer::serialize_struct(
_serializer, #type_name, 2));
try!(_serde::ser::SerializeStruct::serialize_field(
&mut __struct, #tag, #variant_name));
try!(_serde::ser::SerializeStruct::serialize_field(
&mut __struct, #content, &__AdjacentlyTagged {
data: (#(#fields_ident,)*),
phantom: _serde::export::PhantomData::<#ty>,
}));
_serde::ser::SerializeStruct::end(__struct)
})
}
fn serialize_untagged_variant(generics: &syn::Generics,
ty: syn::Ty,
variant: &Variant,
@ -440,11 +547,12 @@ fn serialize_untagged_variant(generics: &syn::Generics,
}
}
Style::Struct => {
let type_name = item_attrs.name().serialize_name();
let block = serialize_struct_variant(StructVariant::Untagged,
generics,
ty,
&variant.fields,
item_attrs);
&type_name);
quote! {
{ #block }
@ -518,7 +626,7 @@ fn serialize_struct_variant<'a>(context: StructVariant<'a>,
generics: &syn::Generics,
ty: syn::Ty,
fields: &[Field],
item_attrs: &attr::Item)
name: &str)
-> Tokens {
let method = match context {
StructVariant::ExternallyTagged { .. } => {
@ -530,8 +638,6 @@ fn serialize_struct_variant<'a>(context: StructVariant<'a>,
let serialize_fields = serialize_struct_visitor(ty.clone(), fields, generics, true, method);
let item_name = item_attrs.name().serialize_name();
let mut serialized_fields = fields.iter()
.filter(|&field| !field.attrs.skip_serializing())
.peekable();
@ -553,7 +659,7 @@ fn serialize_struct_variant<'a>(context: StructVariant<'a>,
quote! {
let #let_mut __serde_state = try!(_serde::Serializer::serialize_struct_variant(
_serializer,
#item_name,
#name,
#variant_index,
#variant_name,
#len,
@ -566,7 +672,7 @@ fn serialize_struct_variant<'a>(context: StructVariant<'a>,
quote! {
let mut __serde_state = try!(_serde::Serializer::serialize_struct(
_serializer,
#item_name,
#name,
#len + 1,
));
try!(_serde::ser::SerializeStruct::serialize_field(
@ -582,7 +688,7 @@ fn serialize_struct_variant<'a>(context: StructVariant<'a>,
quote! {
let #let_mut __serde_state = try!(_serde::Serializer::serialize_struct(
_serializer,
#item_name,
#name,
#len,
));
#(#serialize_fields)*

View File

@ -881,3 +881,88 @@ fn test_internally_tagged_enum() {
Error::Message("unknown variant `Z`, expected one of `A`, `B`, `C`, `D`, `E`, `F`".to_owned()),
);
}
#[test]
fn test_adjacently_tagged_enum() {
#[derive(Debug, PartialEq, Serialize)]
#[serde(tag = "t", content = "c")]
enum AdjacentlyTagged {
Unit,
Newtype(u8),
Tuple(u8, u8),
Struct { f: u8 },
}
assert_ser_tokens(
&AdjacentlyTagged::Unit,
&[
Token::StructStart("AdjacentlyTagged", 1),
Token::StructSep,
Token::Str("t"),
Token::Str("Unit"),
Token::StructEnd,
]
);
assert_ser_tokens(
&AdjacentlyTagged::Newtype(1),
&[
Token::StructStart("AdjacentlyTagged", 2),
Token::StructSep,
Token::Str("t"),
Token::Str("Newtype"),
Token::StructSep,
Token::Str("c"),
Token::U8(1),
Token::StructEnd,
]
);
assert_ser_tokens(
&AdjacentlyTagged::Tuple(1, 1),
&[
Token::StructStart("AdjacentlyTagged", 2),
Token::StructSep,
Token::Str("t"),
Token::Str("Tuple"),
Token::StructSep,
Token::Str("c"),
Token::TupleStart(2),
Token::TupleSep,
Token::U8(1),
Token::TupleSep,
Token::U8(1),
Token::TupleEnd,
Token::StructEnd,
]
);
assert_ser_tokens(
&AdjacentlyTagged::Struct { f: 1 },
&[
Token::StructStart("AdjacentlyTagged", 2),
Token::StructSep,
Token::Str("t"),
Token::Str("Struct"),
Token::StructSep,
Token::Str("c"),
Token::StructStart("Struct", 1),
Token::StructSep,
Token::Str("f"),
Token::U8(1),
Token::StructEnd,
Token::StructEnd,
]
);
}