Implement deserialize_with for variants

Complements variant serialize_with and closes #1013.
This commit is contained in:
Michael Smith 2017-08-14 14:39:29 -07:00
parent 5b815b7001
commit 9fc180e62f
No known key found for this signature in database
GPG Key ID: 8645BDF574E8F755
8 changed files with 308 additions and 27 deletions

View File

@ -375,7 +375,7 @@ fn deserialize_seq(
quote!(try!(_serde::de::SeqAccess::next_element::<#field_ty>(&mut __seq))) quote!(try!(_serde::de::SeqAccess::next_element::<#field_ty>(&mut __seq)))
} }
Some(path) => { Some(path) => {
let (wrapper, wrapper_ty) = wrap_deserialize_with( let (wrapper, wrapper_ty) = wrap_deserialize_field_with(
params, field.ty, path); params, field.ty, path);
quote!({ quote!({
#wrapper #wrapper
@ -431,7 +431,7 @@ fn deserialize_newtype_struct(type_path: &Tokens, params: &Parameters, field: &F
} }
} }
Some(path) => { 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!({ quote!({
#wrapper #wrapper
try!(<#wrapper_ty as _serde::Deserialize>::deserialize(__e)).value try!(<#wrapper_ty as _serde::Deserialize>::deserialize(__e)).value
@ -1087,6 +1087,16 @@ fn deserialize_externally_tagged_variant(
variant: &Variant, variant: &Variant,
cattrs: &attr::Container, cattrs: &attr::Container,
) -> Fragment { ) -> 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; let variant_ident = &variant.ident;
match variant.style { match variant.style {
@ -1115,6 +1125,10 @@ fn deserialize_internally_tagged_variant(
cattrs: &attr::Container, cattrs: &attr::Container,
deserializer: Tokens, deserializer: Tokens,
) -> Fragment { ) -> Fragment {
if variant.attrs.deserialize_with().is_some() {
return deserialize_untagged_variant(params, variant, cattrs, deserializer);
}
let variant_ident = &variant.ident; let variant_ident = &variant.ident;
match variant.style { match variant.style {
@ -1140,6 +1154,16 @@ fn deserialize_untagged_variant(
cattrs: &attr::Container, cattrs: &attr::Container,
deserializer: Tokens, deserializer: Tokens,
) -> Fragment { ) -> 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; let variant_ident = &variant.ident;
match variant.style { match variant.style {
@ -1201,7 +1225,7 @@ fn deserialize_externally_tagged_newtype_variant(
} }
} }
Some(path) => { 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! { quote_block! {
#wrapper #wrapper
_serde::export::Result::map( _serde::export::Result::map(
@ -1229,7 +1253,7 @@ fn deserialize_untagged_newtype_variant(
} }
} }
Some(path) => { 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! { quote_block! {
#wrapper #wrapper
_serde::export::Result::map( _serde::export::Result::map(
@ -1531,7 +1555,7 @@ fn deserialize_map(
} }
} }
Some(path) => { Some(path) => {
let (wrapper, wrapper_ty) = wrap_deserialize_with( let (wrapper, wrapper_ty) = wrap_deserialize_field_with(
params, field.ty, path); params, field.ty, path);
quote!({ quote!({
#wrapper #wrapper
@ -1664,7 +1688,7 @@ fn field_i(i: usize) -> Ident {
/// in a trait to prevent it from accessing the internal `Deserialize` state. /// in a trait to prevent it from accessing the internal `Deserialize` state.
fn wrap_deserialize_with( fn wrap_deserialize_with(
params: &Parameters, params: &Parameters,
field_ty: &syn::Ty, value_ty: Tokens,
deserialize_with: &syn::Path, deserialize_with: &syn::Path,
) -> (Tokens, Tokens) { ) -> (Tokens, Tokens) {
let this = &params.this; let this = &params.this;
@ -1672,7 +1696,7 @@ fn wrap_deserialize_with(
let wrapper = quote! { let wrapper = quote! {
struct __DeserializeWith #de_impl_generics #where_clause { struct __DeserializeWith #de_impl_generics #where_clause {
value: #field_ty, value: #value_ty,
phantom: _serde::export::PhantomData<#this #ty_generics>, phantom: _serde::export::PhantomData<#this #ty_generics>,
lifetime: _serde::export::PhantomData<&'de ()>, lifetime: _serde::export::PhantomData<&'de ()>,
} }
@ -1695,6 +1719,68 @@ fn wrap_deserialize_with(
(wrapper, wrapper_ty) (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 = &params.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 { fn expr_is_missing(field: &Field, cattrs: &attr::Container) -> Fragment {
match *field.attrs.default() { match *field.attrs.default() {
attr::Default::Default => { attr::Default::Default => {

View File

@ -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));
}
}
}
} }
} }

View File

@ -0,0 +1,25 @@
// Copyright 2017 Serde Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<String, D::Error>
where D: Deserializer<'de>,
{
unimplemented!()
}
fn main() { }

View File

@ -0,0 +1,29 @@
// Copyright 2017 Serde Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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() { }

View File

@ -0,0 +1,25 @@
// Copyright 2017 Serde Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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() { }

View File

@ -0,0 +1,26 @@
// Copyright 2017 Serde Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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() { }

View File

@ -11,6 +11,7 @@ extern crate serde_derive;
extern crate serde; extern crate serde;
use self::serde::{Serialize, Serializer, Deserialize, Deserializer}; use self::serde::{Serialize, Serializer, Deserialize, Deserializer};
use self::serde::de::{self, Unexpected};
extern crate serde_test; extern crate serde_test;
use self::serde_test::{Token, assert_tokens, assert_ser_tokens, assert_de_tokens, 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)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
enum SerializeWithVariant { enum WithVariant {
#[serde(serialize_with="serialize_unit_variant_as_i8")] #[serde(serialize_with="serialize_unit_variant_as_i8")]
#[serde(deserialize_with="deserialize_i8_as_unit_variant")]
Unit, Unit,
#[serde(serialize_with="SerializeWith::serialize_with")] #[serde(serialize_with="SerializeWith::serialize_with")]
#[serde(deserialize_with="DeserializeWith::deserialize_with")]
Newtype(i32), 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), 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 { Struct {
f1: String, f1: String,
f2: u8, f2: u8,
@ -835,7 +840,17 @@ fn serialize_unit_variant_as_i8<S>(serializer: S) -> Result<S::Ok, S::Error>
serializer.serialize_i8(0) serializer.serialize_i8(0)
} }
fn serialize_some_other_variant<S>(f1: &String, 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<S>(f1: &String,
f2: &u8, f2: &u8,
serializer: S) serializer: S)
-> Result<S::Ok, S::Error> -> Result<S::Ok, S::Error>
@ -844,36 +859,93 @@ fn serialize_some_other_variant<S>(f1: &String,
serializer.serialize_str(format!("{};{:?}", f1, f2).as_str()) 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] #[test]
fn test_serialize_with_variant() { fn test_serialize_with_variant() {
assert_ser_tokens( assert_ser_tokens(
&SerializeWithVariant::Unit, &WithVariant::Unit,
&[ &[
Token::NewtypeVariant { name: "SerializeWithVariant", variant: "Unit" }, Token::NewtypeVariant { name: "WithVariant", variant: "Unit" },
Token::I8(0), Token::I8(0),
], ],
); );
assert_ser_tokens( assert_ser_tokens(
&SerializeWithVariant::Newtype(123), &WithVariant::Newtype(123),
&[ &[
Token::NewtypeVariant { name: "SerializeWithVariant", variant: "Newtype" }, Token::NewtypeVariant { name: "WithVariant", variant: "Newtype" },
Token::Bool(true), Token::Bool(true),
], ],
); );
assert_ser_tokens( 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"), Token::Str("hello;0"),
], ],
); );
assert_ser_tokens( 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"), Token::Str("world;1"),
], ],
); );

View File

@ -374,7 +374,7 @@ fn test_gen() {
s: vis::S, s: vis::S,
} }
#[derive(Serialize)] #[derive(Serialize, Deserialize)]
enum ExternallyTaggedVariantWith { enum ExternallyTaggedVariantWith {
#[allow(dead_code)] #[allow(dead_code)]
Normal { f1: String }, Normal { f1: String },
@ -404,7 +404,7 @@ fn test_gen() {
} }
assert_ser::<ExternallyTaggedVariantWith>(); assert_ser::<ExternallyTaggedVariantWith>();
#[derive(Serialize)] #[derive(Serialize, Deserialize)]
#[serde(tag = "t")] #[serde(tag = "t")]
enum InternallyTaggedVariantWith { enum InternallyTaggedVariantWith {
#[allow(dead_code)] #[allow(dead_code)]
@ -430,7 +430,7 @@ fn test_gen() {
} }
assert_ser::<InternallyTaggedVariantWith>(); assert_ser::<InternallyTaggedVariantWith>();
#[derive(Serialize)] #[derive(Serialize, Deserialize)]
#[serde(tag = "t", content = "c")] #[serde(tag = "t", content = "c")]
enum AdjacentlyTaggedVariantWith { enum AdjacentlyTaggedVariantWith {
#[allow(dead_code)] #[allow(dead_code)]
@ -461,7 +461,7 @@ fn test_gen() {
} }
assert_ser::<AdjacentlyTaggedVariantWith>(); assert_ser::<AdjacentlyTaggedVariantWith>();
#[derive(Serialize)] #[derive(Serialize, Deserialize)]
#[serde(untagged)] #[serde(untagged)]
enum UntaggedVariantWith { enum UntaggedVariantWith {
#[allow(dead_code)] #[allow(dead_code)]