Skip to content

Commit

Permalink
Add #[serde(rename_all_fields = "foo")] attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
jplatte committed Oct 2, 2020
1 parent be7d0e7 commit e377ec1
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 3 deletions.
9 changes: 6 additions & 3 deletions serde_derive/src/internals/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
);
}
}
}
Expand Down
76 changes: 76 additions & 0 deletions serde_derive/src/internals/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,18 +200,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 @@ -293,6 +306,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 @@ -378,6 +393,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);
Expand Down Expand Up @@ -616,6 +684,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 @@ -638,6 +710,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
}
Expand Down
8 changes: 8 additions & 0 deletions serde_derive/src/internals/case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions serde_derive/src/internals/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
56 changes: 56 additions & 0 deletions test_suite/tests/test_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1839,6 +1839,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 e377ec1

Please sign in to comment.