diff --git a/serde_codegen/src/attr.rs b/serde_codegen/src/attr.rs index 984fe623..387650c8 100644 --- a/serde_codegen/src/attr.rs +++ b/serde_codegen/src/attr.rs @@ -53,14 +53,16 @@ impl ContainerAttrs { #[derive(Debug)] pub struct VariantAttrs { ident: ast::Ident, - name: Option, + serialize_name: Option, + deserialize_name: Option, } impl VariantAttrs { pub fn from_variant(cx: &ExtCtxt, variant: &ast::Variant) -> Result { let mut variant_attrs = VariantAttrs { ident: variant.node.name, - name: None, + serialize_name: None, + deserialize_name: None, }; for meta_items in variant.node.attrs.iter().filter_map(get_serde_meta_items) { @@ -68,7 +70,15 @@ impl VariantAttrs { match meta_item.node { // Parse `#[serde(rename="foo")]` ast::MetaNameValue(ref name, ref lit) if name == &"rename" => { - variant_attrs.name = Some(lit.clone()); + variant_attrs.serialize_name = Some(lit.clone()); + variant_attrs.deserialize_name = Some(lit.clone()); + } + + // Parse `#[serde(rename(serialize="foo", deserialize="bar"))]` + ast::MetaList(ref name, ref meta_items) if name == &"rename" => { + let (ser_name, de_name) = try!(get_renames(cx, meta_items)); + variant_attrs.serialize_name = ser_name; + variant_attrs.deserialize_name = de_name; } _ => { @@ -92,8 +102,16 @@ impl VariantAttrs { } /// Return the field name for the field when serializing. - pub fn name_expr(&self) -> P { - match self.name { + pub fn serialize_name_expr(&self) -> P { + match self.serialize_name { + Some(ref name) => AstBuilder::new().expr().build_lit(P(name.clone())), + None => self.ident_expr(), + } + } + + /// Return the field name for the field when serializing. + pub fn deserialize_name_expr(&self) -> P { + match self.deserialize_name { Some(ref name) => AstBuilder::new().expr().build_lit(P(name.clone())), None => self.ident_expr(), } @@ -104,7 +122,8 @@ impl VariantAttrs { #[derive(Debug)] pub struct FieldAttrs { ident: ast::Ident, - name: Option, + serialize_name: Option, + deserialize_name: Option, skip_serializing_field: bool, skip_serializing_field_if_empty: bool, skip_serializing_field_if_none: bool, @@ -119,38 +138,50 @@ impl FieldAttrs { None => { cx.span_bug(field.span, "struct field has no name?") } }; - let mut skip_serializing_field = false; - let mut skip_serializing_field_if_empty = false; - let mut skip_serializing_field_if_none = false; - let mut field_name = None; - let mut use_default = false; + let mut field_attrs = FieldAttrs { + ident: field_ident, + serialize_name: None, + deserialize_name: None, + skip_serializing_field: false, + skip_serializing_field_if_empty: false, + skip_serializing_field_if_none: false, + use_default: false, + }; for meta_items in field.node.attrs.iter().filter_map(get_serde_meta_items) { for meta_item in meta_items { match meta_item.node { // Parse `#[serde(rename="foo")]` ast::MetaNameValue(ref name, ref lit) if name == &"rename" => { - field_name = Some(lit.clone()); + field_attrs.serialize_name = Some(lit.clone()); + field_attrs.deserialize_name = Some(lit.clone()); + } + + // Parse `#[serde(rename(serialize="foo", deserialize="bar"))]` + ast::MetaList(ref name, ref meta_items) if name == &"rename" => { + let (ser_name, de_name) = try!(get_renames(cx, meta_items)); + field_attrs.serialize_name = ser_name; + field_attrs.deserialize_name = de_name; } // Parse `#[serde(default)]` ast::MetaWord(ref name) if name == &"default" => { - use_default = true; + field_attrs.use_default = true; } // Parse `#[serde(skip_serializing)]` ast::MetaWord(ref name) if name == &"skip_serializing" => { - skip_serializing_field = true; + field_attrs.skip_serializing_field = true; } // Parse `#[serde(skip_serializing_if_none)]` ast::MetaWord(ref name) if name == &"skip_serializing_if_none" => { - skip_serializing_field_if_none = true; + field_attrs.skip_serializing_field_if_none = true; } // Parse `#[serde(skip_serializing_if_empty)]` ast::MetaWord(ref name) if name == &"skip_serializing_if_empty" => { - skip_serializing_field_if_empty = true; + field_attrs.skip_serializing_field_if_empty = true; } _ => { @@ -165,14 +196,7 @@ impl FieldAttrs { } } - Ok(FieldAttrs { - ident: field_ident, - name: field_name, - skip_serializing_field: skip_serializing_field, - skip_serializing_field_if_empty: skip_serializing_field_if_empty, - skip_serializing_field_if_none: skip_serializing_field_if_none, - use_default: use_default, - }) + Ok(field_attrs) } /// Return the string expression of the field ident. @@ -181,8 +205,16 @@ impl FieldAttrs { } /// Return the field name for the field when serializing. - pub fn name_expr(&self) -> P { - match self.name { + pub fn serialize_name_expr(&self) -> P { + match self.serialize_name { + Some(ref name) => AstBuilder::new().expr().build_lit(P(name.clone())), + None => self.ident_expr(), + } + } + + /// Return the field name for the field when deserializing. + pub fn deserialize_name_expr(&self) -> P { + match self.deserialize_name { Some(ref name) => AstBuilder::new().expr().build_lit(P(name.clone())), None => self.ident_expr(), } @@ -215,6 +247,35 @@ pub fn get_struct_field_attrs(cx: &ExtCtxt, .collect() } +fn get_renames(cx: &ExtCtxt, + items: &[P]) -> Result<(Option, Option), Error> { + let mut ser_name = None; + let mut de_name = None; + + for item in items { + match item.node { + ast::MetaNameValue(ref name, ref lit) if name == &"serialize" => { + ser_name = Some(lit.clone()); + } + + ast::MetaNameValue(ref name, ref lit) if name == &"deserialize" => { + de_name = Some(lit.clone()); + } + + _ => { + cx.span_err( + item.span, + &format!("unknown rename attribute `{}`", + meta_item_to_string(item))); + + return Err(Error); + } + } + } + + Ok((ser_name, de_name)) +} + fn get_serde_meta_items(attr: &ast::Attribute) -> Option<&[P]> { match attr.node.value.node { ast::MetaList(ref name, ref items) if name == &"serde" => { diff --git a/serde_codegen/src/de.rs b/serde_codegen/src/de.rs index 66336f80..0e924fa5 100644 --- a/serde_codegen/src/de.rs +++ b/serde_codegen/src/de.rs @@ -554,7 +554,7 @@ fn deserialize_item_enum( enum_def.variants.iter() .map(|variant| { let attrs = try!(attr::VariantAttrs::from_variant(cx, variant)); - Ok(attrs.name_expr()) + Ok(attrs.deserialize_name_expr()) }) .collect() ), @@ -924,7 +924,7 @@ fn deserialize_struct_visitor( fields.iter() .map(|field| { let attrs = try!(attr::FieldAttrs::from_field(cx, field)); - Ok(attrs.name_expr()) + Ok(attrs.deserialize_name_expr()) }) .collect() ), @@ -1007,7 +1007,7 @@ fn deserialize_map( let missing_expr = if field_attr.use_default() { quote_expr!(cx, ::std::default::Default::default()) } else { - let name = field_attr.name_expr(); + let name = field_attr.ident_expr(); quote_expr!(cx, try!(visitor.missing_field($name))) }; diff --git a/serde_codegen/src/ser.rs b/serde_codegen/src/ser.rs index ad3dc002..07ac9174 100644 --- a/serde_codegen/src/ser.rs +++ b/serde_codegen/src/ser.rs @@ -311,7 +311,7 @@ fn serialize_variant( let type_name = builder.expr().str(type_ident); let variant_ident = variant.node.name; let variant_attrs = try!(attr::VariantAttrs::from_variant(cx, variant)); - let variant_name = variant_attrs.name_expr(); + let variant_name = variant_attrs.serialize_name_expr(); match variant.node.data { ast::VariantData::Unit(_) => { @@ -613,7 +613,7 @@ fn serialize_struct_visitor( .filter(|&(ref field, _)| !field.skip_serializing_field()) .enumerate() .map(|(i, (ref field, value_expr))| { - let key_expr = field.name_expr(); + let key_expr = field.serialize_name_expr(); let stmt = if field.skip_serializing_field_if_empty() { quote_stmt!(cx, if ($value_expr).is_empty() { continue; }) diff --git a/serde_tests/tests/test_annotations.rs b/serde_tests/tests/test_annotations.rs index f129787c..a570b691 100644 --- a/serde_tests/tests/test_annotations.rs +++ b/serde_tests/tests/test_annotations.rs @@ -30,11 +30,39 @@ struct Rename { } #[derive(Debug, PartialEq, Serialize, Deserialize)] -enum RenameEnumVariant { +enum RenameVariantVariant { #[serde(rename="bruce_wayne")] Batman, } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +struct RenameStructSerializeDeserialize { + a1: i32, + #[serde(rename(serialize="a4", deserialize="a5"))] + a2: i32, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +enum RenameEnum { + #[serde(rename="bruce_wayne")] + Batman, +} + +#[derive(Debug, PartialEq, Deserialize, Serialize)] +enum RenameVariantSerializeDeserialize { + Map { + a: i8, + #[serde(rename(serialize="c", deserialize="d"))] + b: A, + }, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +enum RenameEnumVariantSerializeDeserialize { + #[serde(rename(serialize="dick_grayson", deserialize="jason_todd"))] + Robin, +} + #[derive(Debug, PartialEq, Deserialize, Serialize)] struct SkipSerializingFields { a: i8, @@ -165,9 +193,106 @@ fn test_rename() { #[test] fn test_rename_enum_variant() { assert_tokens( - &RenameEnumVariant::Batman, + &RenameVariantVariant::Batman, vec![ - Token::EnumUnit("RenameEnumVariant", "bruce_wayne"), + Token::EnumUnit("RenameVariantVariant", "bruce_wayne"), + ] + ); +} + +#[test] +fn test_rename_struct_serialize_deserialize() { + assert_ser_tokens( + &RenameStructSerializeDeserialize { a1: 1, a2: 2 }, + &[ + Token::StructStart("RenameStructSerializeDeserialize", Some(2)), + + Token::MapSep, + Token::Str("a1"), + Token::I32(1), + + Token::MapSep, + Token::Str("a4"), + Token::I32(2), + + Token::MapEnd, + ] + ); + + assert_de_tokens( + &RenameStructSerializeDeserialize { a1: 1, a2: 2 }, + vec![ + Token::StructStart("RenameStructSerializeDeserialize", Some(2)), + + Token::MapSep, + Token::Str("a1"), + Token::I32(1), + + Token::MapSep, + Token::Str("a5"), + Token::I32(2), + + Token::MapEnd, + ] + ); +} + +#[test] +fn test_rename_variant_serialize_deserialize() { + assert_ser_tokens( + &RenameEnumVariantSerializeDeserialize::Robin, + &[ + Token::EnumUnit("RenameEnumVariantSerializeDeserialize", "dick_grayson"), + ] + ); + + assert_de_tokens( + &RenameEnumVariantSerializeDeserialize::Robin, + vec![ + Token::EnumUnit("RenameEnumVariantSerializeDeserialize", "jason_todd"), + ] + ); +} + +#[test] +fn test_enum_serialize_deserialize() { + assert_ser_tokens( + &RenameVariantSerializeDeserialize::Map { + a: 0, + b: String::new(), + }, + &[ + Token::EnumMapStart("RenameVariantSerializeDeserialize", "Map", Some(2)), + + Token::MapSep, + Token::Str("a"), + Token::I8(0), + + Token::MapSep, + Token::Str("c"), + Token::Str(""), + + Token::MapEnd, + ] + ); + + assert_de_tokens( + &RenameVariantSerializeDeserialize::Map { + a: 0, + b: String::new(), + }, + vec![ + Token::EnumMapStart("RenameVariantSerializeDeserialize", "Map", Some(2)), + + Token::MapSep, + Token::Str("a"), + Token::I8(0), + + Token::MapSep, + Token::Str("d"), + Token::Str(""), + + Token::MapEnd, ] ); }