mirror of
https://github.com/serde-rs/serde.git
synced 2025-09-29 22:11:09 +00:00
Merge pull request #1695 from jplatte/rename_all_fields
Add #[serde(rename_all_fields = "foo")] attribute
This commit is contained in:
commit
e74925bc43
@ -86,9 +86,12 @@ impl<'a> Container<'a> {
|
|||||||
if field.attrs.flatten() {
|
if field.attrs.flatten() {
|
||||||
has_flatten = true;
|
has_flatten = true;
|
||||||
}
|
}
|
||||||
field
|
field.attrs.rename_by_rules(
|
||||||
.attrs
|
variant
|
||||||
.rename_by_rules(variant.attrs.rename_all_rules());
|
.attrs
|
||||||
|
.rename_all_rules()
|
||||||
|
.or(attrs.rename_all_fields_rules()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,11 +192,23 @@ impl Name {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
pub struct RenameAllRules {
|
pub struct RenameAllRules {
|
||||||
serialize: RenameRule,
|
serialize: RenameRule,
|
||||||
deserialize: 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.
|
/// Represents struct or enum attribute information.
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
name: Name,
|
name: Name,
|
||||||
@ -204,6 +216,7 @@ pub struct Container {
|
|||||||
deny_unknown_fields: bool,
|
deny_unknown_fields: bool,
|
||||||
default: Default,
|
default: Default,
|
||||||
rename_all_rules: RenameAllRules,
|
rename_all_rules: RenameAllRules,
|
||||||
|
rename_all_fields_rules: RenameAllRules,
|
||||||
ser_bound: Option<Vec<syn::WherePredicate>>,
|
ser_bound: Option<Vec<syn::WherePredicate>>,
|
||||||
de_bound: Option<Vec<syn::WherePredicate>>,
|
de_bound: Option<Vec<syn::WherePredicate>>,
|
||||||
tag: TagType,
|
tag: TagType,
|
||||||
@ -287,6 +300,8 @@ impl Container {
|
|||||||
let mut default = Attr::none(cx, DEFAULT);
|
let mut default = Attr::none(cx, DEFAULT);
|
||||||
let mut rename_all_ser_rule = Attr::none(cx, RENAME_ALL);
|
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_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 ser_bound = Attr::none(cx, BOUND);
|
||||||
let mut de_bound = Attr::none(cx, BOUND);
|
let mut de_bound = Attr::none(cx, BOUND);
|
||||||
let mut untagged = BoolAttr::none(cx, UNTAGGED);
|
let mut untagged = BoolAttr::none(cx, UNTAGGED);
|
||||||
@ -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 {
|
} else if meta.path == TRANSPARENT {
|
||||||
// #[serde(transparent)]
|
// #[serde(transparent)]
|
||||||
transparent.set_true(meta.path);
|
transparent.set_true(meta.path);
|
||||||
@ -527,6 +580,10 @@ impl Container {
|
|||||||
serialize: rename_all_ser_rule.get().unwrap_or(RenameRule::None),
|
serialize: rename_all_ser_rule.get().unwrap_or(RenameRule::None),
|
||||||
deserialize: rename_all_de_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(),
|
ser_bound: ser_bound.get(),
|
||||||
de_bound: de_bound.get(),
|
de_bound: de_bound.get(),
|
||||||
tag: decide_tag(cx, item, untagged, internal_tag, content),
|
tag: decide_tag(cx, item, untagged, internal_tag, content),
|
||||||
@ -546,8 +603,12 @@ impl Container {
|
|||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rename_all_rules(&self) -> &RenameAllRules {
|
pub fn rename_all_rules(&self) -> RenameAllRules {
|
||||||
&self.rename_all_rules
|
self.rename_all_rules
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rename_all_fields_rules(&self) -> RenameAllRules {
|
||||||
|
self.rename_all_fields_rules
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transparent(&self) -> bool {
|
pub fn transparent(&self) -> bool {
|
||||||
@ -920,7 +981,7 @@ impl Variant {
|
|||||||
self.name.deserialize_aliases()
|
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 {
|
if !self.name.serialize_renamed {
|
||||||
self.name.serialize = rules.serialize.apply_to_variant(&self.name.serialize);
|
self.name.serialize = rules.serialize.apply_to_variant(&self.name.serialize);
|
||||||
}
|
}
|
||||||
@ -929,8 +990,8 @@ impl Variant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rename_all_rules(&self) -> &RenameAllRules {
|
pub fn rename_all_rules(&self) -> RenameAllRules {
|
||||||
&self.rename_all_rules
|
self.rename_all_rules
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ser_bound(&self) -> Option<&[syn::WherePredicate]> {
|
pub fn ser_bound(&self) -> Option<&[syn::WherePredicate]> {
|
||||||
@ -1259,7 +1320,7 @@ impl Field {
|
|||||||
self.name.deserialize_aliases()
|
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 {
|
if !self.name.serialize_renamed {
|
||||||
self.name.serialize = rules.serialize.apply_to_field(&self.name.serialize);
|
self.name.serialize = rules.serialize.apply_to_field(&self.name.serialize);
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,8 @@ impl RenameRule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a renaming rule to an enum variant, returning the version expected in the source.
|
/// Apply a renaming rule to an enum variant, returning the version expected in the source.
|
||||||
pub fn apply_to_variant(&self, variant: &str) -> String {
|
pub fn apply_to_variant(self, variant: &str) -> String {
|
||||||
match *self {
|
match self {
|
||||||
None | PascalCase => variant.to_owned(),
|
None | PascalCase => variant.to_owned(),
|
||||||
LowerCase => variant.to_ascii_lowercase(),
|
LowerCase => variant.to_ascii_lowercase(),
|
||||||
UpperCase => variant.to_ascii_uppercase(),
|
UpperCase => variant.to_ascii_uppercase(),
|
||||||
@ -79,8 +79,8 @@ impl RenameRule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a renaming rule to a struct field, returning the version expected in the source.
|
/// Apply a renaming rule to a struct field, returning the version expected in the source.
|
||||||
pub fn apply_to_field(&self, field: &str) -> String {
|
pub fn apply_to_field(self, field: &str) -> String {
|
||||||
match *self {
|
match self {
|
||||||
None | LowerCase | SnakeCase => field.to_owned(),
|
None | LowerCase | SnakeCase => field.to_owned(),
|
||||||
UpperCase => field.to_ascii_uppercase(),
|
UpperCase => field.to_ascii_uppercase(),
|
||||||
PascalCase => {
|
PascalCase => {
|
||||||
@ -107,6 +107,14 @@ impl RenameRule {
|
|||||||
ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(field).replace('_', "-"),
|
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> {
|
pub struct ParseError<'a> {
|
||||||
|
@ -23,6 +23,7 @@ pub const OTHER: Symbol = Symbol("other");
|
|||||||
pub const REMOTE: Symbol = Symbol("remote");
|
pub const REMOTE: Symbol = Symbol("remote");
|
||||||
pub const RENAME: Symbol = Symbol("rename");
|
pub const RENAME: Symbol = Symbol("rename");
|
||||||
pub const RENAME_ALL: Symbol = Symbol("rename_all");
|
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 REPR: Symbol = Symbol("repr");
|
||||||
pub const SERDE: Symbol = Symbol("serde");
|
pub const SERDE: Symbol = Symbol("serde");
|
||||||
pub const SERIALIZE: Symbol = Symbol("serialize");
|
pub const SERIALIZE: Symbol = Symbol("serialize");
|
||||||
|
@ -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]
|
#[test]
|
||||||
fn test_untagged_newtype_variant_containing_unit_struct_not_map() {
|
fn test_untagged_newtype_variant_containing_unit_struct_not_map() {
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user