Implement deserialize_with for variants
Complements variant serialize_with and closes #1013.
This commit is contained in:
parent
5b815b7001
commit
9fc180e62f
@ -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 => {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() { }
|
@ -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() { }
|
@ -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() { }
|
@ -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() { }
|
@ -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,7 +840,17 @@ fn serialize_unit_variant_as_i8<S>(serializer: S) -> Result<S::Ok, S::Error>
|
||||
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,
|
||||
serializer: S)
|
||||
-> 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())
|
||||
}
|
||||
|
||||
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"),
|
||||
],
|
||||
);
|
||||
|
@ -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::<ExternallyTaggedVariantWith>();
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "t")]
|
||||
enum InternallyTaggedVariantWith {
|
||||
#[allow(dead_code)]
|
||||
@ -430,7 +430,7 @@ fn test_gen() {
|
||||
}
|
||||
assert_ser::<InternallyTaggedVariantWith>();
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
enum AdjacentlyTaggedVariantWith {
|
||||
#[allow(dead_code)]
|
||||
@ -461,7 +461,7 @@ fn test_gen() {
|
||||
}
|
||||
assert_ser::<AdjacentlyTaggedVariantWith>();
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum UntaggedVariantWith {
|
||||
#[allow(dead_code)]
|
||||
|
Loading…
Reference in New Issue
Block a user