Skip to content

Commit

Permalink
Merge pull request #1695 from jplatte/rename_all_fields
Browse files Browse the repository at this point in the history
Add #[serde(rename_all_fields = "foo")] attribute
  • Loading branch information
dtolnay committed Jul 27, 2023
2 parents ad94aed + 56be1c2 commit e74925b
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 13 deletions.
9 changes: 6 additions & 3 deletions serde_derive/src/internals/ast.rs
Expand Up @@ -86,9 +86,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()),
);
}
}
}
Expand Down
73 changes: 67 additions & 6 deletions serde_derive/src/internals/attr.rs
Expand Up @@ -192,18 +192,31 @@ 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,
transparent: bool,
deny_unknown_fields: bool,
default: Default,
rename_all_rules: RenameAllRules,
rename_all_fields_rules: RenameAllRules,
ser_bound: Option<Vec<syn::WherePredicate>>,
de_bound: Option<Vec<syn::WherePredicate>>,
tag: TagType,
Expand Down Expand Up @@ -287,6 +300,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);
Expand Down Expand Up @@ -340,6 +355,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);
Expand Down Expand Up @@ -527,6 +580,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),
Expand All @@ -546,8 +603,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 transparent(&self) -> bool {
Expand Down Expand Up @@ -920,7 +981,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);
}
Expand All @@ -929,8 +990,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]> {
Expand Down Expand Up @@ -1259,7 +1320,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);
}
Expand Down
16 changes: 12 additions & 4 deletions serde_derive/src/internals/case.rs
Expand Up @@ -54,8 +54,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(),
Expand All @@ -79,8 +79,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 => {
Expand All @@ -107,6 +107,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> {
Expand Down
1 change: 1 addition & 0 deletions serde_derive/src/internals/symbol.rs
Expand Up @@ -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");
Expand Down
56 changes: 56 additions & 0 deletions test_suite/tests/test_macros.rs
Expand Up @@ -1923,6 +1923,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)]
Expand Down

0 comments on commit e74925b

Please sign in to comment.