Initial work to support serialization of adjacently tagged enums.
This commit is contained in:
parent
c590df13b9
commit
4538143d00
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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<syn::Ident>,
|
||||
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<S>(&self, __inner_serializer: S) -> _serde::export::Result<S::Ok, S::Error>
|
||||
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,
|
||||
|
@ -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<String, String>);
|
||||
|
||||
#[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<String, String>),
|
||||
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,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user