pub fn to_snake_case(value: &str) -> String {
let mut output = String::new();
for (index, character) in value.chars().enumerate() {
if character.is_uppercase() {
if index > 0 {
output.push('_');
}
for lowercase in character.to_lowercase() {
output.push(lowercase);
}
} else {
output.push(character);
}
}
output
}
pub fn pluralize(value: &str) -> String {
if value.ends_with('s') {
format!("{value}es")
} else {
format!("{value}s")
}
}
pub fn model_route_segment(model_name: &str) -> String {
pluralize(&to_snake_case(model_name))
}
#[cfg(test)]
mod tests {
use super::*;
const CASES: &[(&str, &str, &str)] = &[
("UserGroup", "user_group", "user_groups"),
("User_Group", "user__group", "user__groups"),
("User_group", "user_group", "user_groups"),
("HTTPServer", "h_t_t_p_server", "h_t_t_p_servers"),
("User_", "user_", "user_s"),
("_User", "__user", "__users"),
("User__Group", "user___group", "user___groups"),
("User-Group", "user-_group", "user-_groups"),
("User Group", "user _group", "user _groups"),
("Bus", "bus", "buses"),
("Class", "class", "classes"),
("Category", "category", "categorys"),
("already_snake", "already_snake", "already_snakes"),
];
#[test]
fn to_snake_case_matches_table() {
for (input, expected_snake, _) in CASES {
assert_eq!(
to_snake_case(input),
*expected_snake,
"to_snake_case({input:?})"
);
}
}
#[test]
fn pluralize_matches_table() {
for (_, snake, expected_plural) in CASES {
assert_eq!(pluralize(snake), *expected_plural, "pluralize({snake:?})");
}
}
#[test]
fn model_route_segment_composes_snake_then_pluralize() {
for (input, _, expected_plural) in CASES {
assert_eq!(
model_route_segment(input),
*expected_plural,
"model_route_segment({input:?})"
);
}
}
#[test]
fn user_group_with_underscore_is_not_the_naive_tokenized_form() {
assert_ne!(model_route_segment("User_Group"), "user_groups");
assert_eq!(model_route_segment("User_Group"), "user__groups");
}
}