diff --git a/serde_codegen_internals/src/attr.rs b/serde_codegen_internals/src/attr.rs index 2ed7bf42..de5863fe 100644 --- a/serde_codegen_internals/src/attr.rs +++ b/serde_codegen_internals/src/attr.rs @@ -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 { diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index 5b9cb8fc..d3f618f9 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -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) } diff --git a/serde_derive/src/ser.rs b/serde_derive/src/ser.rs index 62786f33..3184c509 100644 --- a/serde_derive/src/ser.rs +++ b/serde_derive/src/ser.rs @@ -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)* diff --git a/test_suite/tests/test_macros.rs b/test_suite/tests/test_macros.rs index 21185412..4b84daeb 100644 --- a/test_suite/tests/test_macros.rs +++ b/test_suite/tests/test_macros.rs @@ -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, + ] + ); +}