Implementing rename_all container attribute using Inflector trait. #140

This commit is contained in:
Michael Mokrysz 2017-02-23 18:57:58 +00:00
parent 8e5f472e27
commit fc94c5399a
4 changed files with 136 additions and 6 deletions

View File

@ -13,6 +13,7 @@ include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE-APACHE", "LICENSE-
[dependencies] [dependencies]
syn = { version = "0.11", default-features = false, features = ["parsing"] } syn = { version = "0.11", default-features = false, features = ["parsing"] }
Inflector = "0.7.0"
[badges] [badges]
travis-ci = { repository = "serde-rs/serde" } travis-ci = { repository = "serde-rs/serde" }

View File

@ -38,7 +38,7 @@ impl<'a> Item<'a> {
pub fn from_ast(cx: &Ctxt, item: &'a syn::MacroInput) -> Item<'a> { pub fn from_ast(cx: &Ctxt, item: &'a syn::MacroInput) -> Item<'a> {
let attrs = attr::Item::from_ast(cx, item); 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::Enum(ref variants) => Body::Enum(enum_from_ast(cx, variants)),
syn::Body::Struct(ref variant_data) => { syn::Body::Struct(ref variant_data) => {
let (style, fields) = struct_from_ast(cx, 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 { Item {
ident: item.ident.clone(), ident: item.ident.clone(),
attrs: attrs, attrs: attrs,

View File

@ -2,6 +2,7 @@ use Ctxt;
use syn; use syn;
use syn::MetaItem::{List, NameValue, Word}; use syn::MetaItem::{List, NameValue, Word};
use syn::NestedMetaItem::{Literal, MetaItem}; use syn::NestedMetaItem::{Literal, MetaItem};
use inflector::Inflector;
// This module handles parsing of `#[serde(...)]` attributes. The entrypoints // This module handles parsing of `#[serde(...)]` attributes. The entrypoints
// are `attr::Item::from_ast`, `attr::Variant::from_ast`, and // 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 /// Represents container (e.g. struct) attribute information
#[derive(Debug)] #[derive(Debug)]
pub struct Item { pub struct Item {
name: Name, name: Name,
deny_unknown_fields: bool, deny_unknown_fields: bool,
default: Default, default: Default,
rename_all: RenameAll,
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: EnumTag, tag: EnumTag,
@ -135,6 +165,7 @@ impl Item {
let mut de_name = Attr::none(cx, "rename"); let mut de_name = Attr::none(cx, "rename");
let mut deny_unknown_fields = BoolAttr::none(cx, "deny_unknown_fields"); let mut deny_unknown_fields = BoolAttr::none(cx, "deny_unknown_fields");
let mut default = Attr::none(cx, "default"); 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 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");
@ -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)]` // Parse `#[serde(deny_unknown_fields)]`
MetaItem(Word(ref name)) if name == "deny_unknown_fields" => { MetaItem(Word(ref name)) if name == "deny_unknown_fields" => {
deny_unknown_fields.set_true(); deny_unknown_fields.set_true();
@ -304,7 +352,7 @@ impl Item {
EnumTag::External EnumTag::External
} }
}; };
// @DEBUG
Item { Item {
name: Name { name: Name {
serialize: ser_name.get().unwrap_or_else(|| item.ident.to_string()), serialize: ser_name.get().unwrap_or_else(|| item.ident.to_string()),
@ -312,6 +360,7 @@ impl Item {
}, },
deny_unknown_fields: deny_unknown_fields.get(), deny_unknown_fields: deny_unknown_fields.get(),
default: default.get().unwrap_or(Default::None), default: default.get().unwrap_or(Default::None),
rename_all: rename_all.get().unwrap_or(RenameAll::None),
ser_bound: ser_bound.get(), ser_bound: ser_bound.get(),
de_bound: de_bound.get(), de_bound: de_bound.get(),
tag: tag, tag: tag,
@ -322,6 +371,10 @@ impl Item {
&self.name &self.name
} }
pub fn rename_all(&self) -> &RenameAll {
&self.rename_all
}
pub fn deny_unknown_fields(&self) -> bool { pub fn deny_unknown_fields(&self) -> bool {
self.deny_unknown_fields self.deny_unknown_fields
} }
@ -347,6 +400,9 @@ impl Item {
#[derive(Debug)] #[derive(Debug)]
pub struct Variant { pub struct Variant {
name: Name, name: Name,
ser_renamed: bool,
de_renamed: bool,
rename_all: RenameAll,
skip_deserializing: bool, skip_deserializing: bool,
skip_serializing: bool, skip_serializing: bool,
} }
@ -357,6 +413,7 @@ impl Variant {
let mut de_name = Attr::none(cx, "rename"); let mut de_name = Attr::none(cx, "rename");
let mut skip_deserializing = BoolAttr::none(cx, "skip_deserializing"); let mut skip_deserializing = BoolAttr::none(cx, "skip_deserializing");
let mut skip_serializing = BoolAttr::none(cx, "skip_serializing"); 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_items in variant.attrs.iter().filter_map(get_serde_meta_items) {
for meta_item in meta_items { for meta_item in meta_items {
@ -376,6 +433,24 @@ impl Variant {
de_name.set_opt(de); 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)]` // Parse `#[serde(skip_deserializing)]`
MetaItem(Word(ref name)) if name == "skip_deserializing" => { MetaItem(Word(ref name)) if name == "skip_deserializing" => {
skip_deserializing.set_true(); 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 { Variant {
name: Name { name: Name {
serialize: ser_name.get().unwrap_or_else(|| variant.ident.to_string()), serialize: ser_name.unwrap_or_else(|| variant.ident.to_string()),
deserialize: de_name.get().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_deserializing: skip_deserializing.get(),
skip_serializing: skip_serializing.get(), skip_serializing: skip_serializing.get(),
} }
@ -410,6 +492,19 @@ impl Variant {
&self.name &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 { pub fn skip_deserializing(&self) -> bool {
self.skip_deserializing self.skip_deserializing
} }
@ -423,6 +518,8 @@ impl Variant {
#[derive(Debug)] #[derive(Debug)]
pub struct Field { pub struct Field {
name: Name, name: Name,
ser_renamed: bool,
de_renamed: bool,
skip_serializing: bool, skip_serializing: bool,
skip_deserializing: bool, skip_deserializing: bool,
skip_serializing_if: Option<syn::Path>, skip_serializing_if: Option<syn::Path>,
@ -571,11 +668,17 @@ impl Field {
default.set_if_none(Default::Default); 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 { Field {
name: Name { name: Name {
serialize: ser_name.get().unwrap_or_else(|| ident.clone()), serialize: ser_name.unwrap_or_else(|| ident.clone()),
deserialize: de_name.get().unwrap_or(ident), deserialize: de_name.unwrap_or(ident),
}, },
ser_renamed: ser_renamed,
de_renamed: de_renamed,
skip_serializing: skip_serializing.get(), skip_serializing: skip_serializing.get(),
skip_deserializing: skip_deserializing.get(), skip_deserializing: skip_deserializing.get(),
skip_serializing_if: skip_serializing_if.get(), skip_serializing_if: skip_serializing_if.get(),
@ -591,6 +694,15 @@ impl Field {
&self.name &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 { pub fn skip_serializing(&self) -> bool {
self.skip_serializing self.skip_serializing
} }

View File

@ -1,4 +1,5 @@
extern crate syn; extern crate syn;
extern crate inflector;
pub mod ast; pub mod ast;
pub mod attr; pub mod attr;