1use self::RenameRule::*;
22use std::fmt::{self, Debug, Display};
23
24#[derive(Copy, Clone, PartialEq)]
26pub enum RenameRule {
27 None,
29 LowerCase,
31 UpperCase,
33 PascalCase,
36 CamelCase,
38 SnakeCase,
41 ScreamingSnakeCase,
44 KebabCase,
46 ScreamingKebabCase,
48}
49
50static RENAME_RULES: &[(&str, RenameRule)] = &[
51 ("lowercase", LowerCase),
52 ("UPPERCASE", UpperCase),
53 ("PascalCase", PascalCase),
54 ("camelCase", CamelCase),
55 ("snake_case", SnakeCase),
56 ("SCREAMING_SNAKE_CASE", ScreamingSnakeCase),
57 ("kebab-case", KebabCase),
58 ("SCREAMING-KEBAB-CASE", ScreamingKebabCase),
59];
60
61impl RenameRule {
62 pub fn from_str(rename_all_str: &str) -> Result<Self, ParseError<'_>> {
63 for (name, rule) in RENAME_RULES {
64 if rename_all_str == *name {
65 return Ok(*rule);
66 }
67 }
68 Err(ParseError {
69 unknown: rename_all_str,
70 })
71 }
72
73 pub fn apply_to_variant(self, variant: &str) -> String {
75 match self {
76 None | PascalCase => variant.to_owned(),
77 LowerCase => variant.to_ascii_lowercase(),
78 UpperCase => variant.to_ascii_uppercase(),
79 CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..],
80 SnakeCase => {
81 let mut snake = String::new();
82 for (i, ch) in variant.char_indices() {
83 if i > 0 && ch.is_uppercase() {
84 snake.push('_');
85 }
86 snake.push(ch.to_ascii_lowercase());
87 }
88 snake
89 }
90 ScreamingSnakeCase => SnakeCase.apply_to_variant(variant).to_ascii_uppercase(),
91 KebabCase => SnakeCase.apply_to_variant(variant).replace('_', "-"),
92 ScreamingKebabCase => ScreamingSnakeCase
93 .apply_to_variant(variant)
94 .replace('_', "-"),
95 }
96 }
97
98 pub fn apply_to_field(self, field: &str) -> String {
100 match self {
101 None | LowerCase | SnakeCase => field.to_owned(),
102 UpperCase => field.to_ascii_uppercase(),
103 PascalCase => {
104 let mut pascal = String::new();
105 let mut capitalize = true;
106 for ch in field.chars() {
107 if ch == '_' {
108 capitalize = true;
109 } else if capitalize {
110 pascal.push(ch.to_ascii_uppercase());
111 capitalize = false;
112 } else {
113 pascal.push(ch);
114 }
115 }
116 pascal
117 }
118 CamelCase => {
119 let pascal = PascalCase.apply_to_field(field);
120 pascal[..1].to_ascii_lowercase() + &pascal[1..]
121 }
122 ScreamingSnakeCase => field.to_ascii_uppercase(),
123 KebabCase => field.replace('_', "-"),
124 ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(field).replace('_', "-"),
125 }
126 }
127}
128
129pub struct ParseError<'a> {
130 unknown: &'a str,
131}
132
133impl Display for ParseError<'_> {
134 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135 f.write_str("unknown rename rule `rename_all = ")?;
136 Debug::fmt(self.unknown, f)?;
137 f.write_str("`, expected one of ")?;
138 for (i, (name, _rule)) in RENAME_RULES.iter().enumerate() {
139 if i > 0 {
140 f.write_str(", ")?;
141 }
142 Debug::fmt(name, f)?;
143 }
144 Ok(())
145 }
146}
147
148#[test]
149fn rename_variants() {
150 for &(original, lower, upper, camel, snake, screaming, kebab, screaming_kebab) in &[
151 (
152 "Outcome", "outcome", "OUTCOME", "outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
153 ),
154 (
155 "VeryTasty",
156 "verytasty",
157 "VERYTASTY",
158 "veryTasty",
159 "very_tasty",
160 "VERY_TASTY",
161 "very-tasty",
162 "VERY-TASTY",
163 ),
164 ("A", "a", "A", "a", "a", "A", "a", "A"),
165 ("Z42", "z42", "Z42", "z42", "z42", "Z42", "z42", "Z42"),
166 ] {
167 assert_eq!(None.apply_to_variant(original), original);
168 assert_eq!(LowerCase.apply_to_variant(original), lower);
169 assert_eq!(UpperCase.apply_to_variant(original), upper);
170 assert_eq!(PascalCase.apply_to_variant(original), original);
171 assert_eq!(CamelCase.apply_to_variant(original), camel);
172 assert_eq!(SnakeCase.apply_to_variant(original), snake);
173 assert_eq!(ScreamingSnakeCase.apply_to_variant(original), screaming);
174 assert_eq!(KebabCase.apply_to_variant(original), kebab);
175 assert_eq!(
176 ScreamingKebabCase.apply_to_variant(original),
177 screaming_kebab
178 );
179 }
180}
181
182#[test]
183fn rename_fields() {
184 for &(original, upper, pascal, camel, screaming, kebab, screaming_kebab) in &[
185 (
186 "outcome", "OUTCOME", "Outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
187 ),
188 (
189 "very_tasty",
190 "VERY_TASTY",
191 "VeryTasty",
192 "veryTasty",
193 "VERY_TASTY",
194 "very-tasty",
195 "VERY-TASTY",
196 ),
197 ("a", "A", "A", "a", "A", "a", "A"),
198 ("z42", "Z42", "Z42", "z42", "Z42", "z42", "Z42"),
199 ] {
200 assert_eq!(None.apply_to_field(original), original);
201 assert_eq!(UpperCase.apply_to_field(original), upper);
202 assert_eq!(PascalCase.apply_to_field(original), pascal);
203 assert_eq!(CamelCase.apply_to_field(original), camel);
204 assert_eq!(SnakeCase.apply_to_field(original), original);
205 assert_eq!(ScreamingSnakeCase.apply_to_field(original), screaming);
206 assert_eq!(KebabCase.apply_to_field(original), kebab);
207 assert_eq!(ScreamingKebabCase.apply_to_field(original), screaming_kebab);
208 }
209}