1use heck::{ToPascalCase, ToSnakeCase};
4
5pub fn to_struct_name(table_name: &str) -> String {
7 table_name.to_pascal_case()
8}
9
10pub fn to_field_name(column_name: &str) -> String {
12 column_name.to_snake_case()
13}
14
15pub fn to_enum_name(table_name: &str, column_name: &str) -> String {
18 format!(
19 "{}{}",
20 table_name.to_pascal_case(),
21 column_name.to_pascal_case()
22 )
23}
24
25pub fn generate_find_by_method_name(columns: &[String]) -> String {
28 let parts: Vec<String> = columns.iter().map(|c| c.to_snake_case()).collect();
29 format!("find_by_{}", parts.join("_and_"))
30}
31
32pub fn generate_find_by_list_method_name(column: &str) -> String {
36 let snake = column.to_snake_case();
37 let plural = pluralize(&snake);
38 if plural == snake {
39 format!("find_by_{}_list", snake)
41 } else {
42 format!("find_by_{}", plural)
43 }
44}
45
46pub fn generate_delete_by_method_name(columns: &[String]) -> String {
48 let parts: Vec<String> = columns.iter().map(|c| c.to_snake_case()).collect();
49 format!("delete_by_{}", parts.join("_and_"))
50}
51
52pub fn generate_update_by_method_name(columns: &[String]) -> String {
54 let parts: Vec<String> = columns.iter().map(|c| c.to_snake_case()).collect();
55 format!("update_by_{}", parts.join("_and_"))
56}
57
58pub fn generate_update_partial_by_method_name(columns: &[String]) -> String {
60 let parts: Vec<String> = columns.iter().map(|c| c.to_snake_case()).collect();
61 format!("update_partial_by_{}", parts.join("_and_"))
62}
63
64pub fn generate_count_by_method_name(columns: &[String]) -> String {
66 let parts: Vec<String> = columns.iter().map(|c| c.to_snake_case()).collect();
67 format!("count_by_{}", parts.join("_and_"))
68}
69
70pub fn to_enum_variant(value: &str) -> String {
73 let value = value.trim_matches('\'').trim_matches('"');
75
76 value.to_pascal_case()
78}
79
80pub fn pluralize(word: &str) -> String {
82 if word.is_empty() {
83 return word.to_string();
84 }
85
86 let irregulars: &[(&str, &str)] = &[
88 ("person", "people"),
89 ("child", "children"),
90 ("man", "men"),
91 ("woman", "women"),
92 ("foot", "feet"),
93 ("tooth", "teeth"),
94 ("mouse", "mice"),
95 ("index", "indices"),
96 ];
97
98 for (singular, plural) in irregulars {
99 if word == *singular {
100 return plural.to_string();
101 }
102 }
103
104 if word.ends_with("is") && word.len() > 2 {
106 return format!("{}es", &word[..word.len() - 2]);
107 }
108
109 if let Some(stripped) = word.strip_suffix("fe") {
111 return format!("{}ves", stripped);
112 }
113 let f_to_ves: &[&str] = &[
114 "leaf", "knife", "wife", "life", "shelf", "self", "half", "calf", "loaf", "thief",
115 ];
116 for &fword in f_to_ves {
117 if word == fword {
118 return format!("{}ves", &word[..word.len() - 1]);
119 }
120 }
121
122 let o_to_oes: &[&str] = &["hero", "potato", "tomato", "echo", "veto"];
124 for &oword in o_to_oes {
125 if word == oword {
126 return format!("{}es", word);
127 }
128 }
129
130 if word.ends_with("ed") && word.len() > 2 {
133 return word.to_string();
134 }
135
136 if word.ends_with("s")
138 || word.ends_with("x")
139 || word.ends_with("z")
140 || word.ends_with("ch")
141 || word.ends_with("sh")
142 {
143 return format!("{}es", word);
144 }
145
146 if word.ends_with("y") && word.len() > 1 {
148 let before_y = word.chars().nth(word.len() - 2).unwrap_or('_');
149 if !"aeiou".contains(before_y) {
150 return format!("{}ies", &word[..word.len() - 1]);
151 }
152 }
153
154 format!("{}s", word)
156}
157
158pub fn is_rust_keyword(name: &str) -> bool {
160 matches!(
161 name,
162 "as" | "async"
163 | "await"
164 | "break"
165 | "const"
166 | "continue"
167 | "crate"
168 | "dyn"
169 | "else"
170 | "enum"
171 | "extern"
172 | "false"
173 | "fn"
174 | "for"
175 | "if"
176 | "impl"
177 | "in"
178 | "let"
179 | "loop"
180 | "match"
181 | "mod"
182 | "move"
183 | "mut"
184 | "pub"
185 | "ref"
186 | "return"
187 | "self"
188 | "Self"
189 | "static"
190 | "struct"
191 | "super"
192 | "trait"
193 | "true"
194 | "type"
195 | "unsafe"
196 | "use"
197 | "where"
198 | "while"
199 | "abstract"
200 | "become"
201 | "box"
202 | "do"
203 | "final"
204 | "macro"
205 | "override"
206 | "priv"
207 | "try"
208 | "typeof"
209 | "unsized"
210 | "virtual"
211 | "yield"
212 )
213}
214
215pub fn escape_field_name(name: &str) -> String {
217 let snake = name.to_snake_case();
218 if is_rust_keyword(&snake) {
219 format!("r#{}", snake)
220 } else {
221 snake
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_to_struct_name() {
231 assert_eq!(to_struct_name("users"), "Users");
232 assert_eq!(to_struct_name("user_settings"), "UserSettings");
233 assert_eq!(to_struct_name("order_items"), "OrderItems");
234 }
235
236 #[test]
237 fn test_to_field_name() {
238 assert_eq!(to_field_name("userId"), "user_id");
239 assert_eq!(to_field_name("first_name"), "first_name");
240 assert_eq!(to_field_name("CreatedAt"), "created_at");
241 }
242
243 #[test]
244 fn test_to_enum_name() {
245 assert_eq!(to_enum_name("users", "status"), "UsersStatus");
246 assert_eq!(
247 to_enum_name("order_items", "payment_type"),
248 "OrderItemsPaymentType"
249 );
250 }
251
252 #[test]
253 fn test_to_enum_variant() {
254 assert_eq!(to_enum_variant("ACTIVE"), "Active");
255 assert_eq!(to_enum_variant("'active'"), "Active");
256 assert_eq!(to_enum_variant("IN_PROGRESS"), "InProgress");
257 assert_eq!(to_enum_variant("PendingReview"), "PendingReview");
258 }
259
260 #[test]
261 fn test_pluralize() {
262 assert_eq!(pluralize("id"), "ids");
264 assert_eq!(pluralize("user"), "users");
265 assert_eq!(pluralize("email"), "emails");
266
267 assert_eq!(pluralize("status"), "statuses");
269 assert_eq!(pluralize("box"), "boxes");
270 assert_eq!(pluralize("match"), "matches");
271 assert_eq!(pluralize("dish"), "dishes");
272
273 assert_eq!(pluralize("category"), "categories");
275 assert_eq!(pluralize("company"), "companies");
276 assert_eq!(pluralize("key"), "keys");
278 assert_eq!(pluralize("day"), "days");
279
280 assert_eq!(pluralize("analysis"), "analyses");
282 assert_eq!(pluralize("basis"), "bases");
283
284 assert_eq!(pluralize("leaf"), "leaves");
286 assert_eq!(pluralize("knife"), "knives");
287
288 assert_eq!(pluralize("person"), "people");
290 assert_eq!(pluralize("child"), "children");
291 assert_eq!(pluralize("index"), "indices");
292
293 assert_eq!(pluralize("hero"), "heroes");
295 assert_eq!(pluralize("photo"), "photos");
296
297 assert_eq!(pluralize("published"), "published");
299 assert_eq!(pluralize("deleted"), "deleted");
300 assert_eq!(pluralize("updated"), "updated");
301 }
302
303 #[test]
304 fn test_generate_find_by_method_name() {
305 assert_eq!(
306 generate_find_by_method_name(&["id".to_string()]),
307 "find_by_id"
308 );
309 assert_eq!(
310 generate_find_by_method_name(&["user_id".to_string(), "device_type".to_string()]),
311 "find_by_user_id_and_device_type"
312 );
313 }
314
315 #[test]
316 fn test_escape_field_name() {
317 assert_eq!(escape_field_name("type"), "r#type");
318 assert_eq!(escape_field_name("name"), "name");
319 assert_eq!(escape_field_name("async"), "r#async");
320 }
321}