From fc94c5399a95f66a5038d27397cdadebc5a6ec43 Mon Sep 17 00:00:00 2001 From: Michael Mokrysz Date: Thu, 23 Feb 2017 18:57:58 +0000 Subject: [PATCH 1/8] 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; From 3b59d47e079f5810f4512fa114ff161a1fa33d4d Mon Sep 17 00:00:00 2001 From: Michael Mokrysz Date: Thu, 23 Feb 2017 23:12:48 +0000 Subject: [PATCH 2/8] Heavily refactored `rename_all` and switched to `SCREAMING_SNAKE_CASE`. --- serde_codegen_internals/src/ast.rs | 2 +- serde_codegen_internals/src/attr.rs | 103 ++++++++++++++++------------ 2 files changed, 60 insertions(+), 45 deletions(-) diff --git a/serde_codegen_internals/src/ast.rs b/serde_codegen_internals/src/ast.rs index 2a85c3a7..0a50691e 100644 --- a/serde_codegen_internals/src/ast.rs +++ b/serde_codegen_internals/src/ast.rs @@ -54,7 +54,7 @@ impl<'a> Item<'a> { 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()); diff --git a/serde_codegen_internals/src/attr.rs b/serde_codegen_internals/src/attr.rs index 9b181aa1..4393807c 100644 --- a/serde_codegen_internals/src/attr.rs +++ b/serde_codegen_internals/src/attr.rs @@ -3,6 +3,7 @@ use syn; use syn::MetaItem::{List, NameValue, Word}; use syn::NestedMetaItem::{Literal, MetaItem}; use inflector::Inflector; +use std::str::FromStr; // This module handles parsing of `#[serde(...)]` attributes. The entrypoints // are `attr::Item::from_ast`, `attr::Variant::from_ast`, and @@ -87,29 +88,45 @@ impl Name { } #[derive(Debug, PartialEq)] -pub enum RenameAll { +pub enum RenameRule { + /// Don't apply a default rename rule. None, - /// Rename fields to "PascalCase" style. By Rust coding standards this is the default. + /// Rename direct children to "PascalCase" style, as typically used for enum variants. 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. + /// Rename direct children to "kebabCase" style. CamelCase, + /// Rename direct children to "snake_case" style, as commonly used for fields. + SnakeCase, + /// Rename direct children to "SNAKE_CASE" style, as commonly used for constants. + ScreamingSnakeCase, + /// Rename direct children to "kebab-case" style. + KebabCase, } -impl RenameAll { +impl RenameRule { 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(), + RenameRule::None => name, + RenameRule::PascalCase => name.to_pascal_case(), + RenameRule::CamelCase => name.to_camel_case(), + RenameRule::SnakeCase => name.to_snake_case(), + RenameRule::ScreamingSnakeCase => name.to_screaming_snake_case(), + RenameRule::KebabCase => name.to_kebab_case(), + } + } +} + +impl FromStr for RenameRule { + type Err = String; + + fn from_str(rename_all_str: &str) -> Result { + match rename_all_str { + "PascalCase" => Ok(RenameRule::PascalCase), + "camelCase" => Ok(RenameRule::CamelCase), + "snake_case" => Ok(RenameRule::SnakeCase), + "SCREAMING_SNAKE_CASE" => Ok(RenameRule::ScreamingSnakeCase), + "kebab-case" => Ok(RenameRule::KebabCase), + other => Err(other.to_string()), } } } @@ -120,7 +137,7 @@ pub struct Item { name: Name, deny_unknown_fields: bool, default: Default, - rename_all: RenameAll, + rename_all: RenameRule, ser_bound: Option>, de_bound: Option>, tag: EnumTag, @@ -194,15 +211,12 @@ 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)) + match RenameRule::from_str(s.as_str()) { + Ok(rename_rule) => rename_all.set(rename_rule), + Err(other) => { + cx.error(format!("unknown rename rule for #[serde(rename_all \ + = {:?})]", + other)) } } } @@ -292,7 +306,8 @@ impl Item { content.set(s); } syn::Body::Struct(_) => { - cx.error("#[serde(content = \"...\")] can only be used on enums") + cx.error("#[serde(content = \"...\")] can only be used on \ + enums") } } } @@ -345,7 +360,10 @@ impl Item { EnumTag::External } (false, Some(tag), Some(content)) => { - EnumTag::Adjacent { tag: tag, content: content } + EnumTag::Adjacent { + tag: tag, + content: content, + } } (true, Some(_), Some(_)) => { cx.error("untagged enum cannot have #[serde(tag = \"...\", content = \"...\")]"); @@ -360,7 +378,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), + rename_all: rename_all.get().unwrap_or(RenameRule::None), ser_bound: ser_bound.get(), de_bound: de_bound.get(), tag: tag, @@ -371,7 +389,7 @@ impl Item { &self.name } - pub fn rename_all(&self) -> &RenameAll { + pub fn rename_all(&self) -> &RenameRule { &self.rename_all } @@ -402,7 +420,7 @@ pub struct Variant { name: Name, ser_renamed: bool, de_renamed: bool, - rename_all: RenameAll, + rename_all: RenameRule, skip_deserializing: bool, skip_serializing: bool, } @@ -437,15 +455,12 @@ impl Variant { // 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)) + match RenameRule::from_str(s.as_str()) { + Ok(rename_rule) => rename_all.set(rename_rule), + Err(other) => { + cx.error(format!("unknown rename rule for #[serde(rename_all \ + = {:?})]", + other)) } } } @@ -482,7 +497,7 @@ impl Variant { }, ser_renamed: ser_renamed, de_renamed: de_renamed, - rename_all: rename_all.get().unwrap_or(RenameAll::None), + rename_all: rename_all.get().unwrap_or(RenameRule::None), skip_deserializing: skip_deserializing.get(), skip_serializing: skip_serializing.get(), } @@ -492,7 +507,7 @@ impl Variant { &self.name } - pub fn rename_by_rule(&mut self, rename_rule: &RenameAll) { + pub fn rename_by_rule(&mut self, rename_rule: &RenameRule) { if !self.ser_renamed { self.name.serialize = rename_rule.apply(self.name.serialize.clone()); } @@ -501,7 +516,7 @@ impl Variant { } } - pub fn rename_all(&self) -> &RenameAll { + pub fn rename_all(&self) -> &RenameRule { &self.rename_all } @@ -694,7 +709,7 @@ impl Field { &self.name } - pub fn rename_by_rule(&mut self, rename_rule: &RenameAll) { + pub fn rename_by_rule(&mut self, rename_rule: &RenameRule) { if !self.ser_renamed { self.name.serialize = rename_rule.apply(self.name.serialize.clone()); } From 84915268ee1f50cf1316fe5184b9af14f95cfd90 Mon Sep 17 00:00:00 2001 From: Michael Mokrysz Date: Fri, 24 Feb 2017 01:55:31 +0000 Subject: [PATCH 3/8] Minor fixes for pull request. Cheers @dtolnay. --- serde_codegen_internals/src/attr.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/serde_codegen_internals/src/attr.rs b/serde_codegen_internals/src/attr.rs index 4393807c..76317b5a 100644 --- a/serde_codegen_internals/src/attr.rs +++ b/serde_codegen_internals/src/attr.rs @@ -93,11 +93,11 @@ pub enum RenameRule { None, /// Rename direct children to "PascalCase" style, as typically used for enum variants. PascalCase, - /// Rename direct children to "kebabCase" style. + /// Rename direct children to "camelCase" style. CamelCase, /// Rename direct children to "snake_case" style, as commonly used for fields. SnakeCase, - /// Rename direct children to "SNAKE_CASE" style, as commonly used for constants. + /// Rename direct children to "SCREAMING_SNAKE_CASE" style, as commonly used for constants. ScreamingSnakeCase, /// Rename direct children to "kebab-case" style. KebabCase, @@ -370,7 +370,7 @@ impl Item { EnumTag::External } }; - // @DEBUG + Item { name: Name { serialize: ser_name.get().unwrap_or_else(|| item.ident.to_string()), From 3308f81c3af90bdb68cb16615347e3fcd25e8df1 Mon Sep 17 00:00:00 2001 From: Michael Mokrysz Date: Sat, 25 Feb 2017 13:24:49 +0000 Subject: [PATCH 4/8] Saving progress on naming convention conversion code to try new Inflector changes. #788 --- serde_codegen_internals/Cargo.toml | 1 - serde_codegen_internals/src/ast.rs | 6 +- serde_codegen_internals/src/attr.rs | 83 ++++++++++++++++++++++----- serde_codegen_internals/src/lib.rs | 1 - serde_codegen_internals/tests/test.rs | 25 ++++++++ 5 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 serde_codegen_internals/tests/test.rs diff --git a/serde_codegen_internals/Cargo.toml b/serde_codegen_internals/Cargo.toml index 38c5c0e7..6e44c806 100644 --- a/serde_codegen_internals/Cargo.toml +++ b/serde_codegen_internals/Cargo.toml @@ -13,7 +13,6 @@ 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 0a50691e..a4d7c582 100644 --- a/serde_codegen_internals/src/ast.rs +++ b/serde_codegen_internals/src/ast.rs @@ -49,15 +49,15 @@ 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()); + variant.attrs.rename_by_rule(attrs.rename_all()).unwrap_or_else(|err| cx.error(err)); for ref mut field in &mut variant.fields { - field.attrs.rename_by_rule(variant.attrs.rename_all()); + field.attrs.rename_by_rule(variant.attrs.rename_all()).unwrap_or_else(|err| cx.error(err)); } } } Body::Struct(_, ref mut fields) => { for field in fields { - field.attrs.rename_by_rule(attrs.rename_all()); + field.attrs.rename_by_rule(attrs.rename_all()).unwrap_or_else(|err| cx.error(err)); } } } diff --git a/serde_codegen_internals/src/attr.rs b/serde_codegen_internals/src/attr.rs index 76317b5a..b3a5328d 100644 --- a/serde_codegen_internals/src/attr.rs +++ b/serde_codegen_internals/src/attr.rs @@ -2,7 +2,6 @@ use Ctxt; use syn; use syn::MetaItem::{List, NameValue, Word}; use syn::NestedMetaItem::{Literal, MetaItem}; -use inflector::Inflector; use std::str::FromStr; // This module handles parsing of `#[serde(...)]` attributes. The entrypoints @@ -104,15 +103,67 @@ pub enum RenameRule { } impl RenameRule { - pub fn apply(&self, name: String) -> String { - match *self { - RenameRule::None => name, - RenameRule::PascalCase => name.to_pascal_case(), - RenameRule::CamelCase => name.to_camel_case(), - RenameRule::SnakeCase => name.to_snake_case(), - RenameRule::ScreamingSnakeCase => name.to_screaming_snake_case(), - RenameRule::KebabCase => name.to_kebab_case(), + pub fn apply_to_variant(&self, variant_name: String) -> Result { + if *self == RenameRule::None { + return Ok(variant_name); } + let mut chars = variant_name.chars(); + let start = chars.next().unwrap(); + if start.is_lowercase() { + return Err(format!("#[serde(rename_all = \"...\")] expects enum variants to be \ + named in `PascalCase`.")); + } + Ok(self.apply_to_words(chars.fold(vec![start.to_lowercase().collect()], |mut words, c| { + if c.is_uppercase() { + words.push(c.to_lowercase().collect()); + } else { + words.last_mut().unwrap().push(c); + } + words + }))) + } + + pub fn apply_to_field(&self, field_name: String) -> Result { + if *self == RenameRule::None { + return Ok(field_name); + } + if field_name != field_name.to_lowercase() { + return Err(format!("#[serde(rename_all = \"...\")] expects fields to be named in \ + `snake_case`.")); + } + Ok(self.apply_to_words(field_name.split('_').map(|s| s.to_string()).collect())) + } + + fn apply_to_words(&self, lowercased_words: Vec) -> String { + match *self { + RenameRule::PascalCase => Self::capitalising_join(lowercased_words), + RenameRule::CamelCase => { + let mut iter = lowercased_words.into_iter(); + let mut first = iter.next().unwrap(); + first.push_str(Self::capitalising_join(iter.collect()).as_str()); + first + } + RenameRule::SnakeCase => Self::delimiting_join(lowercased_words, "_"), + RenameRule::ScreamingSnakeCase => Self::delimiting_join(lowercased_words, "_").to_uppercase(), + RenameRule::KebabCase => Self::delimiting_join(lowercased_words, "-"), + _ => unreachable!(), + } + .clone() + } + + fn delimiting_join(lowercased_words: Vec, delimiter: &str) -> String { + lowercased_words.join(delimiter) + } + + fn capitalising_join(lowercased_words: Vec) -> String { + lowercased_words.into_iter().map(Self::capitalise).collect() + } + + fn capitalise(lowercased_word: String) -> String { + let mut iter = lowercased_word.chars(); + let mut first: String = iter.next().unwrap().to_uppercase().collect(); + first.push_str(iter.collect::().as_str()); + first } } @@ -507,13 +558,14 @@ impl Variant { &self.name } - pub fn rename_by_rule(&mut self, rename_rule: &RenameRule) { + pub fn rename_by_rule(&mut self, rename_rule: &RenameRule) -> Result<(), String> { if !self.ser_renamed { - self.name.serialize = rename_rule.apply(self.name.serialize.clone()); + self.name.serialize = rename_rule.apply_to_variant(self.name.serialize.clone())?; } if !self.de_renamed { - self.name.deserialize = rename_rule.apply(self.name.deserialize.clone()); + self.name.deserialize = rename_rule.apply_to_variant(self.name.deserialize.clone())?; } + Ok(()) } pub fn rename_all(&self) -> &RenameRule { @@ -709,13 +761,14 @@ impl Field { &self.name } - pub fn rename_by_rule(&mut self, rename_rule: &RenameRule) { + pub fn rename_by_rule(&mut self, rename_rule: &RenameRule) -> Result<(), String> { if !self.ser_renamed { - self.name.serialize = rename_rule.apply(self.name.serialize.clone()); + self.name.serialize = rename_rule.apply_to_field(self.name.serialize.clone())?; } if !self.de_renamed { - self.name.deserialize = rename_rule.apply(self.name.deserialize.clone()); + self.name.deserialize = rename_rule.apply_to_field(self.name.deserialize.clone())?; } + Ok(()) } pub fn skip_serializing(&self) -> bool { diff --git a/serde_codegen_internals/src/lib.rs b/serde_codegen_internals/src/lib.rs index e2ffa899..cd998deb 100644 --- a/serde_codegen_internals/src/lib.rs +++ b/serde_codegen_internals/src/lib.rs @@ -1,5 +1,4 @@ extern crate syn; -extern crate inflector; pub mod ast; pub mod attr; diff --git a/serde_codegen_internals/tests/test.rs b/serde_codegen_internals/tests/test.rs new file mode 100644 index 00000000..f006be12 --- /dev/null +++ b/serde_codegen_internals/tests/test.rs @@ -0,0 +1,25 @@ +extern crate serde_codegen_internals; + +use serde_codegen_internals::attr::RenameRule; +use RenameRule::*; + +#[test] +fn test_rename_rule_variant_strs() { + let variants = vec!["Outcome", "VeryTastyVegetables", "A", "Z42", "bad_snake_case"]; + let variants_renamed_expected = vec![ + (PascalCase, vec![Ok("Outcome"), Ok("VeryTastyVegetables"), Ok("A"), Ok("Z42"), Err(())]), + (CamelCase, vec![Ok("outcome"), Ok("veryTastyVegetables"), Ok("a"), Ok("z42"), Err(())]), + (SnakeCase, vec![Ok("outcome"), Ok("very_tasty_vegetables"), Ok("a"), Ok("z42"), Err(())]), + (ScreamingSnakeCase, vec![Ok("OUTCOME"), Ok("VERY_TASTY_VEGETABLES"), Ok("A"), Ok("Z42"), Err(())]), + (KebabCase, vec![Ok("outcome"), Ok("very-tasty-vegetables"), Ok("a"), Ok("z42"), Err(())]), + ]; + + for variant in variants.iter() { + assert_eq!(RenameRule::None.apply_to_variant(variant.to_string()), Ok(variant.to_string())); + } + for (rule, expected) in variants_renamed_expected.into_iter().map(|(rule, expected)| (rule, expected.into_iter().map(|expect| expect.map(|str| str.to_string())))) { + for (variant, expected) in variants.iter().zip(expected) { + assert_eq!(rule.apply_to_variant(variant.to_string()).map_err(|_| ()), expected); + } + } +} From 7952bad41fbada10a17cc085d21c868953c44925 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 25 Feb 2017 10:38:45 -0800 Subject: [PATCH 5/8] Move case conversion to its own file --- serde_codegen_internals/src/attr.rs | 98 +---------------------------- serde_codegen_internals/src/case.rs | 97 ++++++++++++++++++++++++++++ serde_codegen_internals/src/lib.rs | 2 + 3 files changed, 101 insertions(+), 96 deletions(-) create mode 100644 serde_codegen_internals/src/case.rs diff --git a/serde_codegen_internals/src/attr.rs b/serde_codegen_internals/src/attr.rs index b3a5328d..4b0af171 100644 --- a/serde_codegen_internals/src/attr.rs +++ b/serde_codegen_internals/src/attr.rs @@ -12,6 +12,8 @@ use std::str::FromStr; // user will see errors simultaneously for all bad attributes in the crate // rather than just the first. +pub use case::RenameRule; + struct Attr<'c, T> { cx: &'c Ctxt, name: &'static str, @@ -86,102 +88,6 @@ impl Name { } } -#[derive(Debug, PartialEq)] -pub enum RenameRule { - /// Don't apply a default rename rule. - None, - /// Rename direct children to "PascalCase" style, as typically used for enum variants. - PascalCase, - /// Rename direct children to "camelCase" style. - CamelCase, - /// Rename direct children to "snake_case" style, as commonly used for fields. - SnakeCase, - /// Rename direct children to "SCREAMING_SNAKE_CASE" style, as commonly used for constants. - ScreamingSnakeCase, - /// Rename direct children to "kebab-case" style. - KebabCase, -} - -impl RenameRule { - pub fn apply_to_variant(&self, variant_name: String) -> Result { - if *self == RenameRule::None { - return Ok(variant_name); - } - let mut chars = variant_name.chars(); - let start = chars.next().unwrap(); - if start.is_lowercase() { - return Err(format!("#[serde(rename_all = \"...\")] expects enum variants to be \ - named in `PascalCase`.")); - } - Ok(self.apply_to_words(chars.fold(vec![start.to_lowercase().collect()], |mut words, c| { - if c.is_uppercase() { - words.push(c.to_lowercase().collect()); - } else { - words.last_mut().unwrap().push(c); - } - words - }))) - } - - pub fn apply_to_field(&self, field_name: String) -> Result { - if *self == RenameRule::None { - return Ok(field_name); - } - if field_name != field_name.to_lowercase() { - return Err(format!("#[serde(rename_all = \"...\")] expects fields to be named in \ - `snake_case`.")); - } - Ok(self.apply_to_words(field_name.split('_').map(|s| s.to_string()).collect())) - } - - fn apply_to_words(&self, lowercased_words: Vec) -> String { - match *self { - RenameRule::PascalCase => Self::capitalising_join(lowercased_words), - RenameRule::CamelCase => { - let mut iter = lowercased_words.into_iter(); - let mut first = iter.next().unwrap(); - first.push_str(Self::capitalising_join(iter.collect()).as_str()); - first - } - RenameRule::SnakeCase => Self::delimiting_join(lowercased_words, "_"), - RenameRule::ScreamingSnakeCase => Self::delimiting_join(lowercased_words, "_").to_uppercase(), - RenameRule::KebabCase => Self::delimiting_join(lowercased_words, "-"), - _ => unreachable!(), - } - .clone() - } - - fn delimiting_join(lowercased_words: Vec, delimiter: &str) -> String { - lowercased_words.join(delimiter) - } - - fn capitalising_join(lowercased_words: Vec) -> String { - lowercased_words.into_iter().map(Self::capitalise).collect() - } - - fn capitalise(lowercased_word: String) -> String { - let mut iter = lowercased_word.chars(); - let mut first: String = iter.next().unwrap().to_uppercase().collect(); - first.push_str(iter.collect::().as_str()); - first - } -} - -impl FromStr for RenameRule { - type Err = String; - - fn from_str(rename_all_str: &str) -> Result { - match rename_all_str { - "PascalCase" => Ok(RenameRule::PascalCase), - "camelCase" => Ok(RenameRule::CamelCase), - "snake_case" => Ok(RenameRule::SnakeCase), - "SCREAMING_SNAKE_CASE" => Ok(RenameRule::ScreamingSnakeCase), - "kebab-case" => Ok(RenameRule::KebabCase), - other => Err(other.to_string()), - } - } -} - /// Represents container (e.g. struct) attribute information #[derive(Debug)] pub struct Item { diff --git a/serde_codegen_internals/src/case.rs b/serde_codegen_internals/src/case.rs new file mode 100644 index 00000000..91044c68 --- /dev/null +++ b/serde_codegen_internals/src/case.rs @@ -0,0 +1,97 @@ +use std::str::FromStr; + +#[derive(Debug, PartialEq)] +pub enum RenameRule { + /// Don't apply a default rename rule. + None, + /// Rename direct children to "PascalCase" style, as typically used for enum variants. + PascalCase, + /// Rename direct children to "camelCase" style. + CamelCase, + /// Rename direct children to "snake_case" style, as commonly used for fields. + SnakeCase, + /// Rename direct children to "SCREAMING_SNAKE_CASE" style, as commonly used for constants. + ScreamingSnakeCase, + /// Rename direct children to "kebab-case" style. + KebabCase, +} + +impl RenameRule { + pub fn apply_to_variant(&self, variant_name: String) -> Result { + if *self == RenameRule::None { + return Ok(variant_name); + } + let mut chars = variant_name.chars(); + let start = chars.next().unwrap(); + if start.is_lowercase() { + return Err(format!("#[serde(rename_all = \"...\")] expects enum variants to be \ + named in `PascalCase`.")); + } + Ok(self.apply_to_words(chars.fold(vec![start.to_lowercase().collect()], |mut words, c| { + if c.is_uppercase() { + words.push(c.to_lowercase().collect()); + } else { + words.last_mut().unwrap().push(c); + } + words + }))) + } + + pub fn apply_to_field(&self, field_name: String) -> Result { + if *self == RenameRule::None { + return Ok(field_name); + } + if field_name != field_name.to_lowercase() { + return Err(format!("#[serde(rename_all = \"...\")] expects fields to be named in \ + `snake_case`.")); + } + Ok(self.apply_to_words(field_name.split('_').map(|s| s.to_string()).collect())) + } + + fn apply_to_words(&self, lowercased_words: Vec) -> String { + match *self { + RenameRule::PascalCase => Self::capitalising_join(lowercased_words), + RenameRule::CamelCase => { + let mut iter = lowercased_words.into_iter(); + let mut first = iter.next().unwrap(); + first.push_str(Self::capitalising_join(iter.collect()).as_str()); + first + } + RenameRule::SnakeCase => Self::delimiting_join(lowercased_words, "_"), + RenameRule::ScreamingSnakeCase => Self::delimiting_join(lowercased_words, "_").to_uppercase(), + RenameRule::KebabCase => Self::delimiting_join(lowercased_words, "-"), + _ => unreachable!(), + } + .clone() + } + + fn delimiting_join(lowercased_words: Vec, delimiter: &str) -> String { + lowercased_words.join(delimiter) + } + + fn capitalising_join(lowercased_words: Vec) -> String { + lowercased_words.into_iter().map(Self::capitalise).collect() + } + + fn capitalise(lowercased_word: String) -> String { + let mut iter = lowercased_word.chars(); + let mut first: String = iter.next().unwrap().to_uppercase().collect(); + first.push_str(iter.collect::().as_str()); + first + } +} + +impl FromStr for RenameRule { + type Err = String; + + fn from_str(rename_all_str: &str) -> Result { + match rename_all_str { + "PascalCase" => Ok(RenameRule::PascalCase), + "camelCase" => Ok(RenameRule::CamelCase), + "snake_case" => Ok(RenameRule::SnakeCase), + "SCREAMING_SNAKE_CASE" => Ok(RenameRule::ScreamingSnakeCase), + "kebab-case" => Ok(RenameRule::KebabCase), + other => Err(other.to_string()), + } + } +} diff --git a/serde_codegen_internals/src/lib.rs b/serde_codegen_internals/src/lib.rs index cd998deb..c5e8885c 100644 --- a/serde_codegen_internals/src/lib.rs +++ b/serde_codegen_internals/src/lib.rs @@ -5,3 +5,5 @@ pub mod attr; mod ctxt; pub use ctxt::Ctxt; + +mod case; From 06c631db053c4d4157d6d7dc994c78e9a6f30f45 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 25 Feb 2017 11:01:31 -0800 Subject: [PATCH 6/8] Bring in the tests --- serde_codegen_internals/src/case.rs | 21 +++++++++++++++++++++ serde_codegen_internals/tests/test.rs | 25 ------------------------- 2 files changed, 21 insertions(+), 25 deletions(-) delete mode 100644 serde_codegen_internals/tests/test.rs diff --git a/serde_codegen_internals/src/case.rs b/serde_codegen_internals/src/case.rs index 91044c68..2a702b1a 100644 --- a/serde_codegen_internals/src/case.rs +++ b/serde_codegen_internals/src/case.rs @@ -95,3 +95,24 @@ impl FromStr for RenameRule { } } } + +#[test] +fn test_rename_rule_variant_strs() { + let variants = vec!["Outcome", "VeryTastyVegetables", "A", "Z42", "bad_snake_case"]; + let variants_renamed_expected = vec![ + (PascalCase, vec![Ok("Outcome"), Ok("VeryTastyVegetables"), Ok("A"), Ok("Z42"), Err(())]), + (CamelCase, vec![Ok("outcome"), Ok("veryTastyVegetables"), Ok("a"), Ok("z42"), Err(())]), + (SnakeCase, vec![Ok("outcome"), Ok("very_tasty_vegetables"), Ok("a"), Ok("z42"), Err(())]), + (ScreamingSnakeCase, vec![Ok("OUTCOME"), Ok("VERY_TASTY_VEGETABLES"), Ok("A"), Ok("Z42"), Err(())]), + (KebabCase, vec![Ok("outcome"), Ok("very-tasty-vegetables"), Ok("a"), Ok("z42"), Err(())]), + ]; + + for variant in variants.iter() { + assert_eq!(RenameRule::None.apply_to_variant(variant.to_string()), Ok(variant.to_string())); + } + for (rule, expected) in variants_renamed_expected.into_iter().map(|(rule, expected)| (rule, expected.into_iter().map(|expect| expect.map(|str| str.to_string())))) { + for (variant, expected) in variants.iter().zip(expected) { + assert_eq!(rule.apply_to_variant(variant.to_string()).map_err(|_| ()), expected); + } + } +} diff --git a/serde_codegen_internals/tests/test.rs b/serde_codegen_internals/tests/test.rs deleted file mode 100644 index f006be12..00000000 --- a/serde_codegen_internals/tests/test.rs +++ /dev/null @@ -1,25 +0,0 @@ -extern crate serde_codegen_internals; - -use serde_codegen_internals::attr::RenameRule; -use RenameRule::*; - -#[test] -fn test_rename_rule_variant_strs() { - let variants = vec!["Outcome", "VeryTastyVegetables", "A", "Z42", "bad_snake_case"]; - let variants_renamed_expected = vec![ - (PascalCase, vec![Ok("Outcome"), Ok("VeryTastyVegetables"), Ok("A"), Ok("Z42"), Err(())]), - (CamelCase, vec![Ok("outcome"), Ok("veryTastyVegetables"), Ok("a"), Ok("z42"), Err(())]), - (SnakeCase, vec![Ok("outcome"), Ok("very_tasty_vegetables"), Ok("a"), Ok("z42"), Err(())]), - (ScreamingSnakeCase, vec![Ok("OUTCOME"), Ok("VERY_TASTY_VEGETABLES"), Ok("A"), Ok("Z42"), Err(())]), - (KebabCase, vec![Ok("outcome"), Ok("very-tasty-vegetables"), Ok("a"), Ok("z42"), Err(())]), - ]; - - for variant in variants.iter() { - assert_eq!(RenameRule::None.apply_to_variant(variant.to_string()), Ok(variant.to_string())); - } - for (rule, expected) in variants_renamed_expected.into_iter().map(|(rule, expected)| (rule, expected.into_iter().map(|expect| expect.map(|str| str.to_string())))) { - for (variant, expected) in variants.iter().zip(expected) { - assert_eq!(rule.apply_to_variant(variant.to_string()).map_err(|_| ()), expected); - } - } -} From 17279e8a4ff86b7cac2873beeffee38d6fbc60bb Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 25 Feb 2017 11:00:47 -0800 Subject: [PATCH 7/8] Simplify case conversion implementation --- serde_codegen_internals/src/ast.rs | 6 +- serde_codegen_internals/src/attr.rs | 14 ++- serde_codegen_internals/src/case.rs | 153 ++++++++++++++-------------- 3 files changed, 84 insertions(+), 89 deletions(-) diff --git a/serde_codegen_internals/src/ast.rs b/serde_codegen_internals/src/ast.rs index a4d7c582..0a50691e 100644 --- a/serde_codegen_internals/src/ast.rs +++ b/serde_codegen_internals/src/ast.rs @@ -49,15 +49,15 @@ 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()).unwrap_or_else(|err| cx.error(err)); + 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()).unwrap_or_else(|err| cx.error(err)); + 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()).unwrap_or_else(|err| cx.error(err)); + field.attrs.rename_by_rule(attrs.rename_all()); } } } diff --git a/serde_codegen_internals/src/attr.rs b/serde_codegen_internals/src/attr.rs index 4b0af171..461907a8 100644 --- a/serde_codegen_internals/src/attr.rs +++ b/serde_codegen_internals/src/attr.rs @@ -464,14 +464,13 @@ impl Variant { &self.name } - pub fn rename_by_rule(&mut self, rename_rule: &RenameRule) -> Result<(), String> { + pub fn rename_by_rule(&mut self, rule: &RenameRule) { if !self.ser_renamed { - self.name.serialize = rename_rule.apply_to_variant(self.name.serialize.clone())?; + self.name.serialize = rule.apply_to_variant(&self.name.serialize); } if !self.de_renamed { - self.name.deserialize = rename_rule.apply_to_variant(self.name.deserialize.clone())?; + self.name.deserialize = rule.apply_to_variant(&self.name.deserialize); } - Ok(()) } pub fn rename_all(&self) -> &RenameRule { @@ -667,14 +666,13 @@ impl Field { &self.name } - pub fn rename_by_rule(&mut self, rename_rule: &RenameRule) -> Result<(), String> { + pub fn rename_by_rule(&mut self, rule: &RenameRule) { if !self.ser_renamed { - self.name.serialize = rename_rule.apply_to_field(self.name.serialize.clone())?; + self.name.serialize = rule.apply_to_field(&self.name.serialize); } if !self.de_renamed { - self.name.deserialize = rename_rule.apply_to_field(self.name.deserialize.clone())?; + self.name.deserialize = rule.apply_to_field(&self.name.deserialize); } - Ok(()) } pub fn skip_serializing(&self) -> bool { diff --git a/serde_codegen_internals/src/case.rs b/serde_codegen_internals/src/case.rs index 2a702b1a..6351b6f3 100644 --- a/serde_codegen_internals/src/case.rs +++ b/serde_codegen_internals/src/case.rs @@ -1,5 +1,8 @@ +use std::ascii::AsciiExt; use std::str::FromStr; +use self::RenameRule::*; + #[derive(Debug, PartialEq)] pub enum RenameRule { /// Don't apply a default rename rule. @@ -17,67 +20,50 @@ pub enum RenameRule { } impl RenameRule { - pub fn apply_to_variant(&self, variant_name: String) -> Result { - if *self == RenameRule::None { - return Ok(variant_name); - } - let mut chars = variant_name.chars(); - let start = chars.next().unwrap(); - if start.is_lowercase() { - return Err(format!("#[serde(rename_all = \"...\")] expects enum variants to be \ - named in `PascalCase`.")); - } - Ok(self.apply_to_words(chars.fold(vec![start.to_lowercase().collect()], |mut words, c| { - if c.is_uppercase() { - words.push(c.to_lowercase().collect()); - } else { - words.last_mut().unwrap().push(c); - } - words - }))) - } - - pub fn apply_to_field(&self, field_name: String) -> Result { - if *self == RenameRule::None { - return Ok(field_name); - } - if field_name != field_name.to_lowercase() { - return Err(format!("#[serde(rename_all = \"...\")] expects fields to be named in \ - `snake_case`.")); - } - Ok(self.apply_to_words(field_name.split('_').map(|s| s.to_string()).collect())) - } - - fn apply_to_words(&self, lowercased_words: Vec) -> String { + pub fn apply_to_variant(&self, variant: &str) -> String { match *self { - RenameRule::PascalCase => Self::capitalising_join(lowercased_words), - RenameRule::CamelCase => { - let mut iter = lowercased_words.into_iter(); - let mut first = iter.next().unwrap(); - first.push_str(Self::capitalising_join(iter.collect()).as_str()); - first + None | PascalCase => variant.to_owned(), + CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..], + SnakeCase => { + let mut snake = String::new(); + for (i, ch) in variant.char_indices() { + if i > 0 && ch.is_uppercase() { + snake.push('_'); + } + snake.push(ch.to_ascii_lowercase()); } - RenameRule::SnakeCase => Self::delimiting_join(lowercased_words, "_"), - RenameRule::ScreamingSnakeCase => Self::delimiting_join(lowercased_words, "_").to_uppercase(), - RenameRule::KebabCase => Self::delimiting_join(lowercased_words, "-"), - _ => unreachable!(), + snake } - .clone() + ScreamingSnakeCase => SnakeCase.apply_to_variant(variant).to_ascii_uppercase(), + KebabCase => SnakeCase.apply_to_variant(variant).replace('_', "-"), + } } - fn delimiting_join(lowercased_words: Vec, delimiter: &str) -> String { - lowercased_words.join(delimiter) - } - - fn capitalising_join(lowercased_words: Vec) -> String { - lowercased_words.into_iter().map(Self::capitalise).collect() - } - - fn capitalise(lowercased_word: String) -> String { - let mut iter = lowercased_word.chars(); - let mut first: String = iter.next().unwrap().to_uppercase().collect(); - first.push_str(iter.collect::().as_str()); - first + pub fn apply_to_field(&self, field: &str) -> String { + match *self { + None | SnakeCase => field.to_owned(), + PascalCase => { + let mut pascal = String::new(); + let mut capitalize = true; + for ch in field.chars() { + if ch == '_' { + capitalize = true; + } else if capitalize { + pascal.push(ch.to_ascii_uppercase()); + capitalize = false; + } else { + pascal.push(ch); + } + } + pascal + } + CamelCase => { + let pascal = PascalCase.apply_to_field(field); + pascal[..1].to_ascii_lowercase() + &pascal[1..] + } + ScreamingSnakeCase => field.to_ascii_uppercase(), + KebabCase => field.replace('_', "-"), + } } } @@ -86,33 +72,44 @@ impl FromStr for RenameRule { fn from_str(rename_all_str: &str) -> Result { match rename_all_str { - "PascalCase" => Ok(RenameRule::PascalCase), - "camelCase" => Ok(RenameRule::CamelCase), - "snake_case" => Ok(RenameRule::SnakeCase), - "SCREAMING_SNAKE_CASE" => Ok(RenameRule::ScreamingSnakeCase), - "kebab-case" => Ok(RenameRule::KebabCase), + "PascalCase" => Ok(PascalCase), + "camelCase" => Ok(CamelCase), + "snake_case" => Ok(SnakeCase), + "SCREAMING_SNAKE_CASE" => Ok(ScreamingSnakeCase), + "kebab-case" => Ok(KebabCase), other => Err(other.to_string()), } } } #[test] -fn test_rename_rule_variant_strs() { - let variants = vec!["Outcome", "VeryTastyVegetables", "A", "Z42", "bad_snake_case"]; - let variants_renamed_expected = vec![ - (PascalCase, vec![Ok("Outcome"), Ok("VeryTastyVegetables"), Ok("A"), Ok("Z42"), Err(())]), - (CamelCase, vec![Ok("outcome"), Ok("veryTastyVegetables"), Ok("a"), Ok("z42"), Err(())]), - (SnakeCase, vec![Ok("outcome"), Ok("very_tasty_vegetables"), Ok("a"), Ok("z42"), Err(())]), - (ScreamingSnakeCase, vec![Ok("OUTCOME"), Ok("VERY_TASTY_VEGETABLES"), Ok("A"), Ok("Z42"), Err(())]), - (KebabCase, vec![Ok("outcome"), Ok("very-tasty-vegetables"), Ok("a"), Ok("z42"), Err(())]), - ]; - - for variant in variants.iter() { - assert_eq!(RenameRule::None.apply_to_variant(variant.to_string()), Ok(variant.to_string())); - } - for (rule, expected) in variants_renamed_expected.into_iter().map(|(rule, expected)| (rule, expected.into_iter().map(|expect| expect.map(|str| str.to_string())))) { - for (variant, expected) in variants.iter().zip(expected) { - assert_eq!(rule.apply_to_variant(variant.to_string()).map_err(|_| ()), expected); - } +fn rename_variants() { + for &(original, camel, snake, screaming, kebab) in + &[("Outcome", "outcome", "outcome", "OUTCOME", "outcome"), + ("VeryTasty", "veryTasty", "very_tasty", "VERY_TASTY", "very-tasty"), + ("A", "a", "a", "A", "a"), + ("Z42", "z42", "z42", "Z42", "z42")] { + assert_eq!(None.apply_to_variant(original), original); + assert_eq!(PascalCase.apply_to_variant(original), original); + assert_eq!(CamelCase.apply_to_variant(original), camel); + assert_eq!(SnakeCase.apply_to_variant(original), snake); + assert_eq!(ScreamingSnakeCase.apply_to_variant(original), screaming); + assert_eq!(KebabCase.apply_to_variant(original), kebab); + } +} + +#[test] +fn rename_fields() { + for &(original, pascal, camel, screaming, kebab) in + &[("outcome", "Outcome", "outcome", "OUTCOME", "outcome"), + ("very_tasty", "VeryTasty", "veryTasty", "VERY_TASTY", "very-tasty"), + ("a", "A", "a", "A", "a"), + ("z42", "Z42", "z42", "Z42", "z42")] { + assert_eq!(None.apply_to_field(original), original); + assert_eq!(PascalCase.apply_to_field(original), pascal); + assert_eq!(CamelCase.apply_to_field(original), camel); + assert_eq!(SnakeCase.apply_to_field(original), original); + assert_eq!(ScreamingSnakeCase.apply_to_field(original), screaming); + assert_eq!(KebabCase.apply_to_field(original), kebab); } } From 47efbc6d758e50cc9c1ead8e002352def4e7c1a4 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 25 Feb 2017 11:31:00 -0800 Subject: [PATCH 8/8] Don't need to pass back the input here --- serde_codegen_internals/src/attr.rs | 12 ++++++------ serde_codegen_internals/src/case.rs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/serde_codegen_internals/src/attr.rs b/serde_codegen_internals/src/attr.rs index 461907a8..92a183c4 100644 --- a/serde_codegen_internals/src/attr.rs +++ b/serde_codegen_internals/src/attr.rs @@ -168,12 +168,12 @@ 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) { - match RenameRule::from_str(s.as_str()) { + match RenameRule::from_str(&s) { Ok(rename_rule) => rename_all.set(rename_rule), - Err(other) => { + Err(()) => { cx.error(format!("unknown rename rule for #[serde(rename_all \ = {:?})]", - other)) + s)) } } } @@ -412,12 +412,12 @@ impl Variant { // 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) { - match RenameRule::from_str(s.as_str()) { + match RenameRule::from_str(&s) { Ok(rename_rule) => rename_all.set(rename_rule), - Err(other) => { + Err(()) => { cx.error(format!("unknown rename rule for #[serde(rename_all \ = {:?})]", - other)) + s)) } } } diff --git a/serde_codegen_internals/src/case.rs b/serde_codegen_internals/src/case.rs index 6351b6f3..d25d98df 100644 --- a/serde_codegen_internals/src/case.rs +++ b/serde_codegen_internals/src/case.rs @@ -68,7 +68,7 @@ impl RenameRule { } impl FromStr for RenameRule { - type Err = String; + type Err = (); fn from_str(rename_all_str: &str) -> Result { match rename_all_str { @@ -77,7 +77,7 @@ impl FromStr for RenameRule { "snake_case" => Ok(SnakeCase), "SCREAMING_SNAKE_CASE" => Ok(ScreamingSnakeCase), "kebab-case" => Ok(KebabCase), - other => Err(other.to_string()), + _ => Err(()), } } }