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