From 87393b61bb2b921c758dbc6c5ae0317a13b5133d Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 6 Mar 2016 23:27:12 -0800 Subject: [PATCH] feat(codegen) skip_deserializing --- README.md | 1 + serde_codegen/src/attr.rs | 11 +++ serde_codegen/src/de.rs | 184 +++++++++++++++++++---------------- serde_tests/tests/test_de.rs | 75 ++++++++++++-- 4 files changed, 182 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 64fa1da8..02a73a16 100644 --- a/README.md +++ b/README.md @@ -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, &mut S) -> Result<(), S::Error> where S: Serializer` to serialize this value | | `#[serde(deserialize_with="$path")]` | Call a function `fn(&mut D) -> Result where D: Deserializer` to deserialize this value | diff --git a/serde_codegen/src/attr.rs b/serde_codegen/src/attr.rs index 24776f1b..ff1f2f5e 100644 --- a/serde_codegen/src/attr.rs +++ b/serde_codegen/src/attr.rs @@ -181,6 +181,7 @@ impl VariantAttrs { pub struct FieldAttrs { name: Name, skip_serializing_field: bool, + skip_deserializing_field: bool, skip_serializing_field_if: Option>, default_expr_if_missing: Option>, serialize_with: Option>, @@ -204,6 +205,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, @@ -249,6 +251,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( @@ -325,6 +332,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> { self.skip_serializing_field_if.as_ref() } diff --git a/serde_codegen/src/de.rs b/serde_codegen/src/de.rs index c21c9677..2c5081a3 100644 --- a/serde_codegen/src/de.rs +++ b/serde_codegen/src/de.rs @@ -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, 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, - generics: &ast::Generics, - fields: &[ast::StructField], + fields: &[(&ast::StructField, attr::FieldAttrs)], container_attrs: &attr::ContainerAttrs, - is_enum: bool, ) -> Result<(Vec>, ast::Stmt, P), 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, - generics: &ast::Generics, - fields: &[ast::StructField], + fields: &[(&ast::StructField, attr::FieldAttrs)], container_attrs: &attr::ContainerAttrs, - is_enum: bool, ) -> Result, Error> { // Create the field names for the fields. - let field_names: Vec = (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::>(); + + // Declare each field that will be deserialized. + let let_values: Vec = 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 = 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::>(); - 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::>(); + + // 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, + fields: &'a [ast::StructField], + is_enum: bool +) -> Result, Error> { + fields.iter() + .map(|field| { + let attrs = try!(attr::FieldAttrs::from_field(cx, &ty, generics, field, is_enum)); + Ok((field, attrs)) + }) + .collect() +} diff --git a/serde_tests/tests/test_de.rs b/serde_tests/tests/test_de.rs index ab5a79ca..f5bc158b 100644 --- a/serde_tests/tests/test_de.rs +++ b/serde_tests/tests/test_de.rs @@ -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::( +declare_error_tests! { + test_unknown_variant { vec![ Token::EnumUnit("Enum", "Foo"), ], Error::UnknownVariantError("Foo".to_owned()), - ) + } + test_struct_seq_too_long { + 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), + } }