diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index b7fa3f1b..b3589f55 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -375,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 @@ -431,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 @@ -1087,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 { @@ -1115,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 { @@ -1140,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 { @@ -1201,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( @@ -1229,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( @@ -1531,7 +1555,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 @@ -1664,7 +1688,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; @@ -1672,7 +1696,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 ()>, } @@ -1695,6 +1719,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_internals/src/check.rs b/serde_derive_internals/src/check.rs index 6fe31615..5d6a76ff 100644 --- a/serde_derive_internals/src/check.rs +++ b/serde_derive_internals/src/check.rs @@ -130,5 +130,23 @@ fn check_variant_skip_attrs(cx: &Ctxt, cont: &Container) { } } } + + 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 00000000..391af1ba --- /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 00000000..7c563a8a --- /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 00000000..44fd0958 --- /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 00000000..68c015a2 --- /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/test_annotations.rs b/test_suite/tests/test_annotations.rs index 3a31b775..e0f6c8da 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,18 +812,22 @@ fn test_serialize_with_enum() { ); } -#[derive(Debug, PartialEq, Serialize)] -enum SerializeWithVariant { +#[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_some_other_variant")] + #[serde(serialize_with="serialize_variant_as_string")] + #[serde(deserialize_with="deserialize_string_as_variant")] Tuple(String, u8), - #[serde(serialize_with="serialize_some_other_variant")] + #[serde(serialize_with="serialize_variant_as_string")] + #[serde(deserialize_with="deserialize_string_as_variant")] Struct { f1: String, f2: u8, @@ -835,45 +840,112 @@ fn serialize_unit_variant_as_i8(serializer: S) -> Result serializer.serialize_i8(0) } -fn serialize_some_other_variant(f1: &String, - f2: &u8, - serializer: S) - -> Result +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: &String, + 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( - &SerializeWithVariant::Unit, + &WithVariant::Unit, &[ - Token::NewtypeVariant { name: "SerializeWithVariant", variant: "Unit" }, + Token::NewtypeVariant { name: "WithVariant", variant: "Unit" }, Token::I8(0), ], ); assert_ser_tokens( - &SerializeWithVariant::Newtype(123), + &WithVariant::Newtype(123), &[ - Token::NewtypeVariant { name: "SerializeWithVariant", variant: "Newtype" }, + Token::NewtypeVariant { name: "WithVariant", variant: "Newtype" }, Token::Bool(true), ], ); assert_ser_tokens( - &SerializeWithVariant::Tuple("hello".into(), 0), + &WithVariant::Tuple("hello".into(), 0), &[ - Token::NewtypeVariant { name: "SerializeWithVariant", variant: "Tuple" }, + Token::NewtypeVariant { name: "WithVariant", variant: "Tuple" }, Token::Str("hello;0"), ], ); assert_ser_tokens( - &SerializeWithVariant::Struct { f1: "world".into(), f2: 1 }, + &WithVariant::Struct { f1: "world".into(), f2: 1 }, &[ - Token::NewtypeVariant { name: "SerializeWithVariant", variant: "Struct" }, + 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"), ], ); diff --git a/test_suite/tests/test_gen.rs b/test_suite/tests/test_gen.rs index 2c85a116..5ec186d0 100644 --- a/test_suite/tests/test_gen.rs +++ b/test_suite/tests/test_gen.rs @@ -374,7 +374,7 @@ fn test_gen() { s: vis::S, } - #[derive(Serialize)] + #[derive(Serialize, Deserialize)] enum ExternallyTaggedVariantWith { #[allow(dead_code)] Normal { f1: String }, @@ -404,7 +404,7 @@ fn test_gen() { } assert_ser::(); - #[derive(Serialize)] + #[derive(Serialize, Deserialize)] #[serde(tag = "t")] enum InternallyTaggedVariantWith { #[allow(dead_code)] @@ -430,7 +430,7 @@ fn test_gen() { } assert_ser::(); - #[derive(Serialize)] + #[derive(Serialize, Deserialize)] #[serde(tag = "t", content = "c")] enum AdjacentlyTaggedVariantWith { #[allow(dead_code)] @@ -461,7 +461,7 @@ fn test_gen() { } assert_ser::(); - #[derive(Serialize)] + #[derive(Serialize, Deserialize)] #[serde(untagged)] enum UntaggedVariantWith { #[allow(dead_code)]