From be8431ca3a84e6ff355551b98714242b318a7706 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 15 Dec 2019 19:08:27 +0100 Subject: [PATCH] Add #[serde(rename_all_fields = "foo")] attribute --- serde_derive/src/internals/ast.rs | 9 ++-- serde_derive/src/internals/attr.rs | 76 ++++++++++++++++++++++++++++ serde_derive/src/internals/case.rs | 8 +++ serde_derive/src/internals/symbol.rs | 1 + test_suite/tests/test_macros.rs | 56 ++++++++++++++++++++ 5 files changed, 147 insertions(+), 3 deletions(-) diff --git a/serde_derive/src/internals/ast.rs b/serde_derive/src/internals/ast.rs index 415a6ea659..f78536bceb 100644 --- a/serde_derive/src/internals/ast.rs +++ b/serde_derive/src/internals/ast.rs @@ -88,9 +88,12 @@ impl<'a> Container<'a> { if field.attrs.flatten() { has_flatten = true; } - field - .attrs - .rename_by_rules(variant.attrs.rename_all_rules()); + field.attrs.rename_by_rules( + &variant + .attrs + .rename_all_rules() + .or(attrs.rename_all_fields_rules()), + ); } } } diff --git a/serde_derive/src/internals/attr.rs b/serde_derive/src/internals/attr.rs index 0a5595dc7c..93e943a74e 100644 --- a/serde_derive/src/internals/attr.rs +++ b/serde_derive/src/internals/attr.rs @@ -200,11 +200,23 @@ impl Name { } } +#[derive(Copy, Clone)] pub struct RenameAllRules { serialize: RenameRule, deserialize: RenameRule, } +impl RenameAllRules { + /// Returns a new `RenameAllRules` with the individual rules of `self` and + /// `other_rules` joined by `RenameRules::or`. + pub fn or(&self, other_rules: &Self) -> Self { + Self { + serialize: self.serialize.or(&other_rules.serialize), + deserialize: self.deserialize.or(&other_rules.deserialize), + } + } +} + /// Represents struct or enum attribute information. pub struct Container { name: Name, @@ -212,6 +224,7 @@ pub struct Container { deny_unknown_fields: bool, default: Default, rename_all_rules: RenameAllRules, + rename_all_fields_rules: RenameAllRules, ser_bound: Option>, de_bound: Option>, tag: TagType, @@ -292,6 +305,8 @@ impl Container { let mut default = Attr::none(cx, DEFAULT); let mut rename_all_ser_rule = Attr::none(cx, RENAME_ALL); let mut rename_all_de_rule = Attr::none(cx, RENAME_ALL); + let mut rename_all_fields_ser_rule = Attr::none(cx, RENAME_ALL_FIELDS); + let mut rename_all_fields_de_rule = Attr::none(cx, RENAME_ALL_FIELDS); let mut ser_bound = Attr::none(cx, BOUND); let mut de_bound = Attr::none(cx, BOUND); let mut untagged = BoolAttr::none(cx, UNTAGGED); @@ -377,6 +392,59 @@ impl Container { } } + // Parse `#[serde(rename_all_fields = "foo")]` + Meta(NameValue(m)) if m.path == RENAME_ALL_FIELDS => { + if let Ok(s) = get_lit_str(cx, RENAME_ALL_FIELDS, &m.lit) { + match RenameRule::from_str(&s.value()) { + Ok(rename_rule) => { + rename_all_fields_ser_rule.set(&m.path, rename_rule); + rename_all_fields_de_rule.set(&m.path, rename_rule); + } + Err(()) => cx.error_spanned_by( + s, + format!( + "unknown rename rule for #[serde(rename_all_fields = {:?})]", + s.value() + ), + ), + } + } + } + + // Parse `#[serde(rename_all_fields(serialize = "foo", deserialize = "bar"))] + Meta(List(m)) if m.path == RENAME_ALL_FIELDS => { + if let Ok((ser, de)) = get_renames(cx, &m.nested) { + if let Some(ser) = ser { + match RenameRule::from_str(&ser.value()) { + Ok(rename_rule) => { + rename_all_fields_ser_rule.set(&m.path, rename_rule) + } + Err(()) => cx.error_spanned_by( + ser, + format!( + "unknown rename rule for #[serde(rename_all_fields = {:?})]", + ser.value(), + ), + ), + } + } + if let Some(de) = de { + match RenameRule::from_str(&de.value()) { + Ok(rename_rule) => { + rename_all_fields_de_rule.set(&m.path, rename_rule) + } + Err(()) => cx.error_spanned_by( + de, + format!( + "unknown rename rule for #[serde(rename_all_fields = {:?})]", + de.value(), + ), + ), + } + } + } + } + // Parse `#[serde(transparent)]` Meta(Path(word)) if word == TRANSPARENT => { transparent.set_true(word); @@ -601,6 +669,10 @@ impl Container { serialize: rename_all_ser_rule.get().unwrap_or(RenameRule::None), deserialize: rename_all_de_rule.get().unwrap_or(RenameRule::None), }, + rename_all_fields_rules: RenameAllRules { + serialize: rename_all_fields_ser_rule.get().unwrap_or(RenameRule::None), + deserialize: rename_all_fields_de_rule.get().unwrap_or(RenameRule::None), + }, ser_bound: ser_bound.get(), de_bound: de_bound.get(), tag: decide_tag(cx, item, untagged, internal_tag, content), @@ -622,6 +694,10 @@ impl Container { &self.rename_all_rules } + pub fn rename_all_fields_rules(&self) -> &RenameAllRules { + &self.rename_all_fields_rules + } + pub fn transparent(&self) -> bool { self.transparent } diff --git a/serde_derive/src/internals/case.rs b/serde_derive/src/internals/case.rs index 3fcbb32dbd..bbcb16d41c 100644 --- a/serde_derive/src/internals/case.rs +++ b/serde_derive/src/internals/case.rs @@ -90,6 +90,14 @@ impl RenameRule { ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(field).replace('_', "-"), } } + + /// Returns the `RenameRule` if it is not `None`, `rule_b` otherwise. + pub fn or(&self, rule_b: &Self) -> Self { + match self { + None => *rule_b, + _ => *self, + } + } } impl FromStr for RenameRule { diff --git a/serde_derive/src/internals/symbol.rs b/serde_derive/src/internals/symbol.rs index 318b81bbbd..9a1c7aaf62 100644 --- a/serde_derive/src/internals/symbol.rs +++ b/serde_derive/src/internals/symbol.rs @@ -22,6 +22,7 @@ pub const OTHER: Symbol = Symbol("other"); pub const REMOTE: Symbol = Symbol("remote"); pub const RENAME: Symbol = Symbol("rename"); pub const RENAME_ALL: Symbol = Symbol("rename_all"); +pub const RENAME_ALL_FIELDS: Symbol = Symbol("rename_all_fields"); pub const SERDE: Symbol = Symbol("serde"); pub const SERIALIZE: Symbol = Symbol("serialize"); pub const SERIALIZE_WITH: Symbol = Symbol("serialize_with"); diff --git a/test_suite/tests/test_macros.rs b/test_suite/tests/test_macros.rs index d4456bed7a..44f405677c 100644 --- a/test_suite/tests/test_macros.rs +++ b/test_suite/tests/test_macros.rs @@ -1825,6 +1825,62 @@ fn test_rename_all() { ); } +#[test] +fn test_rename_all_fields() { + #[derive(Serialize, Deserialize, Debug, PartialEq)] + #[serde(rename_all_fields = "kebab-case")] + enum E { + V1, + V2(bool), + V3 { + a_field: bool, + another_field: bool, + #[serde(rename = "last-field")] + yet_another_field: bool, + }, + #[serde(rename_all = "snake_case")] + V4 { + a_field: bool, + }, + } + + assert_tokens( + &E::V3 { + a_field: true, + another_field: true, + yet_another_field: true, + }, + &[ + Token::StructVariant { + name: "E", + variant: "V3", + len: 3, + }, + Token::Str("a-field"), + Token::Bool(true), + Token::Str("another-field"), + Token::Bool(true), + Token::Str("last-field"), + Token::Bool(true), + Token::StructVariantEnd, + ], + ); + + assert_tokens( + &E::V4 { a_field: true }, + &[ + Token::StructVariant { + name: "E", + variant: "V4", + len: 1, + }, + Token::Str("a_field"), + Token::Bool(true), + Token::StructVariantEnd, + ], + ); +} + #[test] fn test_untagged_newtype_variant_containing_unit_struct_not_map() { #[derive(Debug, PartialEq, Serialize, Deserialize)]