From fc94c5399a95f66a5038d27397cdadebc5a6ec43 Mon Sep 17 00:00:00 2001 From: Michael Mokrysz Date: Thu, 23 Feb 2017 18:57:58 +0000 Subject: [PATCH] Implementing `rename_all` container attribute using `Inflector` trait. #140 --- serde_codegen_internals/Cargo.toml | 1 + serde_codegen_internals/src/ast.rs | 18 +++- serde_codegen_internals/src/attr.rs | 122 ++++++++++++++++++++++++++-- serde_codegen_internals/src/lib.rs | 1 + 4 files changed, 136 insertions(+), 6 deletions(-) diff --git a/serde_codegen_internals/Cargo.toml b/serde_codegen_internals/Cargo.toml index 6e44c806..38c5c0e7 100644 --- a/serde_codegen_internals/Cargo.toml +++ b/serde_codegen_internals/Cargo.toml @@ -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" } diff --git a/serde_codegen_internals/src/ast.rs b/serde_codegen_internals/src/ast.rs index ea598d80..2a85c3a7 100644 --- a/serde_codegen_internals/src/ast.rs +++ b/serde_codegen_internals/src/ast.rs @@ -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, diff --git a/serde_codegen_internals/src/attr.rs b/serde_codegen_internals/src/attr.rs index de7a997d..9b181aa1 100644 --- a/serde_codegen_internals/src/attr.rs +++ b/serde_codegen_internals/src/attr.rs @@ -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>, de_bound: Option>, 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, @@ -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 } diff --git a/serde_codegen_internals/src/lib.rs b/serde_codegen_internals/src/lib.rs index cd998deb..e2ffa899 100644 --- a/serde_codegen_internals/src/lib.rs +++ b/serde_codegen_internals/src/lib.rs @@ -1,4 +1,5 @@ extern crate syn; +extern crate inflector; pub mod ast; pub mod attr;