Merge pull request #780 from Thomasdezeeuw/default_container
Add support for `#[serde(default)]` on structs
This commit is contained in:
commit
ad480d2b04
@ -90,6 +90,7 @@ impl Name {
|
||||
pub struct Item {
|
||||
name: Name,
|
||||
deny_unknown_fields: bool,
|
||||
default: bool,
|
||||
ser_bound: Option<Vec<syn::WherePredicate>>,
|
||||
de_bound: Option<Vec<syn::WherePredicate>>,
|
||||
tag: EnumTag,
|
||||
@ -133,6 +134,7 @@ impl Item {
|
||||
let mut ser_name = Attr::none(cx, "rename");
|
||||
let mut de_name = Attr::none(cx, "rename");
|
||||
let mut deny_unknown_fields = BoolAttr::none(cx, "deny_unknown_fields");
|
||||
let mut default = BoolAttr::none(cx, "default");
|
||||
let mut ser_bound = Attr::none(cx, "bound");
|
||||
let mut de_bound = Attr::none(cx, "bound");
|
||||
let mut untagged = BoolAttr::none(cx, "untagged");
|
||||
@ -163,6 +165,18 @@ impl Item {
|
||||
deny_unknown_fields.set_true();
|
||||
}
|
||||
|
||||
// Parse `#[serde(default)]`
|
||||
MetaItem(Word(ref name)) if name == "default" => {
|
||||
match item.body {
|
||||
syn::Body::Struct(_) => {
|
||||
default.set_true();
|
||||
}
|
||||
_ => {
|
||||
cx.error("#[serde(default)] can only be used on structs")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(bound="D: Serialize")]`
|
||||
MetaItem(NameValue(ref name, ref lit)) if name == "bound" => {
|
||||
if let Ok(where_predicates) =
|
||||
@ -281,6 +295,7 @@ impl Item {
|
||||
deserialize: de_name.get().unwrap_or_else(|| item.ident.to_string()),
|
||||
},
|
||||
deny_unknown_fields: deny_unknown_fields.get(),
|
||||
default: default.get(),
|
||||
ser_bound: ser_bound.get(),
|
||||
de_bound: de_bound.get(),
|
||||
tag: tag,
|
||||
@ -295,6 +310,10 @@ impl Item {
|
||||
self.deny_unknown_fields
|
||||
}
|
||||
|
||||
pub fn default(&self) -> bool {
|
||||
self.default
|
||||
}
|
||||
|
||||
pub fn ser_bound(&self) -> Option<&[syn::WherePredicate]> {
|
||||
self.ser_bound.as_ref().map(|vec| &vec[..])
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ fn deserialize_seq(ident: &syn::Ident,
|
||||
let let_values = vars.clone().zip(fields)
|
||||
.map(|(var, field)| {
|
||||
if field.attrs.skip_deserializing() {
|
||||
let default = expr_is_missing(&field.attrs);
|
||||
let default = expr_is_missing(&field.attrs, false, "");
|
||||
quote! {
|
||||
let #var = #default;
|
||||
}
|
||||
@ -1012,7 +1012,11 @@ fn deserialize_map(ident: &syn::Ident,
|
||||
let extract_values = fields_names.iter()
|
||||
.filter(|&&(field, _)| !field.attrs.skip_deserializing())
|
||||
.map(|&(field, ref name)| {
|
||||
let missing_expr = expr_is_missing(&field.attrs);
|
||||
// Use the ident as field name, since the user can rename the field
|
||||
// in the attributes using `#[serde(rename = "name")]`, but we need
|
||||
// the original (in code) name of the field.
|
||||
let ident = field.ident.clone().expect("struct contains unnamed fields");
|
||||
let missing_expr = expr_is_missing(&field.attrs, item_attrs.default(), ident.as_ref());
|
||||
|
||||
quote! {
|
||||
let #name = match #name {
|
||||
@ -1026,18 +1030,30 @@ fn deserialize_map(ident: &syn::Ident,
|
||||
.map(|&(field, ref name)| {
|
||||
let ident = field.ident.clone().expect("struct contains unnamed fields");
|
||||
let value = if field.attrs.skip_deserializing() {
|
||||
expr_is_missing(&field.attrs)
|
||||
expr_is_missing(&field.attrs, item_attrs.default(), ident.as_ref())
|
||||
} else {
|
||||
quote!(#name)
|
||||
};
|
||||
quote!(#ident: #value)
|
||||
});
|
||||
|
||||
let default = if item_attrs.default() {
|
||||
quote!(
|
||||
let default: #struct_path = _serde::export::Default::default();
|
||||
)
|
||||
} else {
|
||||
// We don't need the default value, to prevent an unused variable warning
|
||||
// we'll leave the line empty.
|
||||
quote!()
|
||||
};
|
||||
|
||||
quote! {
|
||||
#(#let_values)*
|
||||
|
||||
#match_keys
|
||||
|
||||
#default
|
||||
|
||||
#(#extract_values)*
|
||||
|
||||
_serde::export::Ok(#struct_path { #(#result),* })
|
||||
@ -1081,7 +1097,7 @@ fn wrap_deserialize_with(ident: &syn::Ident,
|
||||
(wrapper, wrapper_ty)
|
||||
}
|
||||
|
||||
fn expr_is_missing(attrs: &attr::Field) -> Tokens {
|
||||
fn expr_is_missing(attrs: &attr::Field, use_default: bool, field_name: &str) -> Tokens {
|
||||
match *attrs.default() {
|
||||
attr::FieldDefault::Default => {
|
||||
return quote!(_serde::export::Default::default());
|
||||
@ -1092,6 +1108,14 @@ fn expr_is_missing(attrs: &attr::Field) -> Tokens {
|
||||
attr::FieldDefault::None => { /* below */ }
|
||||
}
|
||||
|
||||
if use_default {
|
||||
// Field name without the qoutes.
|
||||
let field_name = quote::Ident::new(field_name);
|
||||
return quote!(
|
||||
default.#field_name
|
||||
)
|
||||
}
|
||||
|
||||
let name = attrs.name().deserialize_name();
|
||||
match attrs.deserialize_with() {
|
||||
None => {
|
||||
|
8
test_suite/tests/compile-fail/default-attribute/enum.rs
Normal file
8
test_suite/tests/compile-fail/default-attribute/enum.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked
|
||||
#[serde(default)] //~^ HELP: #[serde(default)] can only be used on structs
|
||||
enum E {
|
||||
S { f: u8 },
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked
|
||||
#[serde(default)] //~^ HELP: #[serde(default)] can only be used on structs
|
||||
struct T(u8, u8);
|
||||
|
||||
fn main() { }
|
@ -2,6 +2,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::net;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use std::default::Default;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
@ -40,6 +41,22 @@ struct StructDenyUnknown {
|
||||
b: i32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
struct StructDefault {
|
||||
a: i32,
|
||||
b: String,
|
||||
}
|
||||
|
||||
impl Default for StructDefault {
|
||||
fn default() -> StructDefault {
|
||||
StructDefault{
|
||||
a: 100,
|
||||
b: "default".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Deserialize)]
|
||||
struct StructSkipAll {
|
||||
#[serde(skip_deserializing)]
|
||||
@ -728,6 +745,23 @@ declare_tests! {
|
||||
Token::StructEnd,
|
||||
],
|
||||
}
|
||||
test_struct_default {
|
||||
StructDefault{ a: 50, b: "overwritten".to_string() } => &[
|
||||
Token::StructStart("StructDefault", 1),
|
||||
Token::StructSep,
|
||||
Token::Str("a"),
|
||||
Token::I32(50),
|
||||
|
||||
Token::StructSep,
|
||||
Token::Str("b"),
|
||||
Token::String("overwritten".to_string()),
|
||||
Token::StructEnd,
|
||||
],
|
||||
StructDefault{ a: 100, b: "default".to_string() } => &[
|
||||
Token::StructStart("StructDefault", 0),
|
||||
Token::StructEnd,
|
||||
],
|
||||
}
|
||||
test_enum_unit {
|
||||
Enum::Unit => &[
|
||||
Token::EnumUnit("Enum", "Unit"),
|
||||
|
Loading…
Reference in New Issue
Block a user