diff --git a/README.md b/README.md index 8010784e..f2fe5bd3 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ Documentation is available at: * [serde\_json](https://serde-rs.github.io/serde/serde_json/serde_json/index.html) * [serde\_codegen](https://serde-rs.github.io/serde/serde_codegen/serde_codegen/index.html) -Using Serde -=========== +Using Serde with Nightly Rust and serde\_macros +=============================================== Here is a simple example that demonstrates how to use Serde by serializing and deserializing to JSON. Serde comes with some powerful code generation libraries @@ -76,6 +76,9 @@ When run, it produces: Point { x: 1, y: 2 } ``` +Using Serde with Stable Rust, syntex, and serde\_codegen +======================================================== + Stable Rust is a little more complicated because it does not yet support compiler plugins. Instead we need to use the code generation library [syntex](https://github.com/erickt/rust-syntex) for this: @@ -215,6 +218,20 @@ include!(concat!(env!("OUT_DIR"), "/main.rs")); The `src/main.rs.in` is the same as before. +Then to run with stable: + +``` +% cargo build +... +``` + +Or with nightly: + +```rust +% cargo build --features nightly --no-default-features +... +``` + Serialization without Macros ============================ @@ -311,6 +328,8 @@ as a named map. Its visitor uses a simple state machine to iterate through all the fields: ```rust +extern crate serde; + struct Point { x: i32, y: i32, @@ -479,6 +498,13 @@ deserializes an enum variant from a string. So for our `Point` example from before, we need to generate: ```rust +extern crate serde; + +struct Point { + x: i32, + y: i32, +} + enum PointField { X, Y, @@ -507,11 +533,7 @@ impl serde::Deserialize for PointField { deserializer.visit(PointFieldVisitor) } } -``` -This is then used in our actual deserializer: - -```rust impl serde::Deserialize for Point { fn deserialize(deserializer: &mut D) -> Result where D: serde::de::Deserializer @@ -557,6 +579,21 @@ impl serde::de::Visitor for PointVisitor { } ``` +Annotations +=========== + +`serde_codegen` and `serde_macros` support annotations that help to customize +how types are serialized. Here are the supported annotations: + +| Annotation | Function | +| ---------- | -------- | +| `#[serde(rename(json="name1", xml="name2"))` | Serialize this field with the given name for the given formats | +| `#[serde(default)` | If the value is not specified, use the `Default::default()` | +| `#[serde(rename="name")` | Serialize this field with the given name | +| `#[serde(skip_serializing)` | Do not serialize this value | +| `#[serde(skip_serializing_if_empty)` | Do not serialize this value if `$value.is_empty()` is `true` | +| `#[serde(skip_serializing_if_none)` | Do not serialize this value if `$value.is_none()` is `true` | + Serialization Formats Using Serde ================================= diff --git a/serde/Cargo.toml b/serde/Cargo.toml index 89f3ce12..c6a1f459 100644 --- a/serde/Cargo.toml +++ b/serde/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serde" -version = "0.6.0" +version = "0.6.1" authors = ["Erick Tryzelaar "] license = "MIT/Apache-2.0" description = "A generic serialization/deserialization framework" @@ -9,9 +9,9 @@ documentation = "https://serde-rs.github.io/serde/serde/serde/index.html" readme = "../README.md" keywords = ["serde", "serialization"] -[dependencies] -num = "*" +[dependencies.num] +version = "^0.1.27" +default-features = false [features] nightly = [] - diff --git a/serde/src/lib.rs b/serde/src/lib.rs index e6b8528f..380029c7 100644 --- a/serde/src/lib.rs +++ b/serde/src/lib.rs @@ -5,6 +5,9 @@ //! handshake protocol between serializers and serializees can be completely optimized away, //! leaving serde to perform roughly the same speed as a hand written serializer for a specific //! type. +//! +//! For a detailed tutorial on the different ways to use serde please check out the +//! [github repository](https://github.com/serde-rs/serde) #![doc(html_root_url="https://serde-rs.github.io/serde/serde")] #![cfg_attr(feature = "nightly", feature(collections, core, enumset, nonzero, step_trait, zero_one))] diff --git a/serde/src/ser/mod.rs b/serde/src/ser/mod.rs index b07e654a..7dc9b1de 100644 --- a/serde/src/ser/mod.rs +++ b/serde/src/ser/mod.rs @@ -337,7 +337,7 @@ pub trait SeqVisitor { } } -/// A trait that is used by a `Serializer` to iterate through a map. +/// A trait that is used by a `Serialize` to iterate through a map. pub trait MapVisitor { /// Serializes a map item in the serializer. /// diff --git a/serde_codegen/Cargo.toml b/serde_codegen/Cargo.toml index 81805a10..e5cc8ad9 100644 --- a/serde_codegen/Cargo.toml +++ b/serde_codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serde_codegen" -version = "0.5.3" +version = "0.6.1" authors = ["Erick Tryzelaar "] license = "MIT/Apache-2.0" description = "Macros to auto-generate implementations for the serde framework" @@ -15,12 +15,12 @@ nightly = ["quasi_macros"] with-syntex = ["quasi/with-syntex", "quasi_codegen", "quasi_codegen/with-syntex", "syntex", "syntex_syntax"] [build-dependencies] -quasi_codegen = { verision = "*", optional = true } -syntex = { version = "*", optional = true } +quasi_codegen = { verision = "^0.3.4", optional = true } +syntex = { version = "^0.18.0", optional = true } [dependencies] -aster = { version = "*", default-features = false } -quasi = { verision = "*", default-features = false } -quasi_macros = { version = "*", optional = true } -syntex = { version = "*", optional = true } -syntex_syntax = { version = "*", optional = true } +aster = { version = "^0.5.0", default-features = false } +quasi = { verision = "^0.3.5", default-features = false } +quasi_macros = { version = "^0.3.5", optional = true } +syntex = { version = "^0.17.0", optional = true } +syntex_syntax = { version = "^0.18.0", optional = true } diff --git a/serde_codegen/src/attr.rs b/serde_codegen/src/attr.rs index e4015546..af65f6d1 100644 --- a/serde_codegen/src/attr.rs +++ b/serde_codegen/src/attr.rs @@ -2,10 +2,14 @@ use std::collections::HashMap; use std::collections::HashSet; use syntax::ast; +use syntax::attr; use syntax::ext::base::ExtCtxt; use syntax::ptr::P; +use aster; + /// Represents field name information +#[derive(Debug)] pub enum FieldNames { Global(P), Format{ @@ -15,43 +19,16 @@ pub enum FieldNames { } /// Represents field attribute information +#[derive(Debug)] pub struct FieldAttrs { skip_serializing_field: bool, + skip_serializing_field_if_empty: bool, + skip_serializing_field_if_none: bool, names: FieldNames, use_default: bool, } impl FieldAttrs { - /// Create a FieldAttr with a single default field name - pub fn new( - skip_serializing_field: bool, - default_value: bool, - name: P, - ) -> FieldAttrs { - FieldAttrs { - skip_serializing_field: skip_serializing_field, - names: FieldNames::Global(name), - use_default: default_value, - } - } - - /// Create a FieldAttr with format specific field names - pub fn new_with_formats( - skip_serializing_field: bool, - default_value: bool, - default_name: P, - formats: HashMap, P>, - ) -> FieldAttrs { - FieldAttrs { - skip_serializing_field: skip_serializing_field, - names: FieldNames::Format { - formats: formats, - default: default_name, - }, - use_default: default_value, - } - } - /// Return a set of formats that the field has attributes for. pub fn formats(&self) -> HashSet> { match self.names { @@ -70,22 +47,21 @@ impl FieldAttrs { /// /// The resulting expression assumes that `S` refers to a type /// that implements `Serializer`. - pub fn serializer_key_expr(self, cx: &ExtCtxt) -> P { + pub fn serializer_key_expr(&self, cx: &ExtCtxt) -> P { match self.names { - FieldNames::Global(x) => x, - FieldNames::Format{formats, default} => { + FieldNames::Global(ref name) => name.clone(), + FieldNames::Format { ref formats, ref default } => { let arms = formats.iter() .map(|(fmt, lit)| { quote_arm!(cx, $fmt => { $lit }) }) .collect::>(); quote_expr!(cx, - { - match S::format() { - $arms - _ => { $default } - } - }) + match S::format() { + $arms + _ => { $default } + } + ) }, } } @@ -94,17 +70,17 @@ impl FieldAttrs { pub fn default_key_expr(&self) -> &P { match self.names { FieldNames::Global(ref expr) => expr, - FieldNames::Format{formats: _, ref default} => default + FieldNames::Format{formats: _, ref default} => default, } } /// Return the field name for the field in the specified format. pub fn key_expr(&self, format: &P) -> &P { match self.names { - FieldNames::Global(ref expr) => - expr, - FieldNames::Format{ref formats, ref default} => + FieldNames::Global(ref expr) => expr, + FieldNames::Format { ref formats, ref default } => { formats.get(format).unwrap_or(default) + } } } @@ -117,4 +93,153 @@ impl FieldAttrs { pub fn skip_serializing_field(&self) -> bool { self.skip_serializing_field } + + pub fn skip_serializing_field_if_empty(&self) -> bool { + self.skip_serializing_field_if_empty + } + + pub fn skip_serializing_field_if_none(&self) -> bool { + self.skip_serializing_field_if_none + } +} + +pub struct FieldAttrsBuilder<'a> { + builder: &'a aster::AstBuilder, + skip_serializing_field: bool, + skip_serializing_field_if_empty: bool, + skip_serializing_field_if_none: bool, + name: Option>, + format_rename: HashMap, P>, + use_default: bool, +} + +impl<'a> FieldAttrsBuilder<'a> { + pub fn new(builder: &'a aster::AstBuilder) -> FieldAttrsBuilder<'a> { + FieldAttrsBuilder { + builder: builder, + skip_serializing_field: false, + skip_serializing_field_if_empty: false, + skip_serializing_field_if_none: false, + name: None, + format_rename: HashMap::new(), + use_default: false, + } + } + + pub fn field(mut self, field: &ast::StructField) -> FieldAttrsBuilder<'a> { + match field.node.kind { + ast::NamedField(name, _) => { + self.name = Some(self.builder.expr().str(name)); + } + ast::UnnamedField(_) => { } + }; + + self.attrs(&field.node.attrs) + } + + pub fn attrs(self, attrs: &[ast::Attribute]) -> FieldAttrsBuilder<'a> { + attrs.iter().fold(self, FieldAttrsBuilder::attr) + } + + pub fn attr(self, attr: &ast::Attribute) -> FieldAttrsBuilder<'a> { + match attr.node.value.node { + ast::MetaList(ref name, ref items) if name == &"serde" => { + attr::mark_used(&attr); + items.iter().fold(self, FieldAttrsBuilder::meta_item) + } + _ => { + self + } + } + } + + pub fn meta_item(mut self, meta_item: &P) -> FieldAttrsBuilder<'a> { + match meta_item.node { + ast::MetaNameValue(ref name, ref lit) if name == &"rename" => { + let expr = self.builder.expr().build_lit(P(lit.clone())); + + self.name(expr) + } + ast::MetaList(ref name, ref items) if name == &"rename" => { + for item in items { + match item.node { + ast::MetaNameValue(ref name, ref lit) => { + let name = self.builder.expr().str(name); + let expr = self.builder.expr().build_lit(P(lit.clone())); + + self = self.format_rename(name, expr); + } + _ => { } + } + } + self + } + ast::MetaWord(ref name) if name == &"default" => { + self.default() + } + ast::MetaWord(ref name) if name == &"skip_serializing" => { + self.skip_serializing_field() + } + ast::MetaWord(ref name) if name == &"skip_serializing_if_empty" => { + self.skip_serializing_field_if_empty() + } + ast::MetaWord(ref name) if name == &"skip_serializing_if_none" => { + self.skip_serializing_field_if_none() + } + _ => { + // Ignore unknown meta variables for now. + self + } + } + } + + pub fn skip_serializing_field(mut self) -> FieldAttrsBuilder<'a> { + self.skip_serializing_field = true; + self + } + + pub fn skip_serializing_field_if_empty(mut self) -> FieldAttrsBuilder<'a> { + self.skip_serializing_field_if_empty = true; + self + } + + pub fn skip_serializing_field_if_none(mut self) -> FieldAttrsBuilder<'a> { + self.skip_serializing_field_if_none = true; + self + } + + pub fn name(mut self, name: P) -> FieldAttrsBuilder<'a> { + self.name = Some(name); + self + } + + pub fn format_rename(mut self, format: P, name: P) -> FieldAttrsBuilder<'a> { + self.format_rename.insert(format, name); + self + } + + pub fn default(mut self) -> FieldAttrsBuilder<'a> { + self.use_default = true; + self + } + + pub fn build(self) -> FieldAttrs { + let name = self.name.expect("here"); + let names = if self.format_rename.is_empty() { + FieldNames::Global(name) + } else { + FieldNames::Format { + formats: self.format_rename, + default: name, + } + }; + + FieldAttrs { + skip_serializing_field: self.skip_serializing_field, + skip_serializing_field_if_empty: self.skip_serializing_field_if_empty, + skip_serializing_field_if_none: self.skip_serializing_field_if_none, + names: names, + use_default: self.use_default, + } + } } diff --git a/serde_codegen/src/de.rs b/serde_codegen/src/de.rs index c903e706..4268326c 100644 --- a/serde_codegen/src/de.rs +++ b/serde_codegen/src/de.rs @@ -4,12 +4,11 @@ use aster; use syntax::ast::{ self, - Ident, - MetaItem, - Item, - Expr, - StructDef, EnumDef, + Expr, + Ident, + Item, + MetaItem, }; use syntax::codemap::Span; use syntax::ext::base::{Annotatable, ExtCtxt}; @@ -87,14 +86,14 @@ fn deserialize_body( ty: P, ) -> P { match item.node { - ast::ItemStruct(ref struct_def, _) => { + ast::ItemStruct(ref variant_data, _) => { deserialize_item_struct( cx, builder, item, impl_generics, ty, - struct_def, + variant_data, ) } ast::ItemEnum(ref enum_def, _) => { @@ -117,27 +116,17 @@ fn deserialize_item_struct( item: &Item, impl_generics: &ast::Generics, ty: P, - struct_def: &ast::StructDef, + variant_data: &ast::VariantData, ) -> P { - let mut named_fields = vec![]; - let mut unnamed_fields = 0; - - for field in struct_def.fields.iter() { - match field.node.kind { - ast::NamedField(name, _) => { named_fields.push(name); } - ast::UnnamedField(_) => { unnamed_fields += 1; } - } - } - - match (named_fields.is_empty(), unnamed_fields) { - (true, 0) => { + match *variant_data { + ast::VariantData::Unit(_) => { deserialize_unit_struct( cx, &builder, item.ident, ) } - (true, 1) => { + ast::VariantData::Tuple(ref fields, _) if fields.len() == 1 => { deserialize_newtype_struct( cx, &builder, @@ -146,29 +135,34 @@ fn deserialize_item_struct( ty, ) } - (true, _) => { + ast::VariantData::Tuple(ref fields, _) => { + if fields.iter().any(|field| !field.node.kind.is_unnamed()) { + cx.bug("tuple struct has named fields") + } + deserialize_tuple_struct( cx, &builder, item.ident, impl_generics, ty, - unnamed_fields, + fields.len(), ) } - (false, 0) => { + ast::VariantData::Struct(ref fields, _) => { + if fields.iter().any(|field| field.node.kind.is_unnamed()) { + cx.bug("struct has unnamed fields") + } + deserialize_struct( cx, &builder, item.ident, impl_generics, ty, - struct_def, + fields, ) } - (false, _) => { - cx.bug("struct has named and unnamed fields") - } } } @@ -422,9 +416,9 @@ fn deserialize_struct_as_seq( cx: &ExtCtxt, builder: &aster::AstBuilder, struct_path: ast::Path, - struct_def: &StructDef, + fields: &[ast::StructField], ) -> P { - let let_values: Vec> = (0 .. struct_def.fields.len()) + let let_values: Vec> = (0 .. fields.len()) .map(|i| { let name = builder.id(format!("__field{}", i)); quote_stmt!(cx, @@ -440,13 +434,13 @@ fn deserialize_struct_as_seq( let result = builder.expr().struct_path(struct_path) .with_id_exprs( - struct_def.fields.iter() + fields.iter() .enumerate() .map(|(i, field)| { ( match field.node.kind { ast::NamedField(name, _) => name.clone(), - ast::UnnamedField(_) => panic!("struct contains unnamed fields"), + ast::UnnamedField(_) => cx.bug("struct contains unnamed fields"), }, builder.expr().id(format!("__field{}", i)), ) @@ -469,7 +463,7 @@ fn deserialize_struct( type_ident: Ident, impl_generics: &ast::Generics, ty: P, - struct_def: &StructDef, + fields: &[ast::StructField], ) -> P { let where_clause = &impl_generics.where_clause; @@ -486,14 +480,14 @@ fn deserialize_struct( cx, builder, type_path.clone(), - struct_def + fields, ); let (field_visitor, fields_stmt, visit_map_expr) = deserialize_struct_visitor( cx, builder, - struct_def, - type_path.clone() + type_path.clone(), + fields, ); let type_name = builder.expr().str(type_ident); @@ -543,11 +537,13 @@ fn deserialize_item_enum( cx, builder, enum_def.variants.iter() - .map(|variant| - attr::FieldAttrs::new( - false, - true, - builder.expr().str(variant.node.name))) + .map(|variant| { + let expr = builder.expr().str(variant.node.name); + attr::FieldAttrsBuilder::new(builder) + .name(expr) + .default() + .build() + }) .collect() ); @@ -591,7 +587,7 @@ fn deserialize_item_enum( impl_generics, vec![deserializer_ty_param(builder)], vec![deserializer_ty_arg(builder)], - ); + ); quote_expr!(cx, { $variant_visitor @@ -626,20 +622,20 @@ fn deserialize_variant( ) -> P { let variant_ident = variant.node.name; - match variant.node.kind { - ast::TupleVariantKind(ref args) if args.is_empty() => { + match *variant.node.data { + ast::VariantData::Unit(_) => { quote_expr!(cx, { try!(visitor.visit_unit()); Ok($type_ident::$variant_ident) }) } - ast::TupleVariantKind(ref args) if args.len() == 1 => { + ast::VariantData::Tuple(ref args, _) if args.len() == 1 => { quote_expr!(cx, { let val = try!(visitor.visit_newtype()); Ok($type_ident::$variant_ident(val)) }) } - ast::TupleVariantKind(ref args) => { + ast::VariantData::Tuple(ref fields, _) => { deserialize_tuple_variant( cx, builder, @@ -647,10 +643,10 @@ fn deserialize_variant( variant_ident, generics, ty, - args.len(), + fields.len(), ) } - ast::StructVariantKind(ref struct_def) => { + ast::VariantData::Struct(ref fields, _) => { deserialize_struct_variant( cx, builder, @@ -658,7 +654,7 @@ fn deserialize_variant( variant_ident, generics, ty, - struct_def, + fields, ) } } @@ -681,7 +677,7 @@ fn deserialize_tuple_variant( generics, vec![deserializer_ty_param(builder)], vec![deserializer_ty_arg(builder)], - ); + ); let visit_seq_expr = deserialize_seq( cx, @@ -714,7 +710,7 @@ fn deserialize_struct_variant( variant_ident: ast::Ident, generics: &ast::Generics, ty: P, - struct_def: &ast::StructDef, + fields: &[ast::StructField], ) -> P { let where_clause = &generics.where_clause; @@ -727,14 +723,14 @@ fn deserialize_struct_variant( cx, builder, type_path.clone(), - struct_def + fields, ); let (field_visitor, fields_stmt, field_expr) = deserialize_struct_visitor( cx, builder, - struct_def, - type_path + type_path, + fields, ); let (visitor_item, visitor_ty, visitor_expr, visitor_generics) = @@ -743,7 +739,7 @@ fn deserialize_struct_variant( generics, vec![deserializer_ty_param(builder)], vec![deserializer_ty_arg(builder)], - ); + ); quote_expr!(cx, { $field_visitor @@ -789,7 +785,7 @@ fn deserialize_field_visitor( .enum_("__Field") .with_variants( field_idents.iter().map(|field_ident| { - builder.variant(field_ident).tuple().build() + builder.variant(field_ident).unit() }) ) .build(); @@ -925,25 +921,25 @@ fn deserialize_field_visitor( fn deserialize_struct_visitor( cx: &ExtCtxt, builder: &aster::AstBuilder, - struct_def: &ast::StructDef, struct_path: ast::Path, + fields: &[ast::StructField], ) -> (Vec>, P, P) { let field_visitor = deserialize_field_visitor( cx, builder, - field::struct_field_attrs(cx, builder, struct_def), + field::struct_field_attrs(cx, builder, fields), ); let visit_map_expr = deserialize_map( cx, builder, struct_path, - struct_def, + fields, ); let fields_expr = builder.expr().addr_of().slice() .with_exprs( - struct_def.fields.iter() + fields.iter() .map(|field| { match field.node.kind { ast::NamedField(name, _) => builder.expr().str(name), @@ -964,10 +960,10 @@ fn deserialize_map( cx: &ExtCtxt, builder: &aster::AstBuilder, struct_path: ast::Path, - struct_def: &StructDef, + fields: &[ast::StructField], ) -> P { // Create the field names for the fields. - let field_names: Vec = (0 .. struct_def.fields.len()) + let field_names: Vec = (0 .. fields.len()) .map(|i| builder.id(format!("__field{}", i))) .collect(); @@ -988,7 +984,7 @@ fn deserialize_map( .collect(); let extract_values: Vec> = field_names.iter() - .zip(field::struct_field_attrs(cx, builder, struct_def).iter()) + .zip(field::struct_field_attrs(cx, builder, fields).iter()) .map(|(field_name, field_attr)| { let missing_expr = if field_attr.use_default() { quote_expr!(cx, ::std::default::Default::default()) @@ -1025,7 +1021,7 @@ fn deserialize_map( let result = builder.expr().struct_path(struct_path) .with_id_exprs( - struct_def.fields.iter() + fields.iter() .zip(field_names.iter()) .map(|(field, field_name)| { ( diff --git a/serde_codegen/src/field.rs b/serde_codegen/src/field.rs index 6f10eacf..bf1a522b 100644 --- a/serde_codegen/src/field.rs +++ b/serde_codegen/src/field.rs @@ -1,147 +1,17 @@ -use std::collections::HashMap; +use syntax::ast; +use syntax::ext::base::ExtCtxt; use aster; - -use syntax::ast; -use syntax::attr; -use syntax::ext::base::ExtCtxt; -use syntax::ptr::P; - -use attr::FieldAttrs; - -enum Rename<'a> { - None, - Global(&'a ast::Lit), - Format(HashMap, &'a ast::Lit>) -} - -fn rename<'a>( - builder: &aster::AstBuilder, - mi: &'a ast::MetaItem, - ) -> Option> -{ - match mi.node { - ast::MetaNameValue(ref n, ref lit) => { - if n == &"rename" { - Some(Rename::Global(lit)) - } else { - None - } - }, - ast::MetaList(ref n, ref items) => { - if n == &"rename" { - let mut m = HashMap::new(); - m.extend( - items.iter() - .filter_map( - |item| - match item.node { - ast::MetaNameValue(ref n, ref lit) => - Some((builder.expr().str(n), - lit)), - _ => None - })); - Some(Rename::Format(m)) - } else { - None - } - }, - _ => None - } -} - -fn default_value(mi: &ast::MetaItem) -> bool { - if let ast::MetaItem_::MetaWord(ref n) = mi.node { - n == &"default" - } else { - false - } -} - -fn skip_serializing_field(mi: &ast::MetaItem) -> bool { - if let ast::MetaItem_::MetaWord(ref n) = mi.node { - n == &"skip_serializing" - } else { - false - } -} - -fn field_attrs<'a>( - builder: &aster::AstBuilder, - field: &'a ast::StructField, -) -> (Rename<'a>, bool, bool) { - field.node.attrs.iter() - .find(|sa| { - if let ast::MetaList(ref n, _) = sa.node.value.node { - n == &"serde" - } else { - false - } - }) - .and_then(|sa| { - if let ast::MetaList(_, ref vals) = sa.node.value.node { - attr::mark_used(&sa); - Some(( - vals.iter() - .fold(None, |v, mi| v.or(rename(builder, mi))) - .unwrap_or(Rename::None), - vals.iter().any(|mi| default_value(mi)), - vals.iter().any(|mi| skip_serializing_field(mi)), - )) - } else { - Some((Rename::None, false, false)) - } - }) - .unwrap_or((Rename::None, false, false)) -} +use attr::{FieldAttrs, FieldAttrsBuilder}; pub fn struct_field_attrs( - cx: &ExtCtxt, + _cx: &ExtCtxt, builder: &aster::AstBuilder, - struct_def: &ast::StructDef, + fields: &[ast::StructField], ) -> Vec { - struct_def.fields.iter() + fields.iter() .map(|field| { - match field_attrs(builder, field) { - (Rename::Global(rename), default_value, skip_serializing_field) => - FieldAttrs::new( - skip_serializing_field, - default_value, - builder.expr().build_lit(P(rename.clone()))), - (Rename::Format(renames), default_value, skip_serializing_field) => { - let mut res = HashMap::new(); - res.extend( - renames.into_iter() - .map(|(k,v)| - (k, builder.expr().build_lit(P(v.clone()))))); - FieldAttrs::new_with_formats( - skip_serializing_field, - default_value, - default_field_name(cx, builder, field.node.kind), - res) - }, - (Rename::None, default_value, skip_serializing_field) => { - FieldAttrs::new( - skip_serializing_field, - default_value, - default_field_name(cx, builder, field.node.kind)) - } - } + FieldAttrsBuilder::new(builder).field(field).build() }) .collect() } - -fn default_field_name( - cx: &ExtCtxt, - builder: &aster::AstBuilder, - kind: ast::StructFieldKind, -) -> P { - match kind { - ast::NamedField(name, _) => { - builder.expr().str(name) - } - ast::UnnamedField(_) => { - cx.bug("struct has named and unnamed fields") - } - } -} diff --git a/serde_codegen/src/ser.rs b/serde_codegen/src/ser.rs index a749926f..4f0d6845 100644 --- a/serde_codegen/src/ser.rs +++ b/serde_codegen/src/ser.rs @@ -5,7 +5,6 @@ use syntax::ast::{ MetaItem, Item, Expr, - StructDef, }; use syntax::ast; use syntax::codemap::Span; @@ -82,14 +81,14 @@ fn serialize_body( ty: P, ) -> P { match item.node { - ast::ItemStruct(ref struct_def, _) => { + ast::ItemStruct(ref variant_data, _) => { serialize_item_struct( cx, builder, item, impl_generics, ty, - struct_def, + variant_data, ) } ast::ItemEnum(ref enum_def, _) => { @@ -112,57 +111,51 @@ fn serialize_item_struct( item: &Item, impl_generics: &ast::Generics, ty: P, - struct_def: &ast::StructDef, + variant_data: &ast::VariantData, ) -> P { - let mut named_fields = vec![]; - let mut unnamed_fields = 0; - - for field in struct_def.fields.iter() { - match field.node.kind { - ast::NamedField(name, _) => { named_fields.push(name); } - ast::UnnamedField(_) => { unnamed_fields += 1; } - } - } - - match (named_fields.is_empty(), unnamed_fields) { - (true, 0) => { + match *variant_data { + ast::VariantData::Unit(_) => { serialize_unit_struct( cx, &builder, item.ident, ) } - (true, 1) => { + ast::VariantData::Tuple(ref fields, _) if fields.len() == 1 => { serialize_newtype_struct( cx, &builder, item.ident, ) } - (true, _) => { + ast::VariantData::Tuple(ref fields, _) => { + if fields.iter().any(|field| !field.node.kind.is_unnamed()) { + cx.bug("tuple struct has named fields") + } + serialize_tuple_struct( cx, &builder, item.ident, impl_generics, ty, - unnamed_fields, + fields.len(), ) } - (false, 0) => { + ast::VariantData::Struct(ref fields, _) => { + if fields.iter().any(|field| field.node.kind.is_unnamed()) { + cx.bug("struct has unnamed fields") + } + serialize_struct( cx, &builder, item.ident, impl_generics, ty, - struct_def, - named_fields, + fields, ) } - (false, _) => { - cx.bug("struct has named and unnamed fields") - } } } @@ -225,8 +218,7 @@ fn serialize_struct( type_ident: Ident, impl_generics: &ast::Generics, ty: P, - struct_def: &StructDef, - fields: Vec, + fields: &[ast::StructField], ) -> P { let (visitor_struct, visitor_impl) = serialize_struct_visitor( cx, @@ -236,9 +228,12 @@ fn serialize_struct( .ref_() .lifetime("'__a") .build_ty(ty.clone()), - struct_def, + fields, impl_generics, - fields.iter().map(|field| quote_expr!(cx, &self.value.$field)), + fields.iter().map(|field| { + let name = field.node.ident().expect("struct has unnamed field"); + quote_expr!(cx, &self.value.$name) + }) ); let type_name = builder.expr().str(type_ident); @@ -297,8 +292,8 @@ fn serialize_variant( let variant_ident = variant.node.name; let variant_name = builder.expr().str(variant_ident); - match variant.node.kind { - ast::TupleVariantKind(ref args) if args.is_empty() => { + match *variant.node.data { + ast::VariantData::Unit(_) => { let pat = builder.pat().enum_() .id(type_ident).id(variant_ident).build() .build(); @@ -314,7 +309,7 @@ fn serialize_variant( } ) }, - ast::TupleVariantKind(ref args) if args.len() == 1 => { + ast::VariantData::Tuple(ref fields, _) if fields.len() == 1 => { let field = builder.id("__simple_value"); let field = builder.pat().ref_id(field); let pat = builder.pat().enum_() @@ -333,14 +328,17 @@ fn serialize_variant( } ) }, - ast::TupleVariantKind(ref args) => { - let fields: Vec = (0 .. args.len()) + ast::VariantData::Tuple(ref fields, _) => { + let field_names: Vec = (0 .. fields.len()) .map(|i| builder.id(format!("__field{}", i))) .collect(); let pat = builder.pat().enum_() .id(type_ident).id(variant_ident).build() - .with_pats(fields.iter().map(|field| builder.pat().ref_id(field))) + .with_pats( + field_names.iter() + .map(|field| builder.pat().ref_id(field)) + ) .build(); let expr = serialize_tuple_variant( @@ -351,22 +349,22 @@ fn serialize_variant( variant_name, generics, ty, - args, fields, + field_names, ); quote_arm!(cx, $pat => { $expr }) } - ast::StructVariantKind(ref struct_def) => { - let fields: Vec<_> = (0 .. struct_def.fields.len()) + ast::VariantData::Struct(ref fields, _) => { + let field_names: Vec<_> = (0 .. fields.len()) .map(|i| builder.id(format!("__field{}", i))) .collect(); let pat = builder.pat().struct_() .id(type_ident).id(variant_ident).build() .with_pats( - fields.iter() - .zip(struct_def.fields.iter()) + field_names.iter() + .zip(fields.iter()) .map(|(id, field)| { let name = match field.node.kind { ast::NamedField(name, _) => name, @@ -388,8 +386,8 @@ fn serialize_variant( variant_name, generics, ty, - struct_def, fields, + field_names, ); quote_arm!(cx, $pat => { $expr }) @@ -405,16 +403,16 @@ fn serialize_tuple_variant( variant_name: P, generics: &ast::Generics, structure_ty: P, - args: &[ast::VariantArg], - fields: Vec, + fields: &[ast::StructField], + field_names: Vec, ) -> P { let variant_ty = builder.ty().tuple() .with_tys( - args.iter().map(|arg| { + fields.iter().map(|field| { builder.ty() .ref_() .lifetime("'__a") - .build_ty(arg.ty.clone()) + .build_ty(field.node.ty.clone()) }) ) .build(); @@ -424,13 +422,13 @@ fn serialize_tuple_variant( builder, structure_ty.clone(), variant_ty, - args.len(), + fields.len(), generics, ); let value_expr = builder.expr().tuple() .with_exprs( - fields.iter().map(|field| { + field_names.iter().map(|field| { builder.expr().id(field) }) ) @@ -455,12 +453,12 @@ fn serialize_struct_variant( variant_name: P, generics: &ast::Generics, structure_ty: P, - struct_def: &ast::StructDef, - fields: Vec, + fields: &[ast::StructField], + field_names: Vec, ) -> P { let value_ty = builder.ty().tuple() .with_tys( - struct_def.fields.iter().map(|field| { + fields.iter().map(|field| { builder.ty() .ref_() .lifetime("'__a") @@ -471,7 +469,7 @@ fn serialize_struct_variant( let value_expr = builder.expr().tuple() .with_exprs( - fields.iter().map(|field| { + field_names.iter().map(|field| { builder.expr().id(field) }) ) @@ -482,9 +480,9 @@ fn serialize_struct_variant( builder, structure_ty.clone(), value_ty, - struct_def, + fields, generics, - (0 .. fields.len()).map(|i| { + (0 .. field_names.len()).map(|i| { builder.expr() .tup_field(i) .field("value").self_() @@ -574,29 +572,37 @@ fn serialize_struct_visitor( builder: &aster::AstBuilder, structure_ty: P, variant_ty: P, - struct_def: &StructDef, + fields: &[ast::StructField], generics: &ast::Generics, value_exprs: I, ) -> (P, P) where I: Iterator>, { - let field_attrs = struct_field_attrs(cx, builder, struct_def); + let value_exprs = value_exprs.collect::>(); - let len = struct_def.fields.len() - field_attrs.iter() - .fold(0, |sum, field| { - sum + if field.skip_serializing_field() { 1 } else { 0 } - }); + let field_attrs = struct_field_attrs(cx, builder, fields); - let arms: Vec = field_attrs.into_iter() - .zip(value_exprs) + let arms: Vec = field_attrs.iter() + .zip(value_exprs.iter()) .filter(|&(ref field, _)| !field.skip_serializing_field()) .enumerate() - .map(|(i, (field, value_expr))| { + .map(|(i, (ref field, value_expr))| { let key_expr = field.serializer_key_expr(cx); + + let stmt = if field.skip_serializing_field_if_empty() { + quote_stmt!(cx, if ($value_expr).is_empty() { continue; }) + } else if field.skip_serializing_field_if_none() { + quote_stmt!(cx, if ($value_expr).is_none() { continue; }) + } else { + quote_stmt!(cx, {}) + }; + quote_arm!(cx, $i => { self.state += 1; - Ok( + $stmt + + return Ok( Some( try!( serializer.visit_struct_elt( @@ -605,7 +611,7 @@ fn serialize_struct_visitor( ) ) ) - ) + ); } ) }) @@ -622,6 +628,21 @@ fn serialize_struct_visitor( .strip_bounds() .build(); + let len = field_attrs.iter() + .zip(value_exprs.iter()) + .map(|(field, value_expr)| { + if field.skip_serializing_field() { + quote_expr!(cx, 0) + } else if field.skip_serializing_field_if_empty() { + quote_expr!(cx, if ($value_expr).is_empty() { 0 } else { 1 }) + } else if field.skip_serializing_field_if_none() { + quote_expr!(cx, if ($value_expr).is_none() { 0 } else { 1 }) + } else { + quote_expr!(cx, 1) + } + }) + .fold(quote_expr!(cx, 0), |sum, expr| quote_expr!(cx, $sum + $expr)); + ( quote_item!(cx, struct Visitor $visitor_impl_generics $where_clause { @@ -640,9 +661,11 @@ fn serialize_struct_visitor( fn visit(&mut self, serializer: &mut S) -> ::std::result::Result, S::Error> where S: ::serde::ser::Serializer, { - match self.state { - $arms - _ => Ok(None) + loop { + match self.state { + $arms + _ => { return Ok(None); } + } } } diff --git a/serde_macros/Cargo.toml b/serde_macros/Cargo.toml index 1be136c8..6bbfdd4c 100644 --- a/serde_macros/Cargo.toml +++ b/serde_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serde_macros" -version = "0.5.3" +version = "0.6.1" authors = ["Erick Tryzelaar "] license = "MIT/Apache-2.0" description = "Macros to auto-generate implementations for the serde framework" @@ -16,6 +16,6 @@ plugin = true serde_codegen = { version = "*", path = "../serde_codegen", default-features = false, features = ["nightly"] } [dev-dependencies] -num = "*" -rustc-serialize = "*" +num = "^0.1.27" +rustc-serialize = "^0.3.16" serde = { version = "*", path = "../serde", features = ["nightly"] } diff --git a/serde_tests/tests/test_annotations.rs b/serde_tests/tests/test_annotations.rs index 65508658..87e87960 100644 --- a/serde_tests/tests/test_annotations.rs +++ b/serde_tests/tests/test_annotations.rs @@ -39,6 +39,20 @@ struct SkipSerializingFields { b: A, } +#[derive(Debug, PartialEq, Deserialize, Serialize)] +struct SkipSerializingIfEmptyFields { + a: i8, + #[serde(skip_serializing_if_empty, default)] + b: Vec, +} + +#[derive(Debug, PartialEq, Deserialize, Serialize)] +struct SkipSerializingIfNoneFields { + a: i8, + #[serde(skip_serializing_if_none, default)] + b: Option, +} + #[test] fn test_default() { assert_de_tokens( @@ -169,3 +183,161 @@ fn test_skip_serializing_fields() { ] ); } + +#[test] +fn test_skip_serializing_fields_if_empty() { + assert_ser_tokens( + &SkipSerializingIfEmptyFields:: { + a: 1, + b: vec![], + }, + &[ + Token::StructStart("SkipSerializingIfEmptyFields", Some(1)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapEnd, + ] + ); + + assert_de_tokens( + &SkipSerializingIfEmptyFields:: { + a: 1, + b: vec![], + }, + vec![ + Token::StructStart("SkipSerializingIfEmptyFields", Some(1)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapEnd, + ] + ); + + assert_ser_tokens( + &SkipSerializingIfEmptyFields { + a: 1, + b: vec![2], + }, + &[ + Token::StructStart("SkipSerializingIfEmptyFields", Some(2)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapSep, + Token::Str("b"), + Token::SeqStart(Some(1)), + Token::SeqSep, + Token::I32(2), + Token::SeqEnd, + + Token::MapEnd, + ] + ); + + assert_de_tokens( + &SkipSerializingIfEmptyFields { + a: 1, + b: vec![2], + }, + vec![ + Token::StructStart("SkipSerializingIfEmptyFields", Some(2)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapSep, + Token::Str("b"), + Token::SeqStart(Some(1)), + Token::SeqSep, + Token::I32(2), + Token::SeqEnd, + + Token::MapEnd, + ] + ); +} + +#[test] +fn test_skip_serializing_fields_if_none() { + assert_ser_tokens( + &SkipSerializingIfNoneFields:: { + a: 1, + b: None, + }, + &[ + Token::StructStart("SkipSerializingIfNoneFields", Some(1)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapEnd, + ] + ); + + assert_de_tokens( + &SkipSerializingIfNoneFields:: { + a: 1, + b: None, + }, + vec![ + Token::StructStart("SkipSerializingIfNoneFields", Some(1)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapEnd, + ] + ); + + assert_ser_tokens( + &SkipSerializingIfNoneFields { + a: 1, + b: Some(2), + }, + &[ + Token::StructStart("SkipSerializingIfNoneFields", Some(2)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapSep, + Token::Str("b"), + Token::Option(true), + Token::I32(2), + + Token::MapEnd, + ] + ); + + assert_de_tokens( + &SkipSerializingIfNoneFields { + a: 1, + b: Some(2), + }, + vec![ + Token::StructStart("SkipSerializingIfNoneFields", Some(2)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapSep, + Token::Str("b"), + Token::Option(true), + Token::I32(2), + + Token::MapEnd, + ] + ); +}