mirror of
https://github.com/serde-rs/serde.git
synced 2025-10-02 07:21:12 +00:00
Implementing rename_all
container attribute using Inflector
trait. #140
This commit is contained in:
parent
8e5f472e27
commit
fc94c5399a
@ -13,6 +13,7 @@ include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE-APACHE", "LICENSE-
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "0.11", default-features = false, features = ["parsing"] }
|
||||
Inflector = "0.7.0"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "serde-rs/serde" }
|
||||
|
@ -38,7 +38,7 @@ impl<'a> Item<'a> {
|
||||
pub fn from_ast(cx: &Ctxt, item: &'a syn::MacroInput) -> Item<'a> {
|
||||
let attrs = attr::Item::from_ast(cx, item);
|
||||
|
||||
let body = match item.body {
|
||||
let mut body = match item.body {
|
||||
syn::Body::Enum(ref variants) => Body::Enum(enum_from_ast(cx, variants)),
|
||||
syn::Body::Struct(ref variant_data) => {
|
||||
let (style, fields) = struct_from_ast(cx, variant_data);
|
||||
@ -46,6 +46,22 @@ impl<'a> Item<'a> {
|
||||
}
|
||||
};
|
||||
|
||||
match body {
|
||||
Body::Enum(ref mut variants) => {
|
||||
for ref mut variant in variants {
|
||||
variant.attrs.rename_by_rule(attrs.rename_all());
|
||||
for ref mut field in &mut variant.fields {
|
||||
field.attrs.rename_by_rule(variant.attrs.rename_all());
|
||||
}
|
||||
}
|
||||
},
|
||||
Body::Struct(_, ref mut fields) => {
|
||||
for field in fields {
|
||||
field.attrs.rename_by_rule(attrs.rename_all());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
ident: item.ident.clone(),
|
||||
attrs: attrs,
|
||||
|
@ -2,6 +2,7 @@ use Ctxt;
|
||||
use syn;
|
||||
use syn::MetaItem::{List, NameValue, Word};
|
||||
use syn::NestedMetaItem::{Literal, MetaItem};
|
||||
use inflector::Inflector;
|
||||
|
||||
// This module handles parsing of `#[serde(...)]` attributes. The entrypoints
|
||||
// are `attr::Item::from_ast`, `attr::Variant::from_ast`, and
|
||||
@ -85,12 +86,41 @@ impl Name {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum RenameAll {
|
||||
None,
|
||||
/// Rename fields to "PascalCase" style. By Rust coding standards this is the default.
|
||||
PascalCase,
|
||||
/// Rename fields to "snake_case" style.
|
||||
LowerSnakeCase,
|
||||
/// Rename fields to "SNAKE_CASE" style.
|
||||
UpperSnakeCase,
|
||||
/// Rename fields to "kebab-case" style.
|
||||
KebabCase,
|
||||
/// Rename fields to "kebabCase" style.
|
||||
CamelCase,
|
||||
}
|
||||
|
||||
impl RenameAll {
|
||||
pub fn apply(&self, name: String) -> String {
|
||||
match *self {
|
||||
RenameAll::None => name,
|
||||
RenameAll::PascalCase => name.to_pascal_case(),
|
||||
RenameAll::LowerSnakeCase => name.to_snake_case(),
|
||||
RenameAll::UpperSnakeCase => name.to_screaming_snake_case(),
|
||||
RenameAll::KebabCase => name.to_kebab_case(),
|
||||
RenameAll::CamelCase => name.to_camel_case(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents container (e.g. struct) attribute information
|
||||
#[derive(Debug)]
|
||||
pub struct Item {
|
||||
name: Name,
|
||||
deny_unknown_fields: bool,
|
||||
default: Default,
|
||||
rename_all: RenameAll,
|
||||
ser_bound: Option<Vec<syn::WherePredicate>>,
|
||||
de_bound: Option<Vec<syn::WherePredicate>>,
|
||||
tag: EnumTag,
|
||||
@ -135,6 +165,7 @@ impl Item {
|
||||
let mut de_name = Attr::none(cx, "rename");
|
||||
let mut deny_unknown_fields = BoolAttr::none(cx, "deny_unknown_fields");
|
||||
let mut default = Attr::none(cx, "default");
|
||||
let mut rename_all = Attr::none(cx, "rename_all");
|
||||
let mut ser_bound = Attr::none(cx, "bound");
|
||||
let mut de_bound = Attr::none(cx, "bound");
|
||||
let mut untagged = BoolAttr::none(cx, "untagged");
|
||||
@ -160,6 +191,23 @@ impl Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(rename_all="foo")]`
|
||||
MetaItem(NameValue(ref name, ref lit)) if name == "rename_all" => {
|
||||
if let Ok(s) = get_string_from_lit(cx, name.as_ref(), name.as_ref(), lit) {
|
||||
// The path possibility defies a simple IntoString implementation.
|
||||
match s.as_str() {
|
||||
"snake_case" => rename_all.set(RenameAll::LowerSnakeCase),
|
||||
"SNAKE_CASE" => rename_all.set(RenameAll::UpperSnakeCase),
|
||||
"kebab-case" => rename_all.set(RenameAll::KebabCase),
|
||||
"camelCase" => rename_all.set(RenameAll::CamelCase),
|
||||
"PascalCase" => rename_all.set(RenameAll::PascalCase),
|
||||
_ => {
|
||||
cx.error(format!("unknown rename rule for #[serde(rename_all = \"{:?}\")]", s))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(deny_unknown_fields)]`
|
||||
MetaItem(Word(ref name)) if name == "deny_unknown_fields" => {
|
||||
deny_unknown_fields.set_true();
|
||||
@ -304,7 +352,7 @@ impl Item {
|
||||
EnumTag::External
|
||||
}
|
||||
};
|
||||
|
||||
// @DEBUG
|
||||
Item {
|
||||
name: Name {
|
||||
serialize: ser_name.get().unwrap_or_else(|| item.ident.to_string()),
|
||||
@ -312,6 +360,7 @@ impl Item {
|
||||
},
|
||||
deny_unknown_fields: deny_unknown_fields.get(),
|
||||
default: default.get().unwrap_or(Default::None),
|
||||
rename_all: rename_all.get().unwrap_or(RenameAll::None),
|
||||
ser_bound: ser_bound.get(),
|
||||
de_bound: de_bound.get(),
|
||||
tag: tag,
|
||||
@ -322,6 +371,10 @@ impl Item {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn rename_all(&self) -> &RenameAll {
|
||||
&self.rename_all
|
||||
}
|
||||
|
||||
pub fn deny_unknown_fields(&self) -> bool {
|
||||
self.deny_unknown_fields
|
||||
}
|
||||
@ -347,6 +400,9 @@ impl Item {
|
||||
#[derive(Debug)]
|
||||
pub struct Variant {
|
||||
name: Name,
|
||||
ser_renamed: bool,
|
||||
de_renamed: bool,
|
||||
rename_all: RenameAll,
|
||||
skip_deserializing: bool,
|
||||
skip_serializing: bool,
|
||||
}
|
||||
@ -357,6 +413,7 @@ impl Variant {
|
||||
let mut de_name = Attr::none(cx, "rename");
|
||||
let mut skip_deserializing = BoolAttr::none(cx, "skip_deserializing");
|
||||
let mut skip_serializing = BoolAttr::none(cx, "skip_serializing");
|
||||
let mut rename_all = Attr::none(cx, "rename_all");
|
||||
|
||||
for meta_items in variant.attrs.iter().filter_map(get_serde_meta_items) {
|
||||
for meta_item in meta_items {
|
||||
@ -376,6 +433,24 @@ impl Variant {
|
||||
de_name.set_opt(de);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(rename_all="foo")]`
|
||||
MetaItem(NameValue(ref name, ref lit)) if name == "rename_all" => {
|
||||
if let Ok(s) = get_string_from_lit(cx, name.as_ref(), name.as_ref(), lit) {
|
||||
// The path possibility defies a simple IntoString implementation.
|
||||
match s.as_str() {
|
||||
"snake_case" => rename_all.set(RenameAll::LowerSnakeCase),
|
||||
"SNAKE_CASE" => rename_all.set(RenameAll::UpperSnakeCase),
|
||||
"kebab-case" => rename_all.set(RenameAll::KebabCase),
|
||||
"camelCase" => rename_all.set(RenameAll::CamelCase),
|
||||
"PascalCase" => rename_all.set(RenameAll::PascalCase),
|
||||
_ => {
|
||||
cx.error(format!("unknown rename rule for #[serde(rename_all = \"{:?}\")]", s))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(skip_deserializing)]`
|
||||
MetaItem(Word(ref name)) if name == "skip_deserializing" => {
|
||||
skip_deserializing.set_true();
|
||||
@ -396,11 +471,18 @@ impl Variant {
|
||||
}
|
||||
}
|
||||
|
||||
let ser_name = ser_name.get();
|
||||
let ser_renamed = ser_name.is_some();
|
||||
let de_name = de_name.get();
|
||||
let de_renamed = de_name.is_some();
|
||||
Variant {
|
||||
name: Name {
|
||||
serialize: ser_name.get().unwrap_or_else(|| variant.ident.to_string()),
|
||||
deserialize: de_name.get().unwrap_or_else(|| variant.ident.to_string()),
|
||||
serialize: ser_name.unwrap_or_else(|| variant.ident.to_string()),
|
||||
deserialize: de_name.unwrap_or_else(|| variant.ident.to_string()),
|
||||
},
|
||||
ser_renamed: ser_renamed,
|
||||
de_renamed: de_renamed,
|
||||
rename_all: rename_all.get().unwrap_or(RenameAll::None),
|
||||
skip_deserializing: skip_deserializing.get(),
|
||||
skip_serializing: skip_serializing.get(),
|
||||
}
|
||||
@ -410,6 +492,19 @@ impl Variant {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn rename_by_rule(&mut self, rename_rule: &RenameAll) {
|
||||
if !self.ser_renamed {
|
||||
self.name.serialize = rename_rule.apply(self.name.serialize.clone());
|
||||
}
|
||||
if !self.de_renamed {
|
||||
self.name.deserialize = rename_rule.apply(self.name.deserialize.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rename_all(&self) -> &RenameAll {
|
||||
&self.rename_all
|
||||
}
|
||||
|
||||
pub fn skip_deserializing(&self) -> bool {
|
||||
self.skip_deserializing
|
||||
}
|
||||
@ -423,6 +518,8 @@ impl Variant {
|
||||
#[derive(Debug)]
|
||||
pub struct Field {
|
||||
name: Name,
|
||||
ser_renamed: bool,
|
||||
de_renamed: bool,
|
||||
skip_serializing: bool,
|
||||
skip_deserializing: bool,
|
||||
skip_serializing_if: Option<syn::Path>,
|
||||
@ -571,11 +668,17 @@ impl Field {
|
||||
default.set_if_none(Default::Default);
|
||||
}
|
||||
|
||||
let ser_name = ser_name.get();
|
||||
let ser_renamed = ser_name.is_some();
|
||||
let de_name = de_name.get();
|
||||
let de_renamed = de_name.is_some();
|
||||
Field {
|
||||
name: Name {
|
||||
serialize: ser_name.get().unwrap_or_else(|| ident.clone()),
|
||||
deserialize: de_name.get().unwrap_or(ident),
|
||||
serialize: ser_name.unwrap_or_else(|| ident.clone()),
|
||||
deserialize: de_name.unwrap_or(ident),
|
||||
},
|
||||
ser_renamed: ser_renamed,
|
||||
de_renamed: de_renamed,
|
||||
skip_serializing: skip_serializing.get(),
|
||||
skip_deserializing: skip_deserializing.get(),
|
||||
skip_serializing_if: skip_serializing_if.get(),
|
||||
@ -591,6 +694,15 @@ impl Field {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn rename_by_rule(&mut self, rename_rule: &RenameAll) {
|
||||
if !self.ser_renamed {
|
||||
self.name.serialize = rename_rule.apply(self.name.serialize.clone());
|
||||
}
|
||||
if !self.de_renamed {
|
||||
self.name.deserialize = rename_rule.apply(self.name.deserialize.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skip_serializing(&self) -> bool {
|
||||
self.skip_serializing
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
extern crate syn;
|
||||
extern crate inflector;
|
||||
|
||||
pub mod ast;
|
||||
pub mod attr;
|
||||
|
Loading…
x
Reference in New Issue
Block a user