diff --git a/serde_derive/src/bound.rs b/serde_derive/src/bound.rs index 77e7824a4..0160fa85c 100644 --- a/serde_derive/src/bound.rs +++ b/serde_derive/src/bound.rs @@ -10,7 +10,7 @@ use std::collections::HashSet; use syn::{self, visit}; -use internals::ast::Container; +use internals::ast::{Body, Container}; use internals::attr; macro_rules! path { @@ -88,7 +88,7 @@ pub fn with_bound( bound: &syn::Path, ) -> syn::Generics where - F: Fn(&attr::Field) -> bool, + F: Fn(&attr::Field, Option<&attr::Variant>) -> bool, { struct FindTyParams { // Set of all generic type parameters on the current struct (A, B, C in @@ -124,17 +124,27 @@ where .map(|ty_param| ty_param.ident.clone()) .collect(); - let relevant_tys = cont.body - .all_fields() - .filter(|&field| filter(&field.attrs)) - .map(|field| &field.ty); - let mut visitor = FindTyParams { all_ty_params: all_ty_params, relevant_ty_params: HashSet::new(), }; - for ty in relevant_tys { - visit::walk_ty(&mut visitor, ty); + match cont.body { + Body::Enum(ref variants) => { + for variant in variants.iter() { + let relevant_fields = variant + .fields + .iter() + .filter(|field| filter(&field.attrs, Some(&variant.attrs))); + for field in relevant_fields { + visit::walk_ty(&mut visitor, field.ty); + } + } + } + Body::Struct(_, ref fields) => { + for field in fields.iter().filter(|field| filter(&field.attrs, None)) { + visit::walk_ty(&mut visitor, field.ty); + } + } } let new_predicates = generics diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index c9faee313..aedbf3323 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -157,14 +157,17 @@ fn build_generics(cont: &Container) -> syn::Generics { // deserialized by us so we do not generate a bound. Fields with a `bound` // attribute specify their own bound so we do not generate one. All other fields // may need a `T: Deserialize` bound where T is the type of the field. -fn needs_deserialize_bound(attrs: &attr::Field) -> bool { - !attrs.skip_deserializing() && attrs.deserialize_with().is_none() && attrs.de_bound().is_none() +fn needs_deserialize_bound(field: &attr::Field, variant: Option<&attr::Variant>) -> bool { + !field.skip_deserializing() && + field.deserialize_with().is_none() && + field.de_bound().is_none() && + variant.map_or(true, |variant| variant.deserialize_with().is_none()) } // Fields with a `default` attribute (not `default=...`), and fields with a // `skip_deserializing` attribute that do not also have `default=...`. -fn requires_default(attrs: &attr::Field) -> bool { - attrs.default() == &attr::Default::Default +fn requires_default(field: &attr::Field, _variant: Option<&attr::Variant>) -> bool { + field.default() == &attr::Default::Default } // The union of lifetimes borrowed by each field of the container. @@ -372,7 +375,7 @@ fn deserialize_seq( quote!(try!(_serde::de::SeqAccess::next_element::<#field_ty>(&mut __seq))) } Some(path) => { - let (wrapper, wrapper_ty) = wrap_deserialize_with( + let (wrapper, wrapper_ty) = wrap_deserialize_field_with( params, field.ty, path); quote!({ #wrapper @@ -428,7 +431,7 @@ fn deserialize_newtype_struct(type_path: &Tokens, params: &Parameters, field: &F } } Some(path) => { - let (wrapper, wrapper_ty) = wrap_deserialize_with(params, field.ty, path); + let (wrapper, wrapper_ty) = wrap_deserialize_field_with(params, field.ty, path); quote!({ #wrapper try!(<#wrapper_ty as _serde::Deserialize>::deserialize(__e)).value @@ -1084,6 +1087,16 @@ fn deserialize_externally_tagged_variant( variant: &Variant, cattrs: &attr::Container, ) -> Fragment { + if let Some(path) = variant.attrs.deserialize_with() { + let (wrapper, wrapper_ty, unwrap_fn) = + wrap_deserialize_variant_with(params, &variant, path); + return quote_block! { + #wrapper + _serde::export::Result::map( + _serde::de::VariantAccess::newtype_variant::<#wrapper_ty>(__variant), #unwrap_fn) + }; + } + let variant_ident = &variant.ident; match variant.style { @@ -1112,6 +1125,10 @@ fn deserialize_internally_tagged_variant( cattrs: &attr::Container, deserializer: Tokens, ) -> Fragment { + if variant.attrs.deserialize_with().is_some() { + return deserialize_untagged_variant(params, variant, cattrs, deserializer); + } + let variant_ident = &variant.ident; match variant.style { @@ -1137,6 +1154,16 @@ fn deserialize_untagged_variant( cattrs: &attr::Container, deserializer: Tokens, ) -> Fragment { + if let Some(path) = variant.attrs.deserialize_with() { + let (wrapper, wrapper_ty, unwrap_fn) = + wrap_deserialize_variant_with(params, &variant, path); + return quote_block! { + #wrapper + _serde::export::Result::map( + <#wrapper_ty as _serde::Deserialize>::deserialize(#deserializer), #unwrap_fn) + }; + } + let variant_ident = &variant.ident; match variant.style { @@ -1198,7 +1225,7 @@ fn deserialize_externally_tagged_newtype_variant( } } Some(path) => { - let (wrapper, wrapper_ty) = wrap_deserialize_with(params, field.ty, path); + let (wrapper, wrapper_ty) = wrap_deserialize_field_with(params, field.ty, path); quote_block! { #wrapper _serde::export::Result::map( @@ -1226,7 +1253,7 @@ fn deserialize_untagged_newtype_variant( } } Some(path) => { - let (wrapper, wrapper_ty) = wrap_deserialize_with(params, field.ty, path); + let (wrapper, wrapper_ty) = wrap_deserialize_field_with(params, field.ty, path); quote_block! { #wrapper _serde::export::Result::map( @@ -1529,7 +1556,7 @@ fn deserialize_map( } } Some(path) => { - let (wrapper, wrapper_ty) = wrap_deserialize_with( + let (wrapper, wrapper_ty) = wrap_deserialize_field_with( params, field.ty, path); quote!({ #wrapper @@ -1662,7 +1689,7 @@ fn field_i(i: usize) -> Ident { /// in a trait to prevent it from accessing the internal `Deserialize` state. fn wrap_deserialize_with( params: &Parameters, - field_ty: &syn::Ty, + value_ty: Tokens, deserialize_with: &syn::Path, ) -> (Tokens, Tokens) { let this = ¶ms.this; @@ -1670,7 +1697,7 @@ fn wrap_deserialize_with( let wrapper = quote! { struct __DeserializeWith #de_impl_generics #where_clause { - value: #field_ty, + value: #value_ty, phantom: _serde::export::PhantomData<#this #ty_generics>, lifetime: _serde::export::PhantomData<&'de ()>, } @@ -1693,6 +1720,68 @@ fn wrap_deserialize_with( (wrapper, wrapper_ty) } +fn wrap_deserialize_field_with( + params: &Parameters, + field_ty: &syn::Ty, + deserialize_with: &syn::Path, +) -> (Tokens, Tokens) { + wrap_deserialize_with(params, quote!(#field_ty), deserialize_with) +} + +fn wrap_deserialize_variant_with( + params: &Parameters, + variant: &Variant, + deserialize_with: &syn::Path, +) -> (Tokens, Tokens, Tokens) { + let this = ¶ms.this; + let variant_ident = &variant.ident; + + let field_tys = variant.fields.iter().map(|field| field.ty); + let (wrapper, wrapper_ty) = + wrap_deserialize_with(params, quote!((#(#field_tys),*)), deserialize_with); + + let field_access = (0..variant.fields.len()).map(|n| Ident::new(format!("{}", n))); + let unwrap_fn = match variant.style { + Style::Struct => { + let field_idents = variant.fields.iter().map(|field| field.ident.as_ref().unwrap()); + quote! { + { + |__wrap| { + #this::#variant_ident { #(#field_idents: __wrap.value.#field_access),* } + } + } + } + } + Style::Tuple => { + quote! { + { + |__wrap| { + #this::#variant_ident(#(__wrap.value.#field_access),*) + } + } + } + } + Style::Newtype => { + quote! { + { + |__wrap| { + #this::#variant_ident(__wrap.value) + } + } + } + } + Style::Unit => { + quote! { + { + |__wrap| { #this::#variant_ident } + } + } + } + }; + + (wrapper, wrapper_ty, unwrap_fn) +} + fn expr_is_missing(field: &Field, cattrs: &attr::Container) -> Fragment { match *field.attrs.default() { attr::Default::Default => { diff --git a/serde_derive/src/ser.rs b/serde_derive/src/ser.rs index 9c3310be5..aa4891931 100644 --- a/serde_derive/src/ser.rs +++ b/serde_derive/src/ser.rs @@ -143,12 +143,16 @@ fn build_generics(cont: &Container) -> syn::Generics { } } -// Fields with a `skip_serializing` or `serialize_with` attribute are not -// serialized by us so we do not generate a bound. Fields with a `bound` -// attribute specify their own bound so we do not generate one. All other fields -// may need a `T: Serialize` bound where T is the type of the field. -fn needs_serialize_bound(attrs: &attr::Field) -> bool { - !attrs.skip_serializing() && attrs.serialize_with().is_none() && attrs.ser_bound().is_none() +// Fields with a `skip_serializing` or `serialize_with` attribute, or which +// belong to a variant with a `serialize_with` attribute, are not serialized by +// us so we do not generate a bound. Fields with a `bound` attribute specify +// their own bound so we do not generate one. All other fields may need a `T: +// Serialize` bound where T is the type of the field. +fn needs_serialize_bound(field: &attr::Field, variant: Option<&attr::Variant>) -> bool { + !field.skip_serializing() && + field.serialize_with().is_none() && + field.ser_bound().is_none() && + variant.map_or(true, |variant| variant.serialize_with().is_none()) } fn serialize_body(cont: &Container, params: &Parameters) -> Fragment { @@ -203,7 +207,7 @@ fn serialize_newtype_struct( let mut field_expr = get_field(params, field, 0); if let Some(path) = field.attrs.serialize_with() { - field_expr = wrap_serialize_with(params, field.ty, path, field_expr); + field_expr = wrap_serialize_field_with(params, field.ty, path, field_expr); } quote_expr! { @@ -396,6 +400,19 @@ fn serialize_externally_tagged_variant( let type_name = cattrs.name().serialize_name(); let variant_name = variant.attrs.name().serialize_name(); + if let Some(path) = variant.attrs.serialize_with() { + let ser = wrap_serialize_variant_with(params, path, &variant); + return quote_expr! { + _serde::Serializer::serialize_newtype_variant( + __serializer, + #type_name, + #variant_index, + #variant_name, + #ser, + ) + }; + } + match variant.style { Style::Unit => { quote_expr! { @@ -411,7 +428,7 @@ fn serialize_externally_tagged_variant( let field = &variant.fields[0]; let mut field_expr = quote!(__field0); if let Some(path) = field.attrs.serialize_with() { - field_expr = wrap_serialize_with(params, field.ty, path, field_expr); + field_expr = wrap_serialize_field_with(params, field.ty, path, field_expr); } quote_expr! { @@ -461,6 +478,20 @@ fn serialize_internally_tagged_variant( let enum_ident_str = params.type_name(); let variant_ident_str = variant.ident.as_ref(); + if let Some(path) = variant.attrs.serialize_with() { + let ser = wrap_serialize_variant_with(params, path, &variant); + return quote_expr! { + _serde::private::ser::serialize_tagged_newtype( + __serializer, + #enum_ident_str, + #variant_ident_str, + #tag, + #variant_name, + #ser, + ) + }; + } + match variant.style { Style::Unit => { quote_block! { @@ -475,7 +506,7 @@ fn serialize_internally_tagged_variant( let field = &variant.fields[0]; let mut field_expr = quote!(__field0); if let Some(path) = field.attrs.serialize_with() { - field_expr = wrap_serialize_with(params, field.ty, path, field_expr); + field_expr = wrap_serialize_field_with(params, field.ty, path, field_expr); } quote_expr! { @@ -516,44 +547,57 @@ fn serialize_adjacently_tagged_variant( let variant_name = variant.attrs.name().serialize_name(); let inner = Stmts( - match variant.style { - Style::Unit => { - return quote_block! { - 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) - }; + if let Some(path) = variant.attrs.serialize_with() { + let ser = wrap_serialize_variant_with(params, path, &variant); + quote_expr! { + _serde::Serialize::serialize(#ser, __serializer) } - Style::Newtype => { - let field = &variant.fields[0]; - let mut field_expr = quote!(__field0); - if let Some(path) = field.attrs.serialize_with() { - field_expr = wrap_serialize_with(params, field.ty, path, field_expr); + } else { + match variant.style { + Style::Unit => { + return quote_block! { + 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!(__field0); + if let Some(path) = field.attrs.serialize_with() { + field_expr = wrap_serialize_field_with(params, field.ty, path, field_expr); + } - quote_expr! { - _serde::Serialize::serialize(#field_expr, __serializer) + quote_expr! { + _serde::Serialize::serialize(#field_expr, __serializer) + } + } + Style::Tuple => { + serialize_tuple_variant(TupleVariant::Untagged, params, &variant.fields) + } + Style::Struct => { + serialize_struct_variant( + StructVariant::Untagged, + params, + &variant.fields, + &variant_name, + ) } - } - Style::Tuple => { - serialize_tuple_variant(TupleVariant::Untagged, params, &variant.fields) - } - Style::Struct => { - serialize_struct_variant( - StructVariant::Untagged, - params, - &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::Unit => { + if variant.attrs.serialize_with().is_some() { + vec![] + } else { + unreachable!() + } + } Style::Newtype => vec![Ident::new("__field0")], Style::Tuple => { (0..variant.fields.len()) @@ -577,7 +621,11 @@ fn serialize_adjacently_tagged_variant( let (_, ty_generics, where_clause) = params.generics.split_for_impl(); - let wrapper_generics = bound::with_lifetime_bound(¶ms.generics, "'__a"); + let wrapper_generics = if let Style::Unit = variant.style { + params.generics.clone() + } else { + bound::with_lifetime_bound(¶ms.generics, "'__a") + }; let (wrapper_impl_generics, wrapper_ty_generics, _) = wrapper_generics.split_for_impl(); quote_block! { @@ -613,6 +661,13 @@ fn serialize_untagged_variant( variant: &Variant, cattrs: &attr::Container, ) -> Fragment { + if let Some(path) = variant.attrs.serialize_with() { + let ser = wrap_serialize_variant_with(params, path, &variant); + return quote_expr! { + _serde::Serialize::serialize(#ser, __serializer) + }; + } + match variant.style { Style::Unit => { quote_expr! { @@ -623,7 +678,7 @@ fn serialize_untagged_variant( let field = &variant.fields[0]; let mut field_expr = quote!(__field0); if let Some(path) = field.attrs.serialize_with() { - field_expr = wrap_serialize_with(params, field.ty, path, field_expr); + field_expr = wrap_serialize_field_with(params, field.ty, path, field_expr); } quote_expr! { @@ -817,7 +872,7 @@ fn serialize_tuple_struct_visitor( .map(|path| quote!(#path(#field_expr))); if let Some(path) = field.attrs.serialize_with() { - field_expr = wrap_serialize_with(params, field.ty, path, field_expr); + field_expr = wrap_serialize_field_with(params, field.ty, path, field_expr); } let ser = quote! { @@ -860,7 +915,7 @@ fn serialize_struct_visitor( .map(|path| quote!(#path(#field_expr))); if let Some(path) = field.attrs.serialize_with() { - field_expr = wrap_serialize_with(params, field.ty, path, field_expr) + field_expr = wrap_serialize_field_with(params, field.ty, path, field_expr); } let ser = quote! { @@ -884,21 +939,56 @@ fn serialize_struct_visitor( .collect() } -fn wrap_serialize_with( +fn wrap_serialize_field_with( params: &Parameters, field_ty: &syn::Ty, serialize_with: &syn::Path, - value: Tokens, + field_expr: Tokens, +) -> Tokens { + wrap_serialize_with(params, + serialize_with, + &[field_ty], + &[quote!(#field_expr)]) +} + +fn wrap_serialize_variant_with( + params: &Parameters, + serialize_with: &syn::Path, + variant: &Variant, +) -> Tokens { + let field_tys: Vec<_> = variant.fields.iter().map(|field| field.ty).collect(); + let field_exprs: Vec<_> = variant.fields.iter() + .enumerate() + .map(|(i, field)| { + let id = field.ident.as_ref().map_or_else(|| Ident::new(format!("__field{}", i)), + |id| id.clone()); + quote!(#id) + }) + .collect(); + wrap_serialize_with(params, serialize_with, field_tys.as_slice(), field_exprs.as_slice()) +} + +fn wrap_serialize_with( + params: &Parameters, + serialize_with: &syn::Path, + field_tys: &[&syn::Ty], + field_exprs: &[Tokens], ) -> Tokens { let this = ¶ms.this; let (_, ty_generics, where_clause) = params.generics.split_for_impl(); - let wrapper_generics = bound::with_lifetime_bound(¶ms.generics, "'__a"); + let wrapper_generics = if field_exprs.len() == 0 { + params.generics.clone() + } else { + bound::with_lifetime_bound(¶ms.generics, "'__a") + }; let (wrapper_impl_generics, wrapper_ty_generics, _) = wrapper_generics.split_for_impl(); + let field_access = (0..field_exprs.len()).map(|n| Ident::new(format!("{}", n))); + quote!({ struct __SerializeWith #wrapper_impl_generics #where_clause { - value: &'__a #field_ty, + values: (#(&'__a #field_tys, )*), phantom: _serde::export::PhantomData<#this #ty_generics>, } @@ -906,12 +996,12 @@ fn wrap_serialize_with( fn serialize<__S>(&self, __s: __S) -> _serde::export::Result<__S::Ok, __S::Error> where __S: _serde::Serializer { - #serialize_with(self.value, __s) + #serialize_with(#(self.values.#field_access, )* __s) } } &__SerializeWith { - value: #value, + values: (#(#field_exprs, )*), phantom: _serde::export::PhantomData::<#this #ty_generics>, } }) diff --git a/serde_derive_internals/src/attr.rs b/serde_derive_internals/src/attr.rs index caf306cfa..2803da9d7 100644 --- a/serde_derive_internals/src/attr.rs +++ b/serde_derive_internals/src/attr.rs @@ -510,6 +510,8 @@ pub struct Variant { skip_deserializing: bool, skip_serializing: bool, other: bool, + serialize_with: Option, + deserialize_with: Option, } impl Variant { @@ -520,6 +522,8 @@ impl Variant { let mut skip_serializing = BoolAttr::none(cx, "skip_serializing"); let mut rename_all = Attr::none(cx, "rename_all"); let mut other = BoolAttr::none(cx, "other"); + let mut serialize_with = Attr::none(cx, "serialize_with"); + let mut deserialize_with = Attr::none(cx, "deserialize_with"); for meta_items in variant.attrs.iter().filter_map(get_serde_meta_items) { for meta_item in meta_items { @@ -569,6 +573,20 @@ impl Variant { other.set_true(); } + // Parse `#[serde(serialize_with = "...")]` + MetaItem(NameValue(ref name, ref lit)) if name == "serialize_with" => { + if let Ok(path) = parse_lit_into_path(cx, name.as_ref(), lit) { + serialize_with.set(path); + } + } + + // Parse `#[serde(deserialize_with = "...")]` + MetaItem(NameValue(ref name, ref lit)) if name == "deserialize_with" => { + if let Ok(path) = parse_lit_into_path(cx, name.as_ref(), lit) { + deserialize_with.set(path); + } + } + MetaItem(ref meta_item) => { cx.error(format!("unknown serde variant attribute `{}`", meta_item.name())); } @@ -595,6 +613,8 @@ impl Variant { skip_deserializing: skip_deserializing.get(), skip_serializing: skip_serializing.get(), other: other.get(), + serialize_with: serialize_with.get(), + deserialize_with: deserialize_with.get(), } } @@ -626,6 +646,14 @@ impl Variant { pub fn other(&self) -> bool { self.other } + + pub fn serialize_with(&self) -> Option<&syn::Path> { + self.serialize_with.as_ref() + } + + pub fn deserialize_with(&self) -> Option<&syn::Path> { + self.deserialize_with.as_ref() + } } /// Represents field attribute information diff --git a/serde_derive_internals/src/check.rs b/serde_derive_internals/src/check.rs index 84c958ea9..5d6a76ff8 100644 --- a/serde_derive_internals/src/check.rs +++ b/serde_derive_internals/src/check.rs @@ -15,6 +15,7 @@ use Ctxt; pub fn check(cx: &Ctxt, cont: &Container) { check_getter(cx, cont); check_identifier(cx, cont); + check_variant_skip_attrs(cx, cont); } /// Getters are only allowed inside structs (not enums) with the `remote` @@ -94,3 +95,58 @@ fn check_identifier(cx: &Ctxt, cont: &Container) { } } } + +/// Skip-(de)serializing attributes are not allowed on variants marked +/// (de)serialize_with. +fn check_variant_skip_attrs(cx: &Ctxt, cont: &Container) { + let variants = match cont.body { + Body::Enum(ref variants) => variants, + Body::Struct(_, _) => { + return; + } + }; + + for variant in variants.iter() { + if variant.attrs.serialize_with().is_some() { + if variant.attrs.skip_serializing() { + cx.error(format!("variant `{}` cannot have both #[serde(serialize_with)] and \ + #[serde(skip_serializing)]", variant.ident)); + } + + for (i, field) in variant.fields.iter().enumerate() { + let ident = field.ident.as_ref().map_or_else(|| format!("{}", i), + |ident| format!("`{}`", ident)); + + if field.attrs.skip_serializing() { + cx.error(format!("variant `{}` cannot have both #[serde(serialize_with)] and \ + a field {} marked with #[serde(skip_serializing)]", + variant.ident, ident)); + } + + if field.attrs.skip_serializing_if().is_some() { + cx.error(format!("variant `{}` cannot have both #[serde(serialize_with)] and \ + a field {} marked with #[serde(skip_serializing_if)]", + variant.ident, ident)); + } + } + } + + if variant.attrs.deserialize_with().is_some() { + if variant.attrs.skip_deserializing() { + cx.error(format!("variant `{}` cannot have both #[serde(deserialize_with)] and \ + #[serde(skip_deserializing)]", variant.ident)); + } + + for (i, field) in variant.fields.iter().enumerate() { + if field.attrs.skip_deserializing() { + let ident = field.ident.as_ref().map_or_else(|| format!("{}", i), + |ident| format!("`{}`", ident)); + + cx.error(format!("variant `{}` cannot have both #[serde(deserialize_with)] \ + and a field {} marked with #[serde(skip_deserializing)]", + variant.ident, ident)); + } + } + } + } +} diff --git a/test_suite/tests/compile-fail/with-variant/skip_de_newtype_field.rs b/test_suite/tests/compile-fail/with-variant/skip_de_newtype_field.rs new file mode 100644 index 000000000..391af1baa --- /dev/null +++ b/test_suite/tests/compile-fail/with-variant/skip_de_newtype_field.rs @@ -0,0 +1,25 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +//~^ HELP: variant `Newtype` cannot have both #[serde(deserialize_with)] and a field 0 marked with #[serde(skip_deserializing)] +enum Enum { + #[serde(deserialize_with = "deserialize_some_newtype_variant")] + Newtype(#[serde(skip_deserializing)] String), +} + +fn deserialize_some_newtype_variant<'de, D>(_: D) -> StdResult + where D: Deserializer<'de>, +{ + unimplemented!() +} + +fn main() { } diff --git a/test_suite/tests/compile-fail/with-variant/skip_de_struct_field.rs b/test_suite/tests/compile-fail/with-variant/skip_de_struct_field.rs new file mode 100644 index 000000000..7c563a8af --- /dev/null +++ b/test_suite/tests/compile-fail/with-variant/skip_de_struct_field.rs @@ -0,0 +1,29 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +//~^ HELP: variant `Struct` cannot have both #[serde(deserialize_with)] and a field `f1` marked with #[serde(skip_deserializing)] +enum Enum { + #[serde(deserialize_with = "deserialize_some_other_variant")] + Struct { + #[serde(skip_deserializing)] + f1: String, + f2: u8, + }, +} + +fn deserialize_some_other_variant<'de, D>(_: D) -> StdResult<(String, u8), D::Error> + where D: Deserializer<'de>, +{ + unimplemented!() +} + +fn main() { } diff --git a/test_suite/tests/compile-fail/with-variant/skip_de_tuple_field.rs b/test_suite/tests/compile-fail/with-variant/skip_de_tuple_field.rs new file mode 100644 index 000000000..44fd09581 --- /dev/null +++ b/test_suite/tests/compile-fail/with-variant/skip_de_tuple_field.rs @@ -0,0 +1,25 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +//~^ HELP: variant `Tuple` cannot have both #[serde(deserialize_with)] and a field 0 marked with #[serde(skip_deserializing)] +enum Enum { + #[serde(deserialize_with = "deserialize_some_other_variant")] + Tuple(#[serde(skip_deserializing)] String, u8), +} + +fn deserialize_some_other_variant<'de, D>(_: D) -> StdResult<(String, u8), D::Error> + where D: Deserializer<'de>, +{ + unimplemented!() +} + +fn main() { } diff --git a/test_suite/tests/compile-fail/with-variant/skip_de_whole_variant.rs b/test_suite/tests/compile-fail/with-variant/skip_de_whole_variant.rs new file mode 100644 index 000000000..68c015a28 --- /dev/null +++ b/test_suite/tests/compile-fail/with-variant/skip_de_whole_variant.rs @@ -0,0 +1,26 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked +//~^ HELP: variant `Unit` cannot have both #[serde(deserialize_with)] and #[serde(skip_deserializing)] +enum Enum { + #[serde(deserialize_with = "deserialize_some_unit_variant")] + #[serde(skip_deserializing)] + Unit, +} + +fn deserialize_some_unit_variant<'de, D>(_: D) -> StdResult<(), D::Error> + where D: Deserializer<'de>, +{ + unimplemented!() +} + +fn main() { } diff --git a/test_suite/tests/compile-fail/with-variant/skip_ser_newtype_field.rs b/test_suite/tests/compile-fail/with-variant/skip_ser_newtype_field.rs new file mode 100644 index 000000000..162812d40 --- /dev/null +++ b/test_suite/tests/compile-fail/with-variant/skip_ser_newtype_field.rs @@ -0,0 +1,25 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Serialize)] //~ ERROR: proc-macro derive panicked +//~^ HELP: variant `Newtype` cannot have both #[serde(serialize_with)] and a field 0 marked with #[serde(skip_serializing)] +enum Enum { + #[serde(serialize_with = "serialize_some_newtype_variant")] + Newtype(#[serde(skip_serializing)] String), +} + +fn serialize_some_newtype_variant(_: &String) -> StdResult + where S: Serializer, +{ + unimplemented!() +} + +fn main() { } diff --git a/test_suite/tests/compile-fail/with-variant/skip_ser_newtype_field_if.rs b/test_suite/tests/compile-fail/with-variant/skip_ser_newtype_field_if.rs new file mode 100644 index 000000000..065ca20dc --- /dev/null +++ b/test_suite/tests/compile-fail/with-variant/skip_ser_newtype_field_if.rs @@ -0,0 +1,27 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Serialize)] //~ ERROR: proc-macro derive panicked +//~^ HELP: variant `Newtype` cannot have both #[serde(serialize_with)] and a field 0 marked with #[serde(skip_serializing_if)] +enum Enum { + #[serde(serialize_with = "serialize_some_newtype_variant")] + Newtype(#[serde(skip_serializing_if = "always")] String), +} + +fn serialize_some_newtype_variant(_: &String) -> StdResult + where S: Serializer, +{ + unimplemented!() +} + +fn always(_: &T) -> bool { true } + +fn main() { } diff --git a/test_suite/tests/compile-fail/with-variant/skip_ser_struct_field.rs b/test_suite/tests/compile-fail/with-variant/skip_ser_struct_field.rs new file mode 100644 index 000000000..dc046b86a --- /dev/null +++ b/test_suite/tests/compile-fail/with-variant/skip_ser_struct_field.rs @@ -0,0 +1,29 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Serialize)] //~ ERROR: proc-macro derive panicked +//~^ HELP: variant `Struct` cannot have both #[serde(serialize_with)] and a field `f1` marked with #[serde(skip_serializing)] +enum Enum { + #[serde(serialize_with = "serialize_some_other_variant")] + Struct { + #[serde(skip_serializing)] + f1: String, + f2: u8, + }, +} + +fn serialize_some_other_variant(_: &String, _: Option<&u8>, _: S) -> StdResult + where S: Serializer, +{ + unimplemented!() +} + +fn main() { } diff --git a/test_suite/tests/compile-fail/with-variant/skip_ser_struct_field_if.rs b/test_suite/tests/compile-fail/with-variant/skip_ser_struct_field_if.rs new file mode 100644 index 000000000..fd42947d2 --- /dev/null +++ b/test_suite/tests/compile-fail/with-variant/skip_ser_struct_field_if.rs @@ -0,0 +1,31 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Serialize)] //~ ERROR: proc-macro derive panicked +//~^ HELP: variant `Struct` cannot have both #[serde(serialize_with)] and a field `f1` marked with #[serde(skip_serializing_if)] +enum Enum { + #[serde(serialize_with = "serialize_some_newtype_variant")] + Struct { + #[serde(skip_serializing_if = "always")] + f1: String, + f2: u8, + }, +} + +fn serialize_some_other_variant(_: &String, _: Option<&u8>, _: S) -> StdResult + where S: Serializer, +{ + unimplemented!() +} + +fn always(_: &T) -> bool { true } + +fn main() { } diff --git a/test_suite/tests/compile-fail/with-variant/skip_ser_tuple_field.rs b/test_suite/tests/compile-fail/with-variant/skip_ser_tuple_field.rs new file mode 100644 index 000000000..56cbfa450 --- /dev/null +++ b/test_suite/tests/compile-fail/with-variant/skip_ser_tuple_field.rs @@ -0,0 +1,25 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Serialize)] //~ ERROR: proc-macro derive panicked +//~^ HELP: variant `Tuple` cannot have both #[serde(serialize_with)] and a field 0 marked with #[serde(skip_serializing)] +enum Enum { + #[serde(serialize_with = "serialize_some_other_variant")] + Tuple(#[serde(skip_serializing)] String, u8), +} + +fn serialize_some_other_variant(_: &String, _: Option<&u8>, _: S) -> StdResult + where S: Serializer, +{ + unimplemented!() +} + +fn main() { } diff --git a/test_suite/tests/compile-fail/with-variant/skip_ser_tuple_field_if.rs b/test_suite/tests/compile-fail/with-variant/skip_ser_tuple_field_if.rs new file mode 100644 index 000000000..c85db63d0 --- /dev/null +++ b/test_suite/tests/compile-fail/with-variant/skip_ser_tuple_field_if.rs @@ -0,0 +1,27 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Serialize)] //~ ERROR: proc-macro derive panicked +//~^ HELP: variant `Tuple` cannot have both #[serde(serialize_with)] and a field 0 marked with #[serde(skip_serializing_if)] +enum Enum { + #[serde(serialize_with = "serialize_some_other_variant")] + Tuple(#[serde(skip_serializing_if = "always")] String, u8), +} + +fn serialize_some_other_variant(_: &String, _: Option<&u8>, _: S) -> StdResult + where S: Serializer, +{ + unimplemented!() +} + +fn always(_: &T) -> bool { true } + +fn main() { } diff --git a/test_suite/tests/compile-fail/with-variant/skip_ser_whole_variant.rs b/test_suite/tests/compile-fail/with-variant/skip_ser_whole_variant.rs new file mode 100644 index 000000000..8e3291e3e --- /dev/null +++ b/test_suite/tests/compile-fail/with-variant/skip_ser_whole_variant.rs @@ -0,0 +1,26 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate serde_derive; + +#[derive(Serialize)] //~ ERROR: proc-macro derive panicked +//~^ HELP: variant `Unit` cannot have both #[serde(serialize_with)] and #[serde(skip_serializing)] +enum Enum { + #[serde(serialize_with = "serialize_some_unit_variant")] + #[serde(skip_serializing)] + Unit, +} + +fn serialize_some_unit_variant(_: S) -> StdResult + where S: Serializer, +{ + unimplemented!() +} + +fn main() { } diff --git a/test_suite/tests/test_annotations.rs b/test_suite/tests/test_annotations.rs index cb9642e8f..fb7961110 100644 --- a/test_suite/tests/test_annotations.rs +++ b/test_suite/tests/test_annotations.rs @@ -11,6 +11,7 @@ extern crate serde_derive; extern crate serde; use self::serde::{Serialize, Serializer, Deserialize, Deserializer}; +use self::serde::de::{self, Unexpected}; extern crate serde_test; use self::serde_test::{Token, assert_tokens, assert_ser_tokens, assert_de_tokens, @@ -811,6 +812,145 @@ fn test_serialize_with_enum() { ); } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +enum WithVariant { + #[serde(serialize_with="serialize_unit_variant_as_i8")] + #[serde(deserialize_with="deserialize_i8_as_unit_variant")] + Unit, + + #[serde(serialize_with="SerializeWith::serialize_with")] + #[serde(deserialize_with="DeserializeWith::deserialize_with")] + Newtype(i32), + + #[serde(serialize_with="serialize_variant_as_string")] + #[serde(deserialize_with="deserialize_string_as_variant")] + Tuple(String, u8), + + #[serde(serialize_with="serialize_variant_as_string")] + #[serde(deserialize_with="deserialize_string_as_variant")] + Struct { + f1: String, + f2: u8, + }, +} + +fn serialize_unit_variant_as_i8(serializer: S) -> Result + where S: Serializer, +{ + serializer.serialize_i8(0) +} + +fn deserialize_i8_as_unit_variant<'de, D>(deserializer: D) -> Result<(), D::Error> + where D: Deserializer<'de>, +{ + let n = i8::deserialize(deserializer)?; + match n { + 0 => Ok(()), + _ => Err(de::Error::invalid_value(Unexpected::Signed(n as i64), &"0")), + } +} + +fn serialize_variant_as_string(f1: &str, + f2: &u8, + serializer: S) + -> Result + where S: Serializer, +{ + serializer.serialize_str(format!("{};{:?}", f1, f2).as_str()) +} + +fn deserialize_string_as_variant<'de, D>(deserializer: D) -> Result<(String, u8), D::Error> + where D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + let mut pieces = s.split(';'); + let f1 = match pieces.next() { + Some(x) => x, + None => return Err(de::Error::invalid_length(0, &"2")), + }; + let f2 = match pieces.next() { + Some(x) => x, + None => return Err(de::Error::invalid_length(1, &"2")), + }; + let f2 = match f2.parse() { + Ok(n) => n, + Err(_) => { + return Err(de::Error::invalid_value(Unexpected::Str(f2), &"an 8-bit signed integer")); + } + }; + Ok((f1.into(), f2)) +} + +#[test] +fn test_serialize_with_variant() { + assert_ser_tokens( + &WithVariant::Unit, + &[ + Token::NewtypeVariant { name: "WithVariant", variant: "Unit" }, + Token::I8(0), + ], + ); + + assert_ser_tokens( + &WithVariant::Newtype(123), + &[ + Token::NewtypeVariant { name: "WithVariant", variant: "Newtype" }, + Token::Bool(true), + ], + ); + + assert_ser_tokens( + &WithVariant::Tuple("hello".into(), 0), + &[ + Token::NewtypeVariant { name: "WithVariant", variant: "Tuple" }, + Token::Str("hello;0"), + ], + ); + + assert_ser_tokens( + &WithVariant::Struct { f1: "world".into(), f2: 1 }, + &[ + Token::NewtypeVariant { name: "WithVariant", variant: "Struct" }, + Token::Str("world;1"), + ], + ); +} + +#[test] +fn test_deserialize_with_variant() { + assert_de_tokens( + &WithVariant::Unit, + &[ + Token::NewtypeVariant { name: "WithVariant", variant: "Unit" }, + Token::I8(0), + ], + ); + + assert_de_tokens( + &WithVariant::Newtype(123), + &[ + Token::NewtypeVariant { name: "WithVariant", variant: "Newtype" }, + Token::Bool(true), + ], + ); + + assert_de_tokens( + &WithVariant::Tuple("hello".into(), 0), + &[ + Token::NewtypeVariant { name: "WithVariant", variant: "Tuple" }, + Token::Str("hello;0"), + ], + ); + + assert_de_tokens( + &WithVariant::Struct { f1: "world".into(), f2: 1 }, + &[ + Token::NewtypeVariant { name: "WithVariant", variant: "Struct" }, + Token::Str("world;1"), + ], + ); +} + #[derive(Debug, PartialEq, Deserialize)] struct DeserializeWithStruct where diff --git a/test_suite/tests/test_gen.rs b/test_suite/tests/test_gen.rs index 5d708014b..76c57db8a 100644 --- a/test_suite/tests/test_gen.rs +++ b/test_suite/tests/test_gen.rs @@ -373,6 +373,124 @@ fn test_gen() { #[serde(with = "vis::SDef")] s: vis::S, } + + #[derive(Serialize, Deserialize)] + enum ExternallyTaggedVariantWith { + #[allow(dead_code)] + Normal { f1: String }, + + #[serde(serialize_with = "ser_x")] + #[serde(deserialize_with = "de_x")] + #[allow(dead_code)] + Newtype(X), + + #[serde(serialize_with = "serialize_some_other_variant")] + #[serde(deserialize_with = "deserialize_some_other_variant")] + #[allow(dead_code)] + Tuple(String, u8), + + #[serde(serialize_with = "serialize_some_other_variant")] + #[serde(deserialize_with = "deserialize_some_other_variant")] + #[allow(dead_code)] + Struct { + f1: String, + f2: u8, + }, + + #[serde(serialize_with = "serialize_some_unit_variant")] + #[serde(deserialize_with = "deserialize_some_unit_variant")] + #[allow(dead_code)] + Unit, + } + assert_ser::(); + + #[derive(Serialize, Deserialize)] + #[serde(tag = "t")] + enum InternallyTaggedVariantWith { + #[allow(dead_code)] + Normal { f1: String }, + + #[serde(serialize_with = "ser_x")] + #[serde(deserialize_with = "de_x")] + #[allow(dead_code)] + Newtype(X), + + #[serde(serialize_with = "serialize_some_other_variant")] + #[serde(deserialize_with = "deserialize_some_other_variant")] + #[allow(dead_code)] + Struct { + f1: String, + f2: u8, + }, + + #[serde(serialize_with = "serialize_some_unit_variant")] + #[serde(deserialize_with = "deserialize_some_unit_variant")] + #[allow(dead_code)] + Unit, + } + assert_ser::(); + + #[derive(Serialize, Deserialize)] + #[serde(tag = "t", content = "c")] + enum AdjacentlyTaggedVariantWith { + #[allow(dead_code)] + Normal { f1: String }, + + #[serde(serialize_with = "ser_x")] + #[serde(deserialize_with = "de_x")] + #[allow(dead_code)] + Newtype(X), + + #[serde(serialize_with = "serialize_some_other_variant")] + #[serde(deserialize_with = "deserialize_some_other_variant")] + #[allow(dead_code)] + Tuple(String, u8), + + #[serde(serialize_with = "serialize_some_other_variant")] + #[serde(deserialize_with = "deserialize_some_other_variant")] + #[allow(dead_code)] + Struct { + f1: String, + f2: u8, + }, + + #[serde(serialize_with = "serialize_some_unit_variant")] + #[serde(deserialize_with = "deserialize_some_unit_variant")] + #[allow(dead_code)] + Unit, + } + assert_ser::(); + + #[derive(Serialize, Deserialize)] + #[serde(untagged)] + enum UntaggedVariantWith { + #[allow(dead_code)] + Normal { f1: String }, + + #[serde(serialize_with = "ser_x")] + #[serde(deserialize_with = "de_x")] + #[allow(dead_code)] + Newtype(X), + + #[serde(serialize_with = "serialize_some_other_variant")] + #[serde(deserialize_with = "deserialize_some_other_variant")] + #[allow(dead_code)] + Tuple(String, u8), + + #[serde(serialize_with = "serialize_some_other_variant")] + #[serde(deserialize_with = "deserialize_some_other_variant")] + #[allow(dead_code)] + Struct { + f1: String, + f2: u8, + }, + + #[serde(serialize_with = "serialize_some_unit_variant")] + #[serde(deserialize_with = "deserialize_some_unit_variant")] + #[allow(dead_code)] + Unit, + } + assert_ser::(); } ////////////////////////////////////////////////////////////////////////// @@ -414,3 +532,29 @@ impl DeserializeWith for X { unimplemented!() } } + +pub fn serialize_some_unit_variant(_: S) -> StdResult + where S: Serializer, +{ + unimplemented!() +} + +pub fn deserialize_some_unit_variant<'de, D>(_: D) -> StdResult<(), D::Error> + where D: Deserializer<'de>, +{ + unimplemented!() +} + +pub fn serialize_some_other_variant(_: &str, _: &u8, _: S) -> StdResult + where S: Serializer, +{ + unimplemented!() +} + +pub fn deserialize_some_other_variant<'de, D>(_: D) -> StdResult<(String, u8), D::Error> + where D: Deserializer<'de>, +{ + unimplemented!() +} + +pub fn is_zero(n: &u8) -> bool { *n == 0 }