diff --git a/serde_codegen_internals/src/attr.rs b/serde_codegen_internals/src/attr.rs index 2ed7bf42..eae9206d 100644 --- a/serde_codegen_internals/src/attr.rs +++ b/serde_codegen_internals/src/attr.rs @@ -112,6 +112,13 @@ pub enum EnumTag { /// ``` Internal(String), + /// `#[serde(tag = "type", content = "name")]` + /// + /// ```json + /// {"type": "variant1", "name": {"key1": "value1", "key2": "value2"}} + /// ``` + Adjacent(String, String), + /// `#[serde(untagged)]` /// /// ```json @@ -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 internal_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 = "name")]` + 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(_) => { + internal_content.set(s); + } + syn::Body::Struct(_) => { + cx.error("#[serde(tag = \"...\")] 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(), internal_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 { @@ -232,10 +254,33 @@ impl Item { } EnumTag::Internal(tag) } - (true, Some(_)) => { + (false, Some(tag), Some(content)) => { + // Check that there are no struct variants. + if let syn::Body::Enum(ref variants) = item.body { + for variant in variants { + match variant.data { + syn::VariantData::Tuple(_) | + syn::VariantData::Unit => {} + syn::VariantData::Struct(ref fields) => { + if fields.len() != 1 { + cx.error("#[serde(tag = \"...\", content = \"...\")] cannot \ + be used with struct variants"); + break; + } + } + } + } + } + EnumTag::Adjacent(tag, content) + } + (true, Some(_), _) => { cx.error("enum cannot be both untagged and internally tagged"); EnumTag::External // doesn't matter, will error } + (_, None, Some(_)) => { + cx.error("#[serde(content = \"...\")] cannot be used without #[serde(tag = \"...\")]"); + EnumTag::External // doesn't matter, will error + } }; Item { diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index 9e203ec9..410103eb 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -502,6 +502,9 @@ fn deserialize_item_enum(type_ident: &syn::Ident, item_attrs, tag) } + attr::EnumTag::Adjacent(ref tag, ref content) => { + panic!("FIXME: 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..3337ef25 100644 --- a/serde_derive/src/ser.rs +++ b/serde_derive/src/ser.rs @@ -230,30 +230,38 @@ fn serialize_variant(type_ident: &syn::Ident, } } else { // variant wasn't skipped + let field_values; let case = match variant.style { Style::Unit => { + field_values = vec![]; quote! { #type_ident::#variant_ident } } Style::Newtype => { + let field_name = Ident::new("__simple_value"); + field_values = vec![field_name.clone()]; quote! { - #type_ident::#variant_ident(ref __simple_value) + #type_ident::#variant_ident(ref #field_name) } } Style::Tuple => { - let field_names = (0..variant.fields.len()) - .map(|i| Ident::new(format!("__field{}", i))); + let field_names: Vec<_> = (0..variant.fields.len()) + .map(|i| Ident::new(format!("__field{}", i))) + .collect(); + field_values = field_names.clone(); quote! { #type_ident::#variant_ident(#(ref #field_names),*) } } Style::Struct => { - let fields = variant.fields + let field_names: Vec<_> = variant.fields .iter() - .map(|f| f.ident.clone().expect("struct variant has unnamed fields")); + .map(|f| f.ident.clone().expect("struct variant has unnamed fields")) + .collect(); + field_values = field_names.clone(); quote! { - #type_ident::#variant_ident { #(ref #fields),* } + #type_ident::#variant_ident { #(ref #field_names),* } } } }; @@ -275,6 +283,15 @@ 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, + &field_values, + tag, + content) + } attr::EnumTag::None => serialize_untagged_variant(generics, ty, variant, item_attrs), }; @@ -409,6 +426,104 @@ 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, + field_values: &Vec, + tag: &str, + content: &str) + -> Tokens { + let type_name = item_attrs.name().serialize_name(); + let variant_name = variant.attrs.name().serialize_name(); + + match variant.style { + Style::Unit => { + 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 | + Style::Tuple => { + let define_inner = match variant.style { + Style::Unit => unreachable!("checked above"), + Style::Newtype => { + // FIXME: This doesn't handle nested newtype. + let value = field_values[0].clone(); + quote!({ + __inner = #value; + }) + } + Style::Tuple => { + 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("Inner") + .with_generics(wrapper_generics.clone()) + .build() + .build(); + + let field_count = variant.fields.len(); + let field_offset = (0..field_count).map( + |i| syn::Lit::Int(i as u64, syn::IntTy::Unsuffixed)); + let field_tys = variant.fields.iter().map(|f| f.ty.clone()); + + quote!({ + // Define a tuple struct for the interior along with + // the necessary serialization logic. + struct Inner #wrapper_generics #where_clause { + data: (#(&'__a #field_tys),*), + phantom: _serde::export::PhantomData<#ty>, + }; + impl #wrapper_generics _serde::ser::Serialize for #wrapper_ty #where_clause { + fn serialize(&self, __inner_serializer: S) -> _serde::export::Result + where S: _serde::ser::Serializer + { + let mut __tuple = try!(_serde::ser::Serializer::serialize_tuple( + __inner_serializer, #field_count)); + #(try!(_serde::ser::SerializeTuple::serialize_element( + &mut __tuple, &(self.data.#field_offset)));)* + _serde::ser::SerializeTuple::end(__tuple) + } + } + + __inner = Inner { + data: (#(#field_values),*), + phantom: _serde::export::PhantomData::<#ty>, + }; + }) + } + Style::Struct => unreachable!("checked in serde_codegen_internals"), + }; + + quote!({ + let __inner; + { #define_inner } + + // Serialize the exterior struct with tag name. + let mut __struct = try!(_serde::Serializer::serialize_struct( + _serializer, #type_name, 2)); + try!(_serde::ser::SerializeStruct::serialize_field( + &mut __struct, #tag, #variant_name)); + // Serialize the interior tuple. + try!(_serde::ser::SerializeStruct::serialize_field( + &mut __struct, #content, &__inner)); + _serde::ser::SerializeStruct::end(__struct) + }) + } + Style::Struct => unreachable!("checked in serde_codegen_internals"), + } +} + fn serialize_untagged_variant(generics: &syn::Generics, ty: syn::Ty, variant: &Variant, diff --git a/test_suite/tests/test_macros.rs b/test_suite/tests/test_macros.rs index 21185412..af212064 100644 --- a/test_suite/tests/test_macros.rs +++ b/test_suite/tests/test_macros.rs @@ -881,3 +881,146 @@ 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, Deserialize)] + struct Newtype(BTreeMap); + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Struct { + f: u8, + } + + #[derive(Debug, PartialEq, Serialize)] // , Deserialize + #[serde(tag = "type", content = "content")] + enum AdjacentlyTagged { + A(u8), + B(u8, u16, u32), + C, + D(BTreeMap), + E(Newtype), + F(Struct), + } + + assert_ser_tokens( + &AdjacentlyTagged::A(1), + &[ + Token::StructStart("AdjacentlyTagged", 2), + + Token::StructSep, + Token::Str("type"), + Token::Str("A"), + + Token::StructSep, + Token::Str("content"), + Token::U8(1), + + Token::StructEnd, + ] + ); + + assert_ser_tokens( + &AdjacentlyTagged::B(1, 300, 70000), + &[ + Token::StructStart("AdjacentlyTagged", 2), + + Token::StructSep, + Token::Str("type"), + Token::Str("B"), + + Token::StructSep, + Token::Str("content"), + + Token::TupleStart(3), + + Token::TupleSep, + Token::U8(1), + + Token::TupleSep, + Token::U16(300), + + Token::TupleSep, + Token::U32(70000), + + Token::TupleEnd, + + Token::StructEnd, + ] + ); + + assert_ser_tokens( + &AdjacentlyTagged::C, + &[ + Token::StructStart("AdjacentlyTagged", 1), + + Token::StructSep, + Token::Str("type"), + Token::Str("C"), + + Token::StructEnd, + ] + ); + + assert_ser_tokens( + &AdjacentlyTagged::D(BTreeMap::new()), + &[ + Token::StructStart("AdjacentlyTagged", 2), + + Token::StructSep, + Token::Str("type"), + Token::Str("D"), + + Token::StructSep, + Token::Str("content"), + Token::MapStart(Some(0)), + Token::MapEnd, + + Token::StructEnd, + ] + ); + + // FIXME: Nested newtype is broken + + // assert_ser_tokens( + // &AdjacentlyTagged::E(Newtype(BTreeMap::new())), + // &[ + // Token::StructStart("AdjacentlyTagged", 2), + + // Token::StructSep, + // Token::Str("type"), + // Token::Str("E"), + + // Token::StructSep, + // Token::Str("content"), + // Token::MapStart(Some(0)), + // Token::MapEnd, + + // Token::StructEnd, + // ] + // ); + + assert_ser_tokens( + &AdjacentlyTagged::F(Struct { f: 6 }), + &[ + Token::StructStart("AdjacentlyTagged", 2), + + Token::StructSep, + Token::Str("type"), + Token::Str("F"), + + Token::StructSep, + Token::Str("content"), + + Token::StructStart("Struct", 1), + + Token::StructSep, + Token::Str("f"), + Token::U8(6), + + Token::StructEnd, + + Token::StructEnd, + ] + ); +}