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); + } + } +}