spikard_cli/codegen/common/
case_conversion.rs1#[must_use]
30pub fn to_snake_case(s: &str) -> String {
31 if s.is_empty() {
32 return String::new();
33 }
34
35 let mut result = String::new();
36 let chars: Vec<char> = s.chars().collect();
37
38 for (i, &ch) in chars.iter().enumerate() {
39 if ch.is_uppercase() {
40 let should_add_underscore = if i == 0 {
42 false
44 } else if result.ends_with('_') {
45 false
47 } else {
48 let prev_is_lower = chars[i - 1].is_lowercase();
53 let prev_is_digit = chars[i - 1].is_numeric();
54 let next_is_lower = (i + 1 < chars.len()) && chars[i + 1].is_lowercase();
55
56 prev_is_lower || prev_is_digit || (i > 0 && chars[i - 1].is_uppercase() && next_is_lower)
57 };
58
59 if should_add_underscore {
60 result.push('_');
61 }
62 result.push_str(&ch.to_lowercase().to_string());
63 } else {
64 result.push(ch);
65 }
66 }
67
68 result
69}
70
71#[must_use]
92pub fn to_camel_case(s: &str) -> String {
93 if s.is_empty() {
94 return String::new();
95 }
96
97 let parts: Vec<&str> = s.split('_').collect();
98 if parts.is_empty() {
99 return String::new();
100 }
101
102 let has_leading_underscore = s.starts_with('_');
104 let has_trailing_underscore = s.ends_with('_');
105
106 let mut result = if has_leading_underscore {
107 String::from("_")
108 } else {
109 String::new()
110 };
111
112 let non_empty_parts: Vec<&str> = parts.iter().filter(|p| !p.is_empty()).copied().collect();
114
115 if non_empty_parts.is_empty() {
116 if has_trailing_underscore {
118 result.push('_');
119 }
120 return result;
121 }
122
123 result.push_str(non_empty_parts[0]);
125
126 for part in &non_empty_parts[1..] {
128 if let Some(first_char) = part.chars().next() {
129 result.push_str(&first_char.to_uppercase().to_string());
130 result.push_str(&part[first_char.len_utf8()..]);
131 }
132 }
133
134 if has_trailing_underscore {
135 result.push('_');
136 }
137
138 result
139}
140
141#[must_use]
162pub fn to_pascal_case(s: &str) -> String {
163 if s.is_empty() {
164 return String::new();
165 }
166
167 let parts: Vec<&str> = s.split(|c: char| !c.is_alphanumeric()).collect();
169
170 parts
171 .into_iter()
172 .filter(|p| !p.is_empty())
173 .map(|part| {
174 let mut chars = part.chars();
175 match chars.next() {
176 None => String::new(),
177 Some(first) => {
178 let mut result = first.to_uppercase().collect::<String>();
179 result.push_str(chars.as_str());
180 result
181 }
182 }
183 })
184 .collect()
185}
186
187#[must_use]
208pub fn to_kebab_case(s: &str) -> String {
209 if s.is_empty() {
210 return String::new();
211 }
212
213 let mut result = String::new();
214 let chars: Vec<char> = s.chars().collect();
215
216 for (i, &ch) in chars.iter().enumerate() {
217 if ch.is_uppercase() {
218 let should_add_hyphen = if i == 0 || result.ends_with('-') {
220 false
221 } else {
222 let prev_is_lower = chars[i - 1].is_lowercase();
223 let prev_is_digit = chars[i - 1].is_numeric();
224 let next_is_lower = (i + 1 < chars.len()) && chars[i + 1].is_lowercase();
225
226 prev_is_lower || prev_is_digit || (i > 0 && chars[i - 1].is_uppercase() && next_is_lower)
227 };
228
229 if should_add_hyphen {
230 result.push('-');
231 }
232 result.push_str(&ch.to_lowercase().to_string());
233 } else if ch == '_' {
234 if !result.ends_with('-') {
236 result.push('-');
237 }
238 } else {
239 result.push(ch);
240 }
241 }
242
243 result.trim_matches('-').to_string()
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
255 fn test_to_snake_case_simple() {
256 assert_eq!(to_snake_case("user"), "user");
257 assert_eq!(to_snake_case("name"), "name");
258 assert_eq!(to_snake_case("id"), "id");
259 }
260
261 #[test]
262 fn test_to_snake_case_camel_case() {
263 assert_eq!(to_snake_case("getUser"), "get_user");
264 assert_eq!(to_snake_case("userName"), "user_name");
265 assert_eq!(to_snake_case("userId"), "user_id");
266 }
267
268 #[test]
269 fn test_to_snake_case_pascal_case() {
270 assert_eq!(to_snake_case("GetUser"), "get_user");
271 assert_eq!(to_snake_case("UserName"), "user_name");
272 assert_eq!(to_snake_case("CreateUserProfile"), "create_user_profile");
273 }
274
275 #[test]
276 fn test_to_snake_case_acronyms() {
277 assert_eq!(to_snake_case("HTTPServer"), "http_server");
278 assert_eq!(to_snake_case("GraphQLType"), "graph_ql_type"); assert_eq!(to_snake_case("XMLHttpRequest"), "xml_http_request");
280 assert_eq!(to_snake_case("IOError"), "io_error");
281 assert_eq!(to_snake_case("URLPath"), "url_path");
282 }
283
284 #[test]
285 fn test_to_snake_case_consecutive_caps() {
286 assert_eq!(to_snake_case("ID"), "id");
287 assert_eq!(to_snake_case("HTTPSConnection"), "https_connection");
288 assert_eq!(to_snake_case("JSONData"), "json_data");
289 }
290
291 #[test]
292 fn test_to_snake_case_leading_underscore() {
293 assert_eq!(to_snake_case("_id"), "_id");
294 assert_eq!(to_snake_case("_private"), "_private");
295 assert_eq!(to_snake_case("_getUser"), "_get_user");
296 }
297
298 #[test]
299 fn test_to_snake_case_trailing_underscore() {
300 assert_eq!(to_snake_case("id_"), "id_");
301 assert_eq!(to_snake_case("name_"), "name_");
302 assert_eq!(to_snake_case("getUserName_"), "get_user_name_");
303 }
304
305 #[test]
306 fn test_to_snake_case_already_snake_case() {
307 assert_eq!(to_snake_case("get_user"), "get_user");
308 assert_eq!(to_snake_case("create_user_profile"), "create_user_profile");
309 assert_eq!(to_snake_case("http_server"), "http_server");
310 }
311
312 #[test]
313 fn test_to_snake_case_mixed_separators() {
314 assert_eq!(to_snake_case("getUser_Name"), "get_user_name");
315 assert_eq!(to_snake_case("_private_field_"), "_private_field_");
316 }
317
318 #[test]
319 fn test_to_snake_case_numbers() {
320 assert_eq!(to_snake_case("user123"), "user123");
321 assert_eq!(to_snake_case("getUser123"), "get_user123");
322 assert_eq!(to_snake_case("User123Name"), "user123_name");
323 }
324
325 #[test]
326 fn test_to_snake_case_empty() {
327 assert_eq!(to_snake_case(""), "");
328 }
329
330 #[test]
331 fn test_to_snake_case_single_char() {
332 assert_eq!(to_snake_case("a"), "a");
333 assert_eq!(to_snake_case("A"), "a");
334 assert_eq!(to_snake_case("_"), "_");
335 }
336
337 #[test]
342 fn test_to_camel_case_simple() {
343 assert_eq!(to_camel_case("user"), "user");
344 assert_eq!(to_camel_case("name"), "name");
345 assert_eq!(to_camel_case("id"), "id");
346 }
347
348 #[test]
349 fn test_to_camel_case_snake_case() {
350 assert_eq!(to_camel_case("get_user"), "getUser");
351 assert_eq!(to_camel_case("user_name"), "userName");
352 assert_eq!(to_camel_case("user_id"), "userId");
353 }
354
355 #[test]
356 fn test_to_camel_case_multiple_words() {
357 assert_eq!(to_camel_case("create_user_profile"), "createUserProfile");
358 assert_eq!(to_camel_case("get_user_by_id"), "getUserById");
359 assert_eq!(to_camel_case("http_server_config"), "httpServerConfig");
360 }
361
362 #[test]
363 fn test_to_camel_case_pascal_case_input() {
364 assert_eq!(to_camel_case("GetUser"), "GetUser"); assert_eq!(to_camel_case("UserName"), "UserName");
366 }
367
368 #[test]
369 fn test_to_camel_case_leading_underscore() {
370 assert_eq!(to_camel_case("_id"), "_id");
371 assert_eq!(to_camel_case("_get_user"), "_getUser");
372 assert_eq!(to_camel_case("_private"), "_private");
373 }
374
375 #[test]
376 fn test_to_camel_case_trailing_underscore() {
377 assert_eq!(to_camel_case("id_"), "id_");
378 assert_eq!(to_camel_case("get_user_"), "getUser_");
379 }
380
381 #[test]
382 fn test_to_camel_case_consecutive_separators() {
383 assert_eq!(to_camel_case("get__user"), "getUser");
384 assert_eq!(to_camel_case("user___name"), "userName");
385 }
386
387 #[test]
388 fn test_to_camel_case_numbers() {
389 assert_eq!(to_camel_case("user_123"), "user123");
390 assert_eq!(to_camel_case("get_user_123"), "getUser123");
391 }
392
393 #[test]
394 fn test_to_camel_case_empty() {
395 assert_eq!(to_camel_case(""), "");
396 }
397
398 #[test]
399 fn test_to_camel_case_single_char() {
400 assert_eq!(to_camel_case("a"), "a");
401 assert_eq!(to_camel_case("_"), "__"); }
403
404 #[test]
409 fn test_to_pascal_case_simple() {
410 assert_eq!(to_pascal_case("user"), "User");
411 assert_eq!(to_pascal_case("name"), "Name");
412 assert_eq!(to_pascal_case("id"), "Id");
413 }
414
415 #[test]
416 fn test_to_pascal_case_snake_case() {
417 assert_eq!(to_pascal_case("get_user"), "GetUser");
418 assert_eq!(to_pascal_case("user_name"), "UserName");
419 assert_eq!(to_pascal_case("create_user_profile"), "CreateUserProfile");
420 }
421
422 #[test]
423 fn test_to_pascal_case_camel_case() {
424 assert_eq!(to_pascal_case("getUser"), "GetUser");
425 assert_eq!(to_pascal_case("userName"), "UserName");
426 assert_eq!(to_pascal_case("createUserProfile"), "CreateUserProfile");
427 }
428
429 #[test]
430 fn test_to_pascal_case_kebab_case() {
431 assert_eq!(to_pascal_case("get-user"), "GetUser");
432 assert_eq!(to_pascal_case("user-name"), "UserName");
433 assert_eq!(to_pascal_case("http-server"), "HttpServer");
434 }
435
436 #[test]
437 fn test_to_pascal_case_mixed_separators() {
438 assert_eq!(to_pascal_case("get_user-name"), "GetUserName");
439 assert_eq!(to_pascal_case("user-name_id"), "UserNameId");
440 }
441
442 #[test]
443 fn test_to_pascal_case_numbers() {
444 assert_eq!(to_pascal_case("user_123"), "User123");
445 assert_eq!(to_pascal_case("get_user_123"), "GetUser123");
446 assert_eq!(to_pascal_case("user123name"), "User123name");
447 }
448
449 #[test]
450 fn test_to_pascal_case_leading_trailing_separators() {
451 assert_eq!(to_pascal_case("_user"), "User"); assert_eq!(to_pascal_case("user_"), "User"); assert_eq!(to_pascal_case("_user_"), "User");
454 }
455
456 #[test]
457 fn test_to_pascal_case_empty() {
458 assert_eq!(to_pascal_case(""), "");
459 }
460
461 #[test]
462 fn test_to_pascal_case_single_char() {
463 assert_eq!(to_pascal_case("a"), "A");
464 assert_eq!(to_pascal_case("_"), "");
465 }
466
467 #[test]
468 fn test_to_pascal_case_already_pascal() {
469 assert_eq!(to_pascal_case("GetUser"), "GetUser");
470 assert_eq!(to_pascal_case("UserName"), "UserName");
471 assert_eq!(to_pascal_case("CreateUserProfile"), "CreateUserProfile");
472 }
473
474 #[test]
479 fn test_to_kebab_case_simple() {
480 assert_eq!(to_kebab_case("user"), "user");
481 assert_eq!(to_kebab_case("name"), "name");
482 assert_eq!(to_kebab_case("id"), "id");
483 }
484
485 #[test]
486 fn test_to_kebab_case_camel_case() {
487 assert_eq!(to_kebab_case("getUser"), "get-user");
488 assert_eq!(to_kebab_case("userName"), "user-name");
489 assert_eq!(to_kebab_case("createUserProfile"), "create-user-profile");
490 }
491
492 #[test]
493 fn test_to_kebab_case_pascal_case() {
494 assert_eq!(to_kebab_case("GetUser"), "get-user");
495 assert_eq!(to_kebab_case("UserName"), "user-name");
496 assert_eq!(to_kebab_case("CreateUserProfile"), "create-user-profile");
497 }
498
499 #[test]
500 fn test_to_kebab_case_snake_case() {
501 assert_eq!(to_kebab_case("get_user"), "get-user");
502 assert_eq!(to_kebab_case("user_name"), "user-name");
503 assert_eq!(to_kebab_case("http_server"), "http-server");
504 }
505
506 #[test]
507 fn test_to_kebab_case_acronyms() {
508 assert_eq!(to_kebab_case("HTTPServer"), "http-server");
509 assert_eq!(to_kebab_case("GraphQLType"), "graph-ql-type"); assert_eq!(to_kebab_case("XMLHttpRequest"), "xml-http-request");
511 }
512
513 #[test]
514 fn test_to_kebab_case_already_kebab_case() {
515 assert_eq!(to_kebab_case("get-user"), "get-user");
516 assert_eq!(to_kebab_case("user-name"), "user-name");
517 assert_eq!(to_kebab_case("http-server"), "http-server");
518 }
519
520 #[test]
521 fn test_to_kebab_case_numbers() {
522 assert_eq!(to_kebab_case("user123"), "user123");
523 assert_eq!(to_kebab_case("getUser123"), "get-user123");
524 assert_eq!(to_kebab_case("User123Name"), "user123-name");
525 }
526
527 #[test]
528 fn test_to_kebab_case_leading_trailing_hyphens() {
529 assert_eq!(to_kebab_case("getUser-"), "get-user");
531 assert_eq!(to_kebab_case("-getUser"), "get-user");
532 assert_eq!(to_kebab_case("-getUser-"), "get-user");
533 }
534
535 #[test]
536 fn test_to_kebab_case_empty() {
537 assert_eq!(to_kebab_case(""), "");
538 }
539
540 #[test]
541 fn test_to_kebab_case_single_char() {
542 assert_eq!(to_kebab_case("a"), "a");
543 assert_eq!(to_kebab_case("A"), "a");
544 }
545
546 #[test]
551 fn test_round_trip_snake_to_camel_to_snake() {
552 let original = "get_user_profile";
553 let camel = to_camel_case(original);
554 let back = to_snake_case(&camel);
555 assert_eq!(back, original);
556 }
557
558 #[test]
559 fn test_round_trip_snake_to_pascal_to_snake() {
560 let original = "get_user_profile";
561 let pascal = to_pascal_case(original);
562 let back = to_snake_case(&pascal);
563 assert_eq!(back, original);
564 }
565
566 #[test]
567 fn test_acronym_consistency() {
568 assert_eq!(to_snake_case("HTTPServer"), "http_server");
570 assert_eq!(to_camel_case("http_server"), "httpServer");
571 assert_eq!(to_pascal_case("http_server"), "HttpServer");
572 assert_eq!(to_kebab_case("HTTPServer"), "http-server");
573 }
574
575 #[test]
576 fn test_graphql_type_consistency() {
577 let graphql = "GraphQLType";
578 assert_eq!(to_snake_case(graphql), "graph_ql_type"); assert_eq!(to_camel_case("graph_ql_type"), "graphQlType");
580 assert_eq!(to_pascal_case("graph_ql_type"), "GraphQlType");
581 assert_eq!(to_kebab_case(graphql), "graph-ql-type");
582 }
583
584 #[test]
585 fn test_real_world_field_names() {
586 assert_eq!(to_snake_case("userId"), "user_id");
588 assert_eq!(to_snake_case("firstName"), "first_name");
589 assert_eq!(to_snake_case("lastName"), "last_name");
590 assert_eq!(to_snake_case("createdAt"), "created_at");
591 assert_eq!(to_snake_case("updatedAt"), "updated_at");
592
593 assert_eq!(to_pascal_case("user_id"), "UserId");
594 assert_eq!(to_pascal_case("first_name"), "FirstName");
595 assert_eq!(to_pascal_case("created_at"), "CreatedAt");
596 }
597
598 #[test]
599 fn test_edge_case_empty_parts() {
600 assert_eq!(to_camel_case("__"), "__");
602 assert_eq!(to_pascal_case("__"), "");
603 assert_eq!(to_snake_case("__"), "__");
604 assert_eq!(to_kebab_case("__"), "");
605 }
606}