fn to_camel_case(field_name: &str) -> String {
let mut result = String::new();
let mut capitalize_next = false;
let mut in_first_word = true;
let chars: Vec<char> = field_name.chars().collect();
for (i, &ch) in chars.iter().enumerate() {
if ch == '_' {
capitalize_next = true;
in_first_word = false;
continue;
}
if in_first_word {
let next_is_lower = chars.get(i + 1).is_some_and(|c| c.is_lowercase());
if ch.is_uppercase() && next_is_lower && i > 0 {
in_first_word = false;
result.push(ch);
} else {
result.push(ch.to_ascii_lowercase());
}
continue;
}
if capitalize_next {
result.push(ch.to_ascii_uppercase());
capitalize_next = false;
continue;
}
result.push(ch);
}
result
}
fn to_snake_case(field_name: &str) -> String {
let mut result = String::new();
for (i, ch) in field_name.chars().enumerate() {
if ch.is_uppercase() && i > 0 {
result.push('_');
}
result.push(ch.to_ascii_lowercase());
}
result
}
fn to_kebab_case(field_name: &str) -> String {
let mut result = String::new();
for (i, ch) in field_name.chars().enumerate() {
if ch.is_uppercase() {
if i > 0 && !result.ends_with('-') {
result.push('-');
}
result.push(ch.to_ascii_lowercase());
} else if ch == '_' {
result.push('-');
} else {
result.push(ch);
}
}
result
}
fn to_pascal_case(field_name: &str) -> String {
let mut result = String::new();
let mut capitalize_next = true;
for ch in field_name.chars() {
if ch == '_' {
capitalize_next = true;
} else if capitalize_next {
result.push(ch.to_ascii_uppercase());
capitalize_next = false;
} else {
result.push(ch);
}
}
result
}
fn to_screaming_snake_case(field_name: &str) -> String {
if field_name.chars().all(|c| c.is_uppercase() || c == '_') && field_name.contains('_') {
return field_name.to_string();
}
let mut snake_case = String::new();
for (i, ch) in field_name.chars().enumerate() {
if ch.is_uppercase() && i > 0 && !snake_case.ends_with('_') {
snake_case.push('_');
}
if ch != '_' && ch != '-' {
snake_case.push(ch.to_ascii_lowercase());
} else if ch == '_' {
snake_case.push('_');
}
}
snake_case.to_uppercase()
}
fn to_screaming_kebab_case(field_name: &str) -> String {
let mut kebab_case = String::new();
for (i, ch) in field_name.chars().enumerate() {
if ch.is_uppercase() && i > 0 && !kebab_case.ends_with('-') && !kebab_case.ends_with('_') {
kebab_case.push('-');
}
if ch == '_' {
kebab_case.push('-');
} else if ch != '-' {
kebab_case.push(ch.to_ascii_lowercase());
} else {
kebab_case.push('-');
}
}
kebab_case.to_uppercase()
}
pub fn rename_field(field_name: &str, rename_all: Option<&str>) -> String {
match rename_all {
Some("camelCase") => to_camel_case(field_name),
Some("snake_case") => to_snake_case(field_name),
Some("kebab-case") => to_kebab_case(field_name),
Some("PascalCase") => to_pascal_case(field_name),
Some("lowercase") => field_name.to_lowercase(),
Some("UPPERCASE") => field_name.to_uppercase(),
Some("SCREAMING_SNAKE_CASE") => to_screaming_snake_case(field_name),
Some("SCREAMING-KEBAB-CASE") => to_screaming_kebab_case(field_name),
_ => field_name.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case("user_name", Some("camelCase"), "userName")]
#[case("first_name", Some("camelCase"), "firstName")]
#[case("last_name", Some("camelCase"), "lastName")]
#[case("user_id", Some("camelCase"), "userId")]
#[case("api_key", Some("camelCase"), "apiKey")]
#[case("already_camel", Some("camelCase"), "alreadyCamel")]
#[case("UserName", Some("camelCase"), "userName")]
#[case("UserCreated", Some("camelCase"), "userCreated")]
#[case("FirstName", Some("camelCase"), "firstName")]
#[case("ID", Some("camelCase"), "id")]
#[case("XMLParser", Some("camelCase"), "xmlParser")]
#[case("HTTPSConnection", Some("camelCase"), "httpsConnection")]
#[case("userName", Some("snake_case"), "user_name")]
#[case("firstName", Some("snake_case"), "first_name")]
#[case("lastName", Some("snake_case"), "last_name")]
#[case("userId", Some("snake_case"), "user_id")]
#[case("apiKey", Some("snake_case"), "api_key")]
#[case("already_snake", Some("snake_case"), "already_snake")]
#[case("user_name", Some("kebab-case"), "user-name")]
#[case("first_name", Some("kebab-case"), "first-name")]
#[case("last_name", Some("kebab-case"), "last-name")]
#[case("user_id", Some("kebab-case"), "user-id")]
#[case("api_key", Some("kebab-case"), "api-key")]
#[case("already-kebab", Some("kebab-case"), "already-kebab")]
#[case("user_name", Some("PascalCase"), "UserName")]
#[case("first_name", Some("PascalCase"), "FirstName")]
#[case("last_name", Some("PascalCase"), "LastName")]
#[case("user_id", Some("PascalCase"), "UserId")]
#[case("api_key", Some("PascalCase"), "ApiKey")]
#[case("AlreadyPascal", Some("PascalCase"), "AlreadyPascal")]
#[case("UserName", Some("lowercase"), "username")]
#[case("FIRST_NAME", Some("lowercase"), "first_name")]
#[case("lastName", Some("lowercase"), "lastname")]
#[case("User_ID", Some("lowercase"), "user_id")]
#[case("API_KEY", Some("lowercase"), "api_key")]
#[case("already_lower", Some("lowercase"), "already_lower")]
#[case("user_name", Some("UPPERCASE"), "USER_NAME")]
#[case("firstName", Some("UPPERCASE"), "FIRSTNAME")]
#[case("LastName", Some("UPPERCASE"), "LASTNAME")]
#[case("user_id", Some("UPPERCASE"), "USER_ID")]
#[case("apiKey", Some("UPPERCASE"), "APIKEY")]
#[case("ALREADY_UPPER", Some("UPPERCASE"), "ALREADY_UPPER")]
#[case("user_name", Some("SCREAMING_SNAKE_CASE"), "USER_NAME")]
#[case("firstName", Some("SCREAMING_SNAKE_CASE"), "FIRST_NAME")]
#[case("LastName", Some("SCREAMING_SNAKE_CASE"), "LAST_NAME")]
#[case("user_id", Some("SCREAMING_SNAKE_CASE"), "USER_ID")]
#[case("apiKey", Some("SCREAMING_SNAKE_CASE"), "API_KEY")]
#[case("ALREADY_SCREAMING", Some("SCREAMING_SNAKE_CASE"), "ALREADY_SCREAMING")]
#[case("user_name", Some("SCREAMING-KEBAB-CASE"), "USER-NAME")]
#[case("firstName", Some("SCREAMING-KEBAB-CASE"), "FIRST-NAME")]
#[case("LastName", Some("SCREAMING-KEBAB-CASE"), "LAST-NAME")]
#[case("user_id", Some("SCREAMING-KEBAB-CASE"), "USER-ID")]
#[case("apiKey", Some("SCREAMING-KEBAB-CASE"), "API-KEY")]
#[case("already-kebab", Some("SCREAMING-KEBAB-CASE"), "ALREADY-KEBAB")]
#[case("user_name", None, "user_name")]
#[case("firstName", None, "firstName")]
#[case("LastName", None, "LastName")]
#[case("user-id", None, "user-id")]
fn test_rename_field(
#[case] field_name: &str,
#[case] rename_all: Option<&str>,
#[case] expected: &str,
) {
assert_eq!(rename_field(field_name, rename_all), expected);
}
#[test]
fn test_rename_field_camelcase_with_digits() {
let result = rename_field("user_id_123", Some("camelCase"));
assert_eq!(result, "userId123");
let result = rename_field("get_user_by_id", Some("camelCase"));
assert_eq!(result, "getUserById");
}
#[test]
fn test_rename_field_unknown_format() {
let result = rename_field("my_field", Some("unknown_format"));
assert_eq!(result, "my_field");
let result = rename_field("myField", Some("invalid"));
assert_eq!(result, "myField");
let result = rename_field("test_name", Some("not_a_real_format"));
assert_eq!(result, "test_name");
}
#[rstest]
#[case("user_", Some("camelCase"), "user")]
#[case("_user", Some("camelCase"), "User")]
#[case("__user", Some("camelCase"), "User")]
#[case("user_", Some("PascalCase"), "User")]
#[case("user__name", Some("PascalCase"), "UserName")]
fn rename_case_separator_edges(
#[case] input: &str,
#[case] rename_all: Option<&str>,
#[case] expected: &str,
) {
assert_eq!(rename_field(input, rename_all), expected);
}
#[rstest]
#[case("USER", "U_S_E_R")]
#[case("USER_NAME", "USER_NAME")]
#[case("user_name", "USER_NAME")]
fn screaming_snake_cases(#[case] input: &str, #[case] expected: &str) {
assert_eq!(rename_field(input, Some("SCREAMING_SNAKE_CASE")), expected);
}
#[rstest]
#[case("User_Name", "USER-NAME")]
#[case("user__name", "USER--NAME")]
#[case("user-name", "USER-NAME")]
fn screaming_kebab_separator_cases(#[case] input: &str, #[case] expected: &str) {
assert_eq!(rename_field(input, Some("SCREAMING-KEBAB-CASE")), expected);
}
}