use super::tables::is_valid_graphql_identifier_pub as is_valid_graphql_identifier;
use crate::catalog::{FieldDefinition, TableDefinition};
use crate::expr::Idiom;
use crate::expr::part::Part;
pub(crate) fn to_camel_case(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut upper_next = false;
let mut first = true;
for ch in s.chars() {
if ch == '_' || ch == '-' {
upper_next = !first;
continue;
}
if first {
out.push(ch.to_ascii_lowercase());
first = false;
} else if upper_next {
out.push(ch.to_ascii_uppercase());
upper_next = false;
} else {
out.push(ch);
}
}
out
}
pub(crate) fn to_pascal_case(s: &str) -> String {
let camel = to_camel_case(s);
let mut chars = camel.chars();
match chars.next() {
Some(c) => c.to_ascii_uppercase().to_string() + chars.as_str(),
None => String::new(),
}
}
pub(crate) fn pluralize(s: &str) -> String {
if s.is_empty() {
return String::new();
}
let lower = s.to_ascii_lowercase();
if lower.ends_with('s') {
return s.to_string();
}
if lower.ends_with("ch")
|| lower.ends_with("sh")
|| lower.ends_with('x')
|| lower.ends_with('z')
{
return format!("{s}es");
}
if let Some(stripped) = lower.strip_suffix('y')
&& let Some(prev) = stripped.chars().last()
&& !matches!(prev, 'a' | 'e' | 'i' | 'o' | 'u')
{
let mut out: String = s[..s.len() - 1].to_string();
out.push_str("ies");
return out;
}
format!("{s}s")
}
pub(crate) fn table_base_name(tb: &TableDefinition) -> &str {
match tb.graphql_alias.as_deref() {
Some(alias) if is_valid_graphql_identifier(alias) => alias,
_ => tb.name.as_str(),
}
}
pub(crate) fn field_base_name(fd: &FieldDefinition) -> String {
if let Some(ref alias) = fd.graphql_alias
&& is_valid_graphql_identifier(alias)
{
return alias.clone();
}
idiom_to_default_name(&fd.name)
}
pub(crate) fn field_graphql_name(fd: &FieldDefinition) -> String {
field_base_name(fd)
}
pub(crate) fn list_field_name(tb: &TableDefinition) -> String {
let base = table_base_name(tb);
pluralize(&to_camel_case(base))
}
pub(crate) fn get_field_name(tb: &TableDefinition) -> String {
let base = table_base_name(tb);
if tb.graphql_alias.as_deref().is_some_and(is_valid_graphql_identifier) {
return base.to_string();
}
let camel = to_camel_case(base);
if pluralize(&camel) == camel {
format!("_get_{camel}")
} else {
camel
}
}
pub(crate) fn mutation_cap_name(tb: &TableDefinition) -> String {
let base = table_base_name(tb);
if tb.graphql_alias.as_deref().is_some_and(is_valid_graphql_identifier) {
let mut chars = base.chars();
return match chars.next() {
Some(c) => c.to_ascii_uppercase().to_string() + chars.as_str(),
None => String::new(),
};
}
to_pascal_case(base)
}
pub(crate) fn description_with_deprecation(
comment: Option<&str>,
deprecation: Option<&str>,
) -> Option<String> {
match (comment, deprecation) {
(Some(c), Some(r)) => Some(format!("[Deprecated: {r}]\n\n{c}")),
(None, Some(r)) => Some(format!("[Deprecated: {r}]")),
(Some(c), None) => Some(c.to_owned()),
(None, None) => None,
}
}
pub(crate) fn mutation_cap_plural_name(tb: &TableDefinition) -> String {
let cap = mutation_cap_name(tb);
let plural = pluralize(&cap);
if plural == cap {
format!("Many{cap}")
} else {
plural
}
}
fn idiom_to_default_name(idiom: &Idiom) -> String {
if idiom.0.len() == 1
&& let Part::Field(name) = &idiom.0[0]
{
return name.as_str().to_owned();
}
let raw = surrealdb_types::ToSql::to_sql(idiom);
let mut out = String::with_capacity(raw.len());
for (i, c) in raw.chars().enumerate() {
let ok = if i == 0 {
c.is_ascii_alphabetic() || c == '_'
} else {
c.is_ascii_alphanumeric() || c == '_'
};
out.push(if ok {
c
} else {
'_'
});
}
if out.is_empty() {
out.push('_');
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn camel_case_basics() {
assert_eq!(to_camel_case("first_name"), "firstName");
assert_eq!(to_camel_case("firstName"), "firstName");
assert_eq!(to_camel_case("a_b_c"), "aBC");
assert_eq!(to_camel_case("user-profile"), "userProfile");
assert_eq!(to_camel_case(""), "");
}
#[test]
fn pascal_case_basics() {
assert_eq!(to_pascal_case("first_name"), "FirstName");
assert_eq!(to_pascal_case("store"), "Store");
assert_eq!(to_pascal_case("customer_order"), "CustomerOrder");
}
#[test]
fn pluralization_rules() {
assert_eq!(pluralize("store"), "stores");
assert_eq!(pluralize("box"), "boxes");
assert_eq!(pluralize("watch"), "watches");
assert_eq!(pluralize("dish"), "dishes");
assert_eq!(pluralize("city"), "cities");
assert_eq!(pluralize("day"), "days"); assert_eq!(pluralize("Person"), "Persons"); assert_eq!(pluralize("orders"), "orders"); assert_eq!(pluralize(""), "");
}
}