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