From 2f9bf4d3ebacf34fb2aa84b0da7675f8ab82c3b8 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 15 Dec 2019 19:08:27 +0100 Subject: [PATCH 1/2] Add #[serde(rename_all_fields = "foo")] attribute --- serde_derive/src/internals/ast.rs | 9 ++-- serde_derive/src/internals/attr.rs | 61 ++++++++++++++++++++++++++++ serde_derive/src/internals/case.rs | 8 ++++ serde_derive/src/internals/symbol.rs | 1 + test_suite/tests/test_macros.rs | 56 +++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 3 deletions(-) diff --git a/serde_derive/src/internals/ast.rs b/serde_derive/src/internals/ast.rs index 8bcb0ecd1..509658a45 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 42212a64d..c03517c05 100644 --- a/serde_derive/src/internals/attr.rs +++ b/serde_derive/src/internals/attr.rs @@ -193,11 +193,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, @@ -205,6 +217,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, @@ -288,6 +301,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); @@ -341,6 +356,44 @@ impl Container { } } } + } else if meta.path == RENAME_ALL_FIELDS { + // #[serde(rename_all_fields = "foo")] + // #[serde(rename_all_fields(serialize = "foo", deserialize = "bar"))] + let one_name = meta.input.peek(Token![=]); + let (ser, de) = get_renames(cx, RENAME_ALL_FIELDS, &meta)?; + + match item.data { + syn::Data::Enum(_) => { + if let Some(ser) = ser { + match RenameRule::from_str(&ser.value()) { + Ok(rename_rule) => { + rename_all_fields_ser_rule.set(&meta.path, rename_rule); + } + Err(err) => cx.error_spanned_by(ser, err), + } + } + if let Some(de) = de { + match RenameRule::from_str(&de.value()) { + Ok(rename_rule) => { + rename_all_fields_de_rule.set(&meta.path, rename_rule); + } + Err(err) => { + if !one_name { + cx.error_spanned_by(de, err); + } + } + } + } + } + syn::Data::Struct(_) => { + let msg = "#[serde(rename_all_fields)] can only be used on enums"; + cx.error_spanned_by(&meta.path, msg); + } + syn::Data::Union(_) => { + let msg = "#[serde(rename_all_fields)] can only be used on enums"; + cx.error_spanned_by(&meta.path, msg); + } + } } else if meta.path == TRANSPARENT { // #[serde(transparent)] transparent.set_true(meta.path); @@ -528,6 +581,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), @@ -551,6 +608,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 554505160..51ee9efbe 100644 --- a/serde_derive/src/internals/case.rs +++ b/serde_derive/src/internals/case.rs @@ -112,6 +112,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, + } + } } pub struct ParseError<'a> { diff --git a/serde_derive/src/internals/symbol.rs b/serde_derive/src/internals/symbol.rs index 9606edb5f..68091fb85 100644 --- a/serde_derive/src/internals/symbol.rs +++ b/serde_derive/src/internals/symbol.rs @@ -23,6 +23,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 REPR: Symbol = Symbol("repr"); pub const SERDE: Symbol = Symbol("serde"); pub const SERIALIZE: Symbol = Symbol("serialize"); diff --git a/test_suite/tests/test_macros.rs b/test_suite/tests/test_macros.rs index 0594d8d45..d95fa928f 100644 --- a/test_suite/tests/test_macros.rs +++ b/test_suite/tests/test_macros.rs @@ -1924,6 +1924,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)] From 56be1c203e8425b7c54b4206101850d9d70044c8 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 15 Dec 2019 19:11:46 +0100 Subject: [PATCH 2/2] Pass RenameRule, RenameAllRules by value --- serde_derive/src/internals/ast.rs | 2 +- serde_derive/src/internals/attr.rs | 22 +++++++++++----------- serde_derive/src/internals/case.rs | 14 +++++++------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/serde_derive/src/internals/ast.rs b/serde_derive/src/internals/ast.rs index 509658a45..75b28c969 100644 --- a/serde_derive/src/internals/ast.rs +++ b/serde_derive/src/internals/ast.rs @@ -89,7 +89,7 @@ impl<'a> Container<'a> { has_flatten = true; } field.attrs.rename_by_rules( - &variant + 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 c03517c05..133299564 100644 --- a/serde_derive/src/internals/attr.rs +++ b/serde_derive/src/internals/attr.rs @@ -202,10 +202,10 @@ pub struct RenameAllRules { 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 { + pub fn or(self, other_rules: Self) -> Self { Self { - serialize: self.serialize.or(&other_rules.serialize), - deserialize: self.deserialize.or(&other_rules.deserialize), + serialize: self.serialize.or(other_rules.serialize), + deserialize: self.deserialize.or(other_rules.deserialize), } } } @@ -604,12 +604,12 @@ impl Container { &self.name } - pub fn rename_all_rules(&self) -> &RenameAllRules { - &self.rename_all_rules + pub fn rename_all_rules(&self) -> RenameAllRules { + self.rename_all_rules } - pub fn rename_all_fields_rules(&self) -> &RenameAllRules { - &self.rename_all_fields_rules + pub fn rename_all_fields_rules(&self) -> RenameAllRules { + self.rename_all_fields_rules } pub fn transparent(&self) -> bool { @@ -982,7 +982,7 @@ impl Variant { self.name.deserialize_aliases() } - pub fn rename_by_rules(&mut self, rules: &RenameAllRules) { + pub fn rename_by_rules(&mut self, rules: RenameAllRules) { if !self.name.serialize_renamed { self.name.serialize = rules.serialize.apply_to_variant(&self.name.serialize); } @@ -991,8 +991,8 @@ impl Variant { } } - pub fn rename_all_rules(&self) -> &RenameAllRules { - &self.rename_all_rules + pub fn rename_all_rules(&self) -> RenameAllRules { + self.rename_all_rules } pub fn ser_bound(&self) -> Option<&[syn::WherePredicate]> { @@ -1321,7 +1321,7 @@ impl Field { self.name.deserialize_aliases() } - pub fn rename_by_rules(&mut self, rules: &RenameAllRules) { + pub fn rename_by_rules(&mut self, rules: RenameAllRules) { if !self.name.serialize_renamed { self.name.serialize = rules.serialize.apply_to_field(&self.name.serialize); } diff --git a/serde_derive/src/internals/case.rs b/serde_derive/src/internals/case.rs index 51ee9efbe..e769462cd 100644 --- a/serde_derive/src/internals/case.rs +++ b/serde_derive/src/internals/case.rs @@ -59,8 +59,8 @@ impl RenameRule { } /// Apply a renaming rule to an enum variant, returning the version expected in the source. - pub fn apply_to_variant(&self, variant: &str) -> String { - match *self { + pub fn apply_to_variant(self, variant: &str) -> String { + match self { None | PascalCase => variant.to_owned(), LowerCase => variant.to_ascii_lowercase(), UpperCase => variant.to_ascii_uppercase(), @@ -84,8 +84,8 @@ impl RenameRule { } /// Apply a renaming rule to a struct field, returning the version expected in the source. - pub fn apply_to_field(&self, field: &str) -> String { - match *self { + pub fn apply_to_field(self, field: &str) -> String { + match self { None | LowerCase | SnakeCase => field.to_owned(), UpperCase => field.to_ascii_uppercase(), PascalCase => { @@ -114,10 +114,10 @@ impl RenameRule { } /// Returns the `RenameRule` if it is not `None`, `rule_b` otherwise. - pub fn or(&self, rule_b: &Self) -> Self { + pub fn or(self, rule_b: Self) -> Self { match self { - None => *rule_b, - _ => *self, + None => rule_b, + _ => self, } } }