Merge branch 'skip_deserializing' of https://github.com/dtolnay/serde into dtolnay-skip_deserializing
This commit is contained in:
commit
dbba537b66
@ -714,6 +714,7 @@ Field Annotations:
|
||||
| `#[serde(default)]` | If the value is not specified, use the `Default::default()` |
|
||||
| `#[serde(default="$path")]` | Call the path to a function `fn() -> T` to build the value |
|
||||
| `#[serde(skip_serializing)]` | Do not serialize this value |
|
||||
| `#[serde(skip_deserializing)]` | Always use `Default::default()` instead of deserializing this value |
|
||||
| `#[serde(skip_serializing_if="$path")]` | Do not serialize this value if this function `fn(&T) -> bool` returns `false` |
|
||||
| `#[serde(serialize_with="$path")]` | Call a function `fn<T, S>(&T, &mut S) -> Result<(), S::Error> where S: Serializer` to serialize this value |
|
||||
| `#[serde(deserialize_with="$path")]` | Call a function `fn<T, D>(&mut D) -> Result<T, D::Error> where D: Deserializer` to deserialize this value |
|
||||
|
@ -176,6 +176,7 @@ impl VariantAttrs {
|
||||
pub struct FieldAttrs {
|
||||
name: Name,
|
||||
skip_serializing_field: bool,
|
||||
skip_deserializing_field: bool,
|
||||
skip_serializing_field_if: Option<P<ast::Expr>>,
|
||||
default_expr_if_missing: Option<P<ast::Expr>>,
|
||||
serialize_with: Option<P<ast::Expr>>,
|
||||
@ -199,6 +200,7 @@ impl FieldAttrs {
|
||||
let mut field_attrs = FieldAttrs {
|
||||
name: Name::new(field_ident),
|
||||
skip_serializing_field: false,
|
||||
skip_deserializing_field: false,
|
||||
skip_serializing_field_if: None,
|
||||
default_expr_if_missing: None,
|
||||
serialize_with: None,
|
||||
@ -244,6 +246,11 @@ impl FieldAttrs {
|
||||
field_attrs.skip_serializing_field = true;
|
||||
}
|
||||
|
||||
// Parse `#[serde(skip_deserializing)]`
|
||||
ast::MetaItemKind::Word(ref name) if name == &"skip_deserializing" => {
|
||||
field_attrs.skip_deserializing_field = true;
|
||||
}
|
||||
|
||||
// Parse `#[serde(skip_serializing_if="...")]`
|
||||
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"skip_serializing_if" => {
|
||||
let expr = wrap_skip_serializing(
|
||||
@ -320,6 +327,10 @@ impl FieldAttrs {
|
||||
self.skip_serializing_field
|
||||
}
|
||||
|
||||
pub fn skip_deserializing_field(&self) -> bool {
|
||||
self.skip_deserializing_field
|
||||
}
|
||||
|
||||
pub fn skip_serializing_field_if(&self) -> Option<&P<ast::Expr>> {
|
||||
self.skip_serializing_field_if.as_ref()
|
||||
}
|
||||
|
@ -430,19 +430,27 @@ fn deserialize_struct_as_seq(
|
||||
cx: &ExtCtxt,
|
||||
builder: &aster::AstBuilder,
|
||||
struct_path: ast::Path,
|
||||
fields: &[ast::StructField],
|
||||
fields: &[(&ast::StructField, attr::FieldAttrs)],
|
||||
) -> Result<P<ast::Expr>, Error> {
|
||||
let let_values: Vec<_> = (0 .. fields.len())
|
||||
.map(|i| {
|
||||
let let_values: Vec<_> = fields.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &(_, ref attrs))| {
|
||||
let name = builder.id(format!("__field{}", i));
|
||||
quote_stmt!(cx,
|
||||
let $name = match try!(visitor.visit()) {
|
||||
Some(value) => { value },
|
||||
None => {
|
||||
return Err(::serde::de::Error::end_of_stream());
|
||||
}
|
||||
};
|
||||
).unwrap()
|
||||
if attrs.skip_deserializing_field() {
|
||||
let default = builder.expr().default();
|
||||
quote_stmt!(cx,
|
||||
let $name = $default;
|
||||
).unwrap()
|
||||
} else {
|
||||
quote_stmt!(cx,
|
||||
let $name = match try!(visitor.visit()) {
|
||||
Some(value) => { value },
|
||||
None => {
|
||||
return Err(::serde::de::Error::end_of_stream());
|
||||
}
|
||||
};
|
||||
).unwrap()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -450,7 +458,7 @@ fn deserialize_struct_as_seq(
|
||||
.with_id_exprs(
|
||||
fields.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| {
|
||||
.map(|(i, &(field, _))| {
|
||||
(
|
||||
match field.ident {
|
||||
Some(name) => name.clone(),
|
||||
@ -493,22 +501,21 @@ fn deserialize_struct(
|
||||
|
||||
let type_path = builder.path().id(type_ident).build();
|
||||
|
||||
let fields_with_attrs = try!(fields_with_attrs(cx, impl_generics, &ty, fields, false));
|
||||
|
||||
let visit_seq_expr = try!(deserialize_struct_as_seq(
|
||||
cx,
|
||||
builder,
|
||||
type_path.clone(),
|
||||
fields,
|
||||
&fields_with_attrs,
|
||||
));
|
||||
|
||||
let (field_visitor, fields_stmt, visit_map_expr) = try!(deserialize_struct_visitor(
|
||||
cx,
|
||||
builder,
|
||||
type_path.clone(),
|
||||
&ty,
|
||||
impl_generics,
|
||||
fields,
|
||||
&fields_with_attrs,
|
||||
container_attrs,
|
||||
false,
|
||||
));
|
||||
|
||||
let type_name = container_attrs.name().deserialize_name_expr();
|
||||
@ -750,22 +757,21 @@ fn deserialize_struct_variant(
|
||||
.id(variant_ident)
|
||||
.build();
|
||||
|
||||
let fields_with_attrs = try!(fields_with_attrs(cx, generics, &ty, fields, true));
|
||||
|
||||
let visit_seq_expr = try!(deserialize_struct_as_seq(
|
||||
cx,
|
||||
builder,
|
||||
type_path.clone(),
|
||||
fields,
|
||||
&fields_with_attrs,
|
||||
));
|
||||
|
||||
let (field_visitor, fields_stmt, field_expr) = try!(deserialize_struct_visitor(
|
||||
cx,
|
||||
builder,
|
||||
type_path,
|
||||
&ty,
|
||||
generics,
|
||||
fields,
|
||||
&fields_with_attrs,
|
||||
container_attrs,
|
||||
true,
|
||||
));
|
||||
|
||||
let (visitor_item, visitor_ty, visitor_expr, visitor_generics) = try!(deserialize_visitor(
|
||||
@ -966,29 +972,17 @@ fn deserialize_struct_visitor(
|
||||
cx: &ExtCtxt,
|
||||
builder: &aster::AstBuilder,
|
||||
struct_path: ast::Path,
|
||||
container_ty: &P<ast::Ty>,
|
||||
generics: &ast::Generics,
|
||||
fields: &[ast::StructField],
|
||||
fields: &[(&ast::StructField, attr::FieldAttrs)],
|
||||
container_attrs: &attr::ContainerAttrs,
|
||||
is_enum: bool,
|
||||
) -> Result<(Vec<P<ast::Item>>, ast::Stmt, P<ast::Expr>), Error> {
|
||||
let field_exprs = fields.iter()
|
||||
.map(|field| {
|
||||
let field_attrs = try!(
|
||||
attr::FieldAttrs::from_field(cx,
|
||||
container_ty,
|
||||
generics,
|
||||
field,
|
||||
is_enum)
|
||||
);
|
||||
Ok(field_attrs.name().deserialize_name())
|
||||
})
|
||||
.map(|&(_, ref attrs)| attrs.name().deserialize_name())
|
||||
.collect();
|
||||
|
||||
let field_visitor = deserialize_field_visitor(
|
||||
cx,
|
||||
builder,
|
||||
try!(field_exprs),
|
||||
field_exprs,
|
||||
container_attrs,
|
||||
false,
|
||||
);
|
||||
@ -997,17 +991,14 @@ fn deserialize_struct_visitor(
|
||||
cx,
|
||||
builder,
|
||||
struct_path,
|
||||
container_ty,
|
||||
generics,
|
||||
fields,
|
||||
container_attrs,
|
||||
is_enum,
|
||||
));
|
||||
|
||||
let fields_expr = builder.expr().ref_().slice()
|
||||
.with_exprs(
|
||||
fields.iter()
|
||||
.map(|field| {
|
||||
.map(|&(field, _)| {
|
||||
match field.ident {
|
||||
Some(name) => builder.expr().str(name),
|
||||
None => {
|
||||
@ -1029,62 +1020,69 @@ fn deserialize_map(
|
||||
cx: &ExtCtxt,
|
||||
builder: &aster::AstBuilder,
|
||||
struct_path: ast::Path,
|
||||
container_ty: &P<ast::Ty>,
|
||||
generics: &ast::Generics,
|
||||
fields: &[ast::StructField],
|
||||
fields: &[(&ast::StructField, attr::FieldAttrs)],
|
||||
container_attrs: &attr::ContainerAttrs,
|
||||
is_enum: bool,
|
||||
) -> Result<P<ast::Expr>, Error> {
|
||||
// Create the field names for the fields.
|
||||
let field_names: Vec<ast::Ident> = (0 .. fields.len())
|
||||
.map(|i| builder.id(format!("__field{}", i)))
|
||||
let fields_attrs_names = fields.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &(ref field, ref attrs))|
|
||||
(field, attrs, builder.id(format!("__field{}", i))))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Declare each field that will be deserialized.
|
||||
let let_values: Vec<ast::Stmt> = fields_attrs_names.iter()
|
||||
.filter(|&&(_, ref attrs, _)| !attrs.skip_deserializing_field())
|
||||
.map(|&(_, _, name)| quote_stmt!(cx, let mut $name = None;).unwrap())
|
||||
.collect();
|
||||
|
||||
let field_attrs: Vec<_> = try!(
|
||||
fields.iter()
|
||||
.map(|field| attr::FieldAttrs::from_field(cx, container_ty, generics, field, is_enum))
|
||||
.collect()
|
||||
);
|
||||
|
||||
// Declare each field.
|
||||
let let_values: Vec<ast::Stmt> = field_names.iter()
|
||||
.map(|field_name| quote_stmt!(cx, let mut $field_name = None;).unwrap())
|
||||
.collect();
|
||||
|
||||
|
||||
// Visit ignored values to consume them
|
||||
let ignored_arm = if container_attrs.deny_unknown_fields() {
|
||||
None
|
||||
} else {
|
||||
Some(quote_arm!(cx,
|
||||
_ => { try!(visitor.visit_value::<::serde::de::impls::IgnoredAny>()); }
|
||||
))
|
||||
};
|
||||
|
||||
// Match arms to extract a value for a field.
|
||||
let value_arms = field_attrs.iter().zip(field_names.iter())
|
||||
.map(|(field_attr, field_name)| {
|
||||
let expr = match field_attr.deserialize_with() {
|
||||
let value_arms = fields_attrs_names.iter()
|
||||
.filter(|&&(_, ref attrs, _)| !attrs.skip_deserializing_field())
|
||||
.map(|&(_, ref attrs, name)| {
|
||||
let expr = match attrs.deserialize_with() {
|
||||
Some(expr) => expr.clone(),
|
||||
None => quote_expr!(cx, visitor.visit_value()),
|
||||
};
|
||||
|
||||
quote_arm!(cx,
|
||||
__Field::$field_name => {
|
||||
$field_name = Some(try!($expr));
|
||||
__Field::$name => {
|
||||
$name = Some(try!($expr));
|
||||
}
|
||||
)
|
||||
})
|
||||
.chain(ignored_arm.into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let extract_values = field_attrs.iter().zip(field_names.iter())
|
||||
.map(|(field_attr, field_name)| {
|
||||
let missing_expr = field_attr.expr_is_missing();
|
||||
// Match arms to ignore value for fields that have `skip_deserializing`.
|
||||
// Ignored even if `deny_unknown_fields` is set.
|
||||
let skipped_arms = fields_attrs_names.iter()
|
||||
.filter(|&&(_, ref attrs, _)| attrs.skip_deserializing_field())
|
||||
.map(|&(_, _, name)| {
|
||||
quote_arm!(cx,
|
||||
__Field::$name => {
|
||||
try!(visitor.visit_value::<::serde::de::impls::IgnoredAny>());
|
||||
}
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Visit ignored values to consume them
|
||||
let ignored_arm = if !container_attrs.deny_unknown_fields() {
|
||||
Some(quote_arm!(cx,
|
||||
_ => { try!(visitor.visit_value::<::serde::de::impls::IgnoredAny>()); }
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let extract_values = fields_attrs_names.iter()
|
||||
.filter(|&&(_, ref attrs, _)| !attrs.skip_deserializing_field())
|
||||
.map(|&(_, ref attrs, name)| {
|
||||
let missing_expr = attrs.expr_is_missing();
|
||||
|
||||
Ok(quote_stmt!(cx,
|
||||
let $field_name = match $field_name {
|
||||
Some($field_name) => $field_name,
|
||||
let $name = match $name {
|
||||
Some($name) => $name,
|
||||
None => $missing_expr
|
||||
};
|
||||
).unwrap())
|
||||
@ -1095,9 +1093,8 @@ fn deserialize_map(
|
||||
|
||||
let result = builder.expr().struct_path(struct_path)
|
||||
.with_id_exprs(
|
||||
fields.iter()
|
||||
.zip(field_names.iter())
|
||||
.map(|(field, field_name)| {
|
||||
fields_attrs_names.iter()
|
||||
.map(|&(field, attrs, name)| {
|
||||
(
|
||||
match field.ident {
|
||||
Some(name) => name.clone(),
|
||||
@ -1105,7 +1102,11 @@ fn deserialize_map(
|
||||
cx.span_bug(field.span, "struct contains unnamed fields")
|
||||
}
|
||||
},
|
||||
builder.expr().id(field_name),
|
||||
if attrs.skip_deserializing_field() {
|
||||
builder.expr().default()
|
||||
} else {
|
||||
builder.expr().id(name)
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
@ -1117,6 +1118,8 @@ fn deserialize_map(
|
||||
while let Some(key) = try!(visitor.visit_key()) {
|
||||
match key {
|
||||
$value_arms
|
||||
$skipped_arms
|
||||
$ignored_arm
|
||||
}
|
||||
}
|
||||
|
||||
@ -1127,3 +1130,18 @@ fn deserialize_map(
|
||||
Ok($result)
|
||||
}))
|
||||
}
|
||||
|
||||
fn fields_with_attrs<'a>(
|
||||
cx: &ExtCtxt,
|
||||
generics: &ast::Generics,
|
||||
ty: &P<ast::Ty>,
|
||||
fields: &'a [ast::StructField],
|
||||
is_enum: bool
|
||||
) -> Result<Vec<(&'a ast::StructField, attr::FieldAttrs)>, Error> {
|
||||
fields.iter()
|
||||
.map(|field| {
|
||||
let attrs = try!(attr::FieldAttrs::from_field(cx, &ty, generics, field, is_enum));
|
||||
Ok((field, attrs))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ struct TupleStruct(i32, i32, i32);
|
||||
struct Struct {
|
||||
a: i32,
|
||||
b: i32,
|
||||
#[serde(skip_deserializing)]
|
||||
c: i32,
|
||||
}
|
||||
|
||||
@ -60,6 +61,17 @@ macro_rules! declare_tests {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! declare_error_tests {
|
||||
($($name:ident<$target:ident> { $tokens:expr, $expected:expr, })+) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_de_tokens_error::<$target>($tokens, $expected);
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
declare_tests! {
|
||||
@ -524,7 +536,40 @@ declare_tests! {
|
||||
],
|
||||
}
|
||||
test_struct {
|
||||
Struct { a: 1, b: 2, c: 3 } => vec![
|
||||
Struct { a: 1, b: 2, c: 0 } => vec![
|
||||
Token::MapStart(Some(3)),
|
||||
Token::MapSep,
|
||||
Token::Str("a"),
|
||||
Token::I32(1),
|
||||
|
||||
Token::MapSep,
|
||||
Token::Str("b"),
|
||||
Token::I32(2),
|
||||
Token::MapEnd,
|
||||
],
|
||||
Struct { a: 1, b: 2, c: 0 } => vec![
|
||||
Token::StructStart("Struct", Some(3)),
|
||||
Token::StructSep,
|
||||
Token::Str("a"),
|
||||
Token::I32(1),
|
||||
|
||||
Token::StructSep,
|
||||
Token::Str("b"),
|
||||
Token::I32(2),
|
||||
Token::StructEnd,
|
||||
],
|
||||
Struct { a: 1, b: 2, c: 0 } => vec![
|
||||
Token::SeqStart(Some(3)),
|
||||
Token::SeqSep,
|
||||
Token::I32(1),
|
||||
|
||||
Token::SeqSep,
|
||||
Token::I32(2),
|
||||
Token::SeqEnd,
|
||||
],
|
||||
}
|
||||
test_struct_with_skip {
|
||||
Struct { a: 1, b: 2, c: 0 } => vec![
|
||||
Token::MapStart(Some(3)),
|
||||
Token::MapSep,
|
||||
Token::Str("a"),
|
||||
@ -537,9 +582,13 @@ declare_tests! {
|
||||
Token::MapSep,
|
||||
Token::Str("c"),
|
||||
Token::I32(3),
|
||||
|
||||
Token::MapSep,
|
||||
Token::Str("d"),
|
||||
Token::I32(4),
|
||||
Token::MapEnd,
|
||||
],
|
||||
Struct { a: 1, b: 2, c: 3 } => vec![
|
||||
Struct { a: 1, b: 2, c: 0 } => vec![
|
||||
Token::StructStart("Struct", Some(3)),
|
||||
Token::StructSep,
|
||||
Token::Str("a"),
|
||||
@ -552,6 +601,10 @@ declare_tests! {
|
||||
Token::StructSep,
|
||||
Token::Str("c"),
|
||||
Token::I32(3),
|
||||
|
||||
Token::StructSep,
|
||||
Token::Str("d"),
|
||||
Token::I32(4),
|
||||
Token::StructEnd,
|
||||
],
|
||||
}
|
||||
@ -638,12 +691,22 @@ fn test_net_ipaddr() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_error() {
|
||||
assert_de_tokens_error::<Enum>(
|
||||
declare_error_tests! {
|
||||
test_unknown_variant<Enum> {
|
||||
vec![
|
||||
Token::EnumUnit("Enum", "Foo"),
|
||||
],
|
||||
Error::UnknownVariantError("Foo".to_owned()),
|
||||
)
|
||||
}
|
||||
test_struct_seq_too_long<Struct> {
|
||||
vec![
|
||||
Token::SeqStart(Some(4)),
|
||||
Token::SeqSep, Token::I32(1),
|
||||
Token::SeqSep, Token::I32(2),
|
||||
Token::SeqSep, Token::I32(3),
|
||||
Token::SeqSep, Token::I32(4),
|
||||
Token::SeqEnd,
|
||||
],
|
||||
Error::UnexpectedToken(Token::SeqSep),
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user