use crate::core::config::Language;
use heck::{ToKebabCase, ToLowerCamelCase, ToPascalCase, ToShoutySnakeCase, ToSnakeCase};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NameSurface {
PublicHost,
Wire,
InternalRust,
Abi,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IdentifierContext {
PublicType,
PublicMember,
PublicParameter,
PublicEnumVariant,
Wire,
InternalRust,
AbiSymbol,
SwiftSource,
SwiftRustShim,
KotlinSource,
KotlinRustBridge,
DartType,
DartValue,
DartTupleField,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PublicIdentifierKind {
Function,
Method,
Field,
Type,
EnumVariant,
Parameter,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NameCollision {
pub generated: String,
pub originals: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NameError {
InvalidIdentifier {
lang: Language,
context: IdentifierContext,
name: String,
},
Collision(NameCollision),
}
pub fn apply_serde_rename_all(name: &str, rename_all: Option<&str>) -> String {
match rename_all {
Some("lowercase") => name.to_ascii_lowercase(),
Some("UPPERCASE") => name.to_ascii_uppercase(),
Some("PascalCase") => name.to_pascal_case(),
Some("camelCase") => name.to_lower_camel_case(),
Some("snake_case") => pascal_to_snake(name),
Some("SCREAMING_SNAKE_CASE") => pascal_to_screaming_snake(name),
Some("kebab-case") => pascal_to_snake(name).to_kebab_case(),
Some("SCREAMING-KEBAB-CASE") => pascal_to_snake(name).to_kebab_case().to_ascii_uppercase(),
Some(_) | None => name.to_string(),
}
}
pub fn serde_wire_name(rust_name: &str, serde_rename: Option<&str>, rename_all: Option<&str>) -> String {
serde_rename
.map(str::to_string)
.unwrap_or_else(|| apply_serde_rename_all(rust_name, rename_all))
}
pub fn wire_field_name(field_name: &str, serde_rename: Option<&str>, rename_all: Option<&str>) -> String {
serde_wire_name(field_name, serde_rename, rename_all)
}
pub fn wire_variant_value(variant_name: &str, serde_rename: Option<&str>, rename_all: Option<&str>) -> String {
serde_wire_name(variant_name, serde_rename, rename_all)
}
pub fn public_field_name(lang: Language, rust_field_name: &str, rename_fields_value: Option<&str>) -> String {
let base = rename_fields_value.unwrap_or(rust_field_name);
public_host_identifier(lang, PublicIdentifierKind::Field, base)
}
pub fn public_host_identifier(lang: Language, kind: PublicIdentifierKind, rust_name: &str) -> String {
let converted = match kind {
PublicIdentifierKind::Type => public_type_name(lang, rust_name),
PublicIdentifierKind::EnumVariant => public_enum_variant_name(lang, rust_name),
PublicIdentifierKind::Function | PublicIdentifierKind::Method | PublicIdentifierKind::Field => {
public_member_name(lang, rust_name)
}
PublicIdentifierKind::Parameter => public_parameter_name(lang, rust_name),
};
escape_identifier_for(lang, &converted, public_identifier_context(kind))
}
pub fn internal_rust_identifier(name: &str) -> String {
crate::core::keywords::rust_raw_ident(name)
}
pub fn abi_symbol(prefix: &str, name: &str) -> String {
to_c_name(prefix, name)
}
pub fn escape_identifier(lang: Language, name: &str, surface: NameSurface) -> String {
let context = match surface {
NameSurface::PublicHost => IdentifierContext::PublicMember,
NameSurface::Wire => IdentifierContext::Wire,
NameSurface::InternalRust => IdentifierContext::InternalRust,
NameSurface::Abi => IdentifierContext::AbiSymbol,
};
escape_identifier_for(lang, name, context)
}
pub fn escape_identifier_for(lang: Language, name: &str, context: IdentifierContext) -> String {
match context {
IdentifierContext::Wire => name.to_string(),
IdentifierContext::InternalRust => crate::core::keywords::rust_raw_ident(name),
IdentifierContext::AbiSymbol => sanitize_symbol_component(name),
IdentifierContext::SwiftSource => crate::core::keywords::swift_case_ident(name),
IdentifierContext::SwiftRustShim => crate::core::keywords::swift_ident(name),
IdentifierContext::KotlinSource => backtick_keyword(lang, name),
IdentifierContext::KotlinRustBridge => crate::core::keywords::kotlin_ident(name),
IdentifierContext::DartType => dart_type_identifier(name, None),
IdentifierContext::DartValue => dart_value_identifier(name),
IdentifierContext::DartTupleField => dart_tuple_field_identifier(name),
IdentifierContext::PublicType
| IdentifierContext::PublicMember
| IdentifierContext::PublicParameter
| IdentifierContext::PublicEnumVariant => match lang {
Language::Swift => crate::core::keywords::swift_case_ident(name),
Language::Zig => crate::core::keywords::zig_ident(name),
Language::Python => crate::core::keywords::python_ident(name),
Language::Kotlin | Language::KotlinAndroid => crate::core::keywords::kotlin_ident(name),
Language::Dart => match context {
IdentifierContext::PublicType => dart_type_identifier(name, None),
IdentifierContext::PublicMember
| IdentifierContext::PublicParameter
| IdentifierContext::PublicEnumVariant => dart_value_identifier(name),
_ => unreachable!("matched public identifier contexts only"),
},
Language::Gleam => crate::core::keywords::gleam_ident(name),
_ if is_reserved_keyword(lang, name) => format!("{name}_"),
_ => name.to_string(),
},
}
}
pub fn is_valid_identifier(lang: Language, name: &str, surface: NameSurface) -> bool {
if matches!(surface, NameSurface::Wire) {
return !name.is_empty();
}
match lang {
Language::Rust => crate::core::keywords::is_valid_rust_ident_chars(name.trim_start_matches("r#")),
Language::Swift => {
let unescaped = name.strip_prefix('`').and_then(|s| s.strip_suffix('`')).unwrap_or(name);
is_ascii_identifier(unescaped)
}
Language::Zig => is_ascii_identifier(name) && !name.starts_with(|ch: char| ch.is_ascii_digit()),
Language::Csharp => {
let unescaped = name.strip_prefix('@').unwrap_or(name);
is_ascii_identifier(unescaped)
}
_ => is_ascii_identifier(name),
}
}
pub fn validate_identifier(lang: Language, name: &str, context: IdentifierContext) -> Result<(), NameError> {
if is_valid_identifier_for(lang, name, context) {
Ok(())
} else {
Err(NameError::InvalidIdentifier {
lang,
context,
name: name.to_string(),
})
}
}
pub fn is_valid_identifier_for(lang: Language, name: &str, context: IdentifierContext) -> bool {
match context {
IdentifierContext::Wire => !name.is_empty(),
IdentifierContext::InternalRust => {
crate::core::keywords::is_valid_rust_ident_chars(name.trim_start_matches("r#"))
}
IdentifierContext::AbiSymbol => is_ascii_identifier(name),
IdentifierContext::SwiftSource => {
let unescaped = name.strip_prefix('`').and_then(|s| s.strip_suffix('`')).unwrap_or(name);
is_ascii_identifier(unescaped)
}
IdentifierContext::DartTupleField => name.starts_with("field") && is_ascii_identifier(name),
_ => is_valid_identifier(lang, name, NameSurface::PublicHost),
}
}
pub fn dart_type_identifier(name: &str, parent: Option<&str>) -> String {
if is_dart_core_type(name) || is_reserved_keyword(Language::Dart, name) {
match parent {
Some(parent) if !parent.is_empty() => format!("{parent}{name}"),
_ => format!("{name}Node"),
}
} else {
name.to_string()
}
}
pub fn dart_value_identifier(name: &str) -> String {
if name.chars().next().is_some_and(|ch| ch.is_ascii_digit()) {
return format!("field{name}");
}
crate::core::keywords::dart_ident(name)
}
pub fn dart_tuple_field_identifier(name: &str) -> String {
if name.chars().next().is_some_and(|ch| ch.is_ascii_digit()) {
format!("field{name}")
} else {
dart_value_identifier(name)
}
}
pub fn abi_symbol_from_components<I, S>(components: I) -> String
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut parts = components
.into_iter()
.enumerate()
.map(|(idx, component)| {
let sanitized = sanitize_symbol_component(component.as_ref());
if idx == 0 {
sanitized
} else {
sanitized.trim_start_matches('_').to_string()
}
})
.filter(|component| !component.is_empty())
.collect::<Vec<_>>();
let symbol = parts.join("_");
if symbol.chars().next().is_some_and(|ch| ch.is_ascii_digit()) {
parts.insert(0, "_".to_string());
parts.join("_")
} else {
symbol
}
}
pub fn detect_name_collisions<I, O, G>(items: I, generate: G) -> Vec<NameCollision>
where
I: IntoIterator<Item = O>,
O: AsRef<str>,
G: Fn(&str) -> String,
{
let mut grouped: HashMap<String, Vec<String>> = HashMap::new();
for item in items {
let original = item.as_ref();
grouped
.entry(generate(original))
.or_default()
.push(original.to_string());
}
grouped
.into_iter()
.filter_map(|(generated, originals)| {
let unique: HashSet<_> = originals.iter().collect();
(unique.len() > 1).then_some(NameCollision { generated, originals })
})
.collect()
}
fn public_member_name(lang: Language, name: &str) -> String {
match lang {
Language::Python | Language::Ruby | Language::Elixir | Language::Ffi | Language::R | Language::Rust => {
name.to_snake_case()
}
Language::Go => to_go_name(name),
Language::Csharp => to_csharp_name(name),
Language::Node
| Language::Php
| Language::Wasm
| Language::Java
| Language::Kotlin
| Language::KotlinAndroid
| Language::Swift
| Language::Dart => name.to_lower_camel_case(),
Language::Gleam | Language::Zig | Language::C | Language::Jni => name.to_snake_case(),
}
}
fn public_parameter_name(lang: Language, name: &str) -> String {
match lang {
Language::Go => go_param_name(name),
_ => public_member_name(lang, name),
}
}
fn public_type_name(lang: Language, name: &str) -> String {
match lang {
Language::Go => go_type_name(&name.to_pascal_case()),
Language::Csharp => csharp_type_name(&name.to_pascal_case()),
Language::Python
| Language::Node
| Language::Ruby
| Language::Php
| Language::Elixir
| Language::Wasm
| Language::Java
| Language::Kotlin
| Language::KotlinAndroid
| Language::Swift
| Language::Dart
| Language::Gleam
| Language::Zig
| Language::Ffi
| Language::R
| Language::Rust
| Language::C
| Language::Jni => name.to_pascal_case(),
}
}
fn public_enum_variant_name(lang: Language, name: &str) -> String {
match lang {
Language::Python | Language::Ffi | Language::C | Language::Rust => pascal_to_screaming_snake(name),
Language::Ruby | Language::Elixir | Language::R | Language::Gleam | Language::Zig => pascal_to_snake(name),
Language::Go => go_type_name(&name.to_pascal_case()),
Language::Csharp => csharp_type_name(&name.to_pascal_case()),
Language::Node
| Language::Php
| Language::Wasm
| Language::Java
| Language::Kotlin
| Language::KotlinAndroid
| Language::Swift
| Language::Dart
| Language::Jni => name.to_pascal_case(),
}
}
fn public_identifier_context(kind: PublicIdentifierKind) -> IdentifierContext {
match kind {
PublicIdentifierKind::Function | PublicIdentifierKind::Method | PublicIdentifierKind::Field => {
IdentifierContext::PublicMember
}
PublicIdentifierKind::Type => IdentifierContext::PublicType,
PublicIdentifierKind::EnumVariant => IdentifierContext::PublicEnumVariant,
PublicIdentifierKind::Parameter => IdentifierContext::PublicParameter,
}
}
fn is_ascii_identifier(name: &str) -> bool {
if name.is_empty() {
return false;
}
let mut chars = name.chars();
let first = chars.next().expect("non-empty string has a first char");
if !first.is_ascii_alphabetic() && first != '_' {
return false;
}
chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
}
fn sanitize_symbol_component(name: &str) -> String {
let mut sanitized = String::with_capacity(name.len() + 1);
for ch in name.chars() {
if ch.is_ascii_alphanumeric() || ch == '_' {
sanitized.push(ch.to_ascii_lowercase());
} else {
sanitized.push('_');
}
}
while sanitized.contains("__") {
sanitized = sanitized.replace("__", "_");
}
let sanitized = sanitized.trim_matches('_').to_string();
if sanitized.chars().next().is_some_and(|ch| ch.is_ascii_digit()) {
format!("_{sanitized}")
} else {
sanitized
}
}
fn backtick_keyword(lang: Language, name: &str) -> String {
if is_reserved_keyword(lang, name) {
format!("`{name}`")
} else {
name.to_string()
}
}
fn is_dart_core_type(name: &str) -> bool {
const DART_CORE_TYPES: &[&str] = &[
"bool",
"double",
"Duration",
"Error",
"Exception",
"Future",
"int",
"Invocation",
"Iterable",
"Iterator",
"List",
"Map",
"MapEntry",
"Null",
"num",
"Object",
"Pattern",
"RegExp",
"RuneIterator",
"Runes",
"Set",
"Sink",
"StackTrace",
"Stream",
"String",
"StringBuffer",
"Symbol",
"Type",
"Uri",
];
DART_CORE_TYPES.contains(&name)
}
fn is_reserved_keyword(lang: Language, name: &str) -> bool {
match lang {
Language::Python => crate::core::keywords::PYTHON_KEYWORDS.contains(&name),
Language::Node | Language::Wasm => crate::core::keywords::JS_KEYWORDS.contains(&name),
Language::Ruby => crate::core::keywords::RUBY_KEYWORDS.contains(&name),
Language::Php => crate::core::keywords::PHP_KEYWORDS.contains(&name),
Language::Elixir => crate::core::keywords::ELIXIR_KEYWORDS.contains(&name),
Language::Go => crate::core::keywords::GO_KEYWORDS.contains(&name),
Language::Java | Language::Jni => crate::core::keywords::JAVA_KEYWORDS.contains(&name),
Language::Csharp => crate::core::keywords::CSHARP_KEYWORDS.contains(&name),
Language::R => crate::core::keywords::R_KEYWORDS.contains(&name),
Language::Kotlin | Language::KotlinAndroid => crate::core::keywords::KOTLIN_KEYWORDS.contains(&name),
Language::Swift => crate::core::keywords::SWIFT_KEYWORDS.contains(&name),
Language::Dart => crate::core::keywords::DART_KEYWORDS.contains(&name),
Language::Gleam => crate::core::keywords::GLEAM_KEYWORDS.contains(&name),
Language::Zig => crate::core::keywords::ZIG_KEYWORDS.contains(&name),
Language::Rust => crate::core::keywords::RUST_KEYWORDS.contains(&name),
Language::Ffi | Language::C => false,
}
}
pub fn to_python_name(name: &str) -> String {
name.to_snake_case()
}
pub fn to_node_name(name: &str) -> String {
name.to_lower_camel_case()
}
pub fn to_ruby_name(name: &str) -> String {
name.to_snake_case()
}
pub fn to_php_name(name: &str) -> String {
name.to_lower_camel_case()
}
pub fn to_elixir_name(name: &str) -> String {
name.to_snake_case()
}
const INITIALISMS: &[&str] = &[
"API", "ASCII", "CPU", "CSS", "DNS", "EOF", "FTP", "GID", "GraphQL", "GUI", "HTML", "HTTP", "HTTPS", "ID", "IMAP",
"IP", "JSON", "LHS", "MFA", "POP", "QPS", "RAM", "RHS", "RPC", "SLA", "SMTP", "SQL", "SSH", "SSL", "TCP", "TLS",
"TTL", "UDP", "UI", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS",
];
const CSHARP_INITIALISMS: &[&str] = &["GraphQL", "UUID"];
fn apply_initialisms(name: &str, list: &[&str]) -> String {
if name.is_empty() {
return name.to_string();
}
let mut words: Vec<&str> = Vec::new();
let mut word_start = 0;
let bytes = name.as_bytes();
for i in 1..bytes.len() {
if bytes[i].is_ascii_uppercase() {
words.push(&name[word_start..i]);
word_start = i;
}
}
words.push(&name[word_start..]);
let mut result = String::with_capacity(name.len());
let mut i = 0;
while i < words.len() {
let mut matched = false;
for span in (1..=(words.len() - i)).rev() {
let candidate: String = words[i..i + span].concat();
let candidate_upper = candidate.to_ascii_uppercase();
if let Some(&canonical) = list.iter().find(|&&s| s.to_ascii_uppercase() == candidate_upper) {
result.push_str(canonical);
i += span;
matched = true;
break;
}
}
if !matched {
result.push_str(words[i]);
i += 1;
}
}
result
}
fn apply_go_acronyms(name: &str) -> String {
apply_initialisms(name, INITIALISMS)
}
pub fn to_go_name(name: &str) -> String {
apply_go_acronyms(&name.to_pascal_case())
}
pub fn go_type_name(name: &str) -> String {
apply_go_acronyms(name)
}
pub fn go_param_name(name: &str) -> String {
let pascal = apply_go_acronyms(&name.to_pascal_case());
if pascal.is_empty() {
return pascal;
}
let bytes = pascal.as_bytes();
let first_lower = bytes.iter().position(|b| b.is_ascii_lowercase());
match first_lower {
None => {
pascal.to_lowercase()
}
Some(0) => {
pascal
}
Some(pos) => {
let word_end = if pos > 1 { pos - 1 } else { 1 };
let lower_prefix = pascal[..word_end].to_lowercase();
format!("{}{}", lower_prefix, &pascal[word_end..])
}
}
}
pub fn to_java_name(name: &str) -> String {
name.to_lower_camel_case()
}
pub fn to_csharp_name(name: &str) -> String {
apply_initialisms(&name.to_pascal_case(), CSHARP_INITIALISMS)
}
fn normalize_acronym_to_pascalcase(name: &str) -> String {
if name.is_empty() {
return name.to_string();
}
if name.len() >= 3 && name.chars().all(|c| c.is_ascii_uppercase()) {
let mut result = String::with_capacity(name.len());
result.push(name.chars().next().unwrap().to_ascii_uppercase());
result.extend(name.chars().skip(1).map(|c| c.to_ascii_lowercase()));
return result;
}
name.to_string()
}
pub fn csharp_type_name(name: &str) -> String {
let normalized = normalize_acronym_to_pascalcase(name);
apply_initialisms(&normalized, CSHARP_INITIALISMS)
}
pub fn to_c_name(prefix: &str, name: &str) -> String {
format!("{}_{}", prefix, name.to_snake_case())
}
pub fn to_class_name(name: &str) -> String {
name.to_pascal_case()
}
pub fn to_constant_name(name: &str) -> String {
name.to_shouty_snake_case()
}
pub fn pascal_to_snake(name: &str) -> String {
if name.is_empty() {
return String::new();
}
let chars: Vec<char> = name.chars().collect();
let n = chars.len();
let mut out = String::with_capacity(n + 4);
let mut i = 0;
while i < n {
let ch = chars[i];
if ch.is_ascii_uppercase() {
let run_start = i;
while i < n && chars[i].is_ascii_uppercase() {
i += 1;
}
let run_end = i;
let run_len = run_end - run_start;
if run_len == 1 {
if !out.is_empty() {
out.push('_');
}
out.extend(chars[run_start].to_lowercase());
} else {
let split = if i < n && chars[i].is_ascii_lowercase() {
run_len - 1
} else {
run_len
};
if !out.is_empty() {
out.push('_');
}
for &c in chars.iter().skip(run_start).take(split) {
out.extend(c.to_lowercase());
}
if split < run_len {
out.push('_');
out.extend(chars[run_start + split].to_lowercase());
}
}
} else {
out.push(ch);
i += 1;
}
}
out
}
pub fn pascal_to_screaming_snake(name: &str) -> String {
pascal_to_snake(name).to_ascii_uppercase()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::config::Language;
#[test]
fn serde_wire_name_applies_rename_all_strategies() {
let cases = [
("HttpStatus", Some("lowercase"), "httpstatus"),
("HttpStatus", Some("UPPERCASE"), "HTTPSTATUS"),
("http_status", Some("PascalCase"), "HttpStatus"),
("http_status", Some("camelCase"), "httpStatus"),
("HttpStatus", Some("snake_case"), "http_status"),
("HttpStatus", Some("SCREAMING_SNAKE_CASE"), "HTTP_STATUS"),
("HttpStatus", Some("kebab-case"), "http-status"),
("HttpStatus", Some("SCREAMING-KEBAB-CASE"), "HTTP-STATUS"),
("HttpStatus", None, "HttpStatus"),
("HttpStatus", Some("unknown"), "HttpStatus"),
("Rdfa", Some("snake_case"), "rdfa"),
("HTMLParser", Some("snake_case"), "html_parser"),
];
for (name, rename_all, expected) in cases {
assert_eq!(
apply_serde_rename_all(name, rename_all),
expected,
"rename_all={rename_all:?} name={name}"
);
}
}
#[test]
fn serde_rename_wins_over_rename_all() {
assert_eq!(
serde_wire_name("content_type", Some("Content-Type"), Some("camelCase")),
"Content-Type"
);
assert_eq!(
wire_variant_value("HttpStatus", Some("http-status"), Some("snake_case")),
"http-status"
);
assert_eq!(
wire_variant_value("HttpStatus", None, Some("snake_case")),
"http_status"
);
}
#[test]
fn public_identifiers_are_separate_from_wire_names() {
assert_eq!(
public_field_name(Language::Node, "content_type", Some("contentTypeOverride")),
"contentTypeOverride"
);
assert_eq!(
wire_field_name("content_type", Some("Content-Type"), Some("camelCase")),
"Content-Type"
);
}
#[test]
fn public_host_identifier_applies_language_casing_and_keywords() {
let cases = [
(Language::Python, PublicIdentifierKind::Field, "class", "class_"),
(
Language::Node,
PublicIdentifierKind::Function,
"request_url",
"requestUrl",
),
(
Language::Go,
PublicIdentifierKind::Function,
"request_url",
"RequestURL",
),
(
Language::Go,
PublicIdentifierKind::Parameter,
"request_url",
"requestURL",
),
(
Language::Csharp,
PublicIdentifierKind::Method,
"graphql_route",
"GraphQLRoute",
),
(
Language::Ruby,
PublicIdentifierKind::EnumVariant,
"HTTPStatus",
"http_status",
),
];
for (lang, kind, name, expected) in cases {
assert_eq!(public_host_identifier(lang, kind, name), expected);
}
}
#[test]
fn identifier_context_controls_escaping() {
assert_eq!(
escape_identifier_for(Language::Swift, "protocol", IdentifierContext::SwiftSource),
"`protocol`"
);
assert_eq!(
escape_identifier_for(Language::Swift, "protocol", IdentifierContext::SwiftRustShim),
"protocol_"
);
assert_eq!(
escape_identifier_for(Language::Kotlin, "object", IdentifierContext::KotlinSource),
"`object`"
);
assert_eq!(
escape_identifier_for(Language::Kotlin, "object", IdentifierContext::KotlinRustBridge),
"object_"
);
assert_eq!(
escape_identifier_for(Language::Rust, "type", IdentifierContext::InternalRust),
"r#type"
);
}
#[test]
fn dart_identifier_context_handles_core_types_and_tuple_fields() {
assert_eq!(dart_type_identifier("List", Some("NodeContent")), "NodeContentList");
assert_eq!(dart_type_identifier("List", None), "ListNode");
assert_eq!(dart_value_identifier("required"), "required_");
assert_eq!(dart_tuple_field_identifier("0"), "field0");
}
#[test]
fn abi_symbol_components_are_sanitized_and_joined() {
assert_eq!(
abi_symbol_from_components(["my-lib", "HTTPStatus", "Content-Type", "2xx"]),
"my_lib_httpstatus_content_type_2xx"
);
assert_eq!(abi_symbol("ffi", "HTTPStatus"), "ffi_http_status");
}
#[test]
fn identifier_validation_is_contextual() {
assert!(validate_identifier(Language::Swift, "`protocol`", IdentifierContext::SwiftSource).is_ok());
assert!(validate_identifier(Language::Rust, "r#type", IdentifierContext::InternalRust).is_ok());
assert!(validate_identifier(Language::Node, "requestUrl", IdentifierContext::PublicMember).is_ok());
assert!(validate_identifier(Language::Node, "Content-Type", IdentifierContext::PublicMember).is_err());
assert!(validate_identifier(Language::Node, "Content-Type", IdentifierContext::Wire).is_ok());
}
#[test]
fn name_collision_detection_groups_distinct_originals() {
let collisions = detect_name_collisions(["foo_bar", "fooBar", "baz"], |name| {
public_host_identifier(Language::Node, PublicIdentifierKind::Field, name)
});
assert_eq!(
collisions,
vec![NameCollision {
generated: "fooBar".to_string(),
originals: vec!["foo_bar".to_string(), "fooBar".to_string()],
}]
);
}
#[test]
fn test_to_go_name_html_initialism() {
assert_eq!(to_go_name("html"), "HTML");
}
#[test]
fn test_to_go_name_url_initialism() {
assert_eq!(to_go_name("url"), "URL");
}
#[test]
fn test_to_go_name_id_initialism() {
assert_eq!(to_go_name("id"), "ID");
}
#[test]
fn test_to_go_name_plain_word() {
assert_eq!(to_go_name("links"), "Links");
}
#[test]
fn test_to_go_name_user_id() {
assert_eq!(to_go_name("user_id"), "UserID");
}
#[test]
fn test_to_go_name_request_url() {
assert_eq!(to_go_name("request_url"), "RequestURL");
}
#[test]
fn test_to_go_name_http_status() {
assert_eq!(to_go_name("http_status"), "HTTPStatus");
}
#[test]
fn test_to_go_name_json_body() {
assert_eq!(to_go_name("json_body"), "JSONBody");
}
#[test]
fn test_go_param_name_base_url() {
assert_eq!(go_param_name("base_url"), "baseURL");
}
#[test]
fn test_go_param_name_user_id() {
assert_eq!(go_param_name("user_id"), "userID");
}
#[test]
fn test_go_param_name_api_key() {
assert_eq!(go_param_name("api_key"), "apiKey");
}
#[test]
fn test_go_param_name_plain() {
assert_eq!(go_param_name("json"), "json");
}
#[test]
fn pascal_to_snake_normal_case() {
assert_eq!(pascal_to_snake("MyType"), "my_type");
}
#[test]
fn pascal_to_snake_rdfa() {
assert_eq!(pascal_to_snake("Rdfa"), "rdfa");
}
#[test]
fn pascal_to_snake_html_parser() {
assert_eq!(pascal_to_snake("HTMLParser"), "html_parser");
}
#[test]
fn pascal_to_snake_xml_http_request() {
assert_eq!(pascal_to_snake("XMLHttpRequest"), "xml_http_request");
}
#[test]
fn pascal_to_snake_io_error() {
assert_eq!(pascal_to_snake("IOError"), "io_error");
}
#[test]
fn pascal_to_snake_url_path() {
assert_eq!(pascal_to_snake("URLPath"), "url_path");
}
#[test]
fn pascal_to_snake_jsonld_all_caps() {
assert_eq!(pascal_to_snake("JSONLD"), "jsonld");
}
#[test]
fn pascal_to_snake_camel_case() {
assert_eq!(pascal_to_snake("myField"), "my_field");
}
#[test]
fn pascal_to_snake_already_snake() {
assert_eq!(pascal_to_snake("already_snake"), "already_snake");
}
#[test]
fn pascal_to_snake_empty() {
assert_eq!(pascal_to_snake(""), "");
}
#[test]
fn pascal_to_screaming_snake_rdfa() {
assert_eq!(pascal_to_screaming_snake("Rdfa"), "RDFA");
}
#[test]
fn pascal_to_screaming_snake_html_parser() {
assert_eq!(pascal_to_screaming_snake("HTMLParser"), "HTML_PARSER");
}
#[test]
fn pascal_to_screaming_snake_my_type() {
assert_eq!(pascal_to_screaming_snake("MyType"), "MY_TYPE");
}
#[test]
fn test_to_csharp_name_graphql_route_config() {
assert_eq!(to_csharp_name("graphql_route_config"), "GraphQLRouteConfig");
}
#[test]
fn test_to_csharp_name_http_status_no_acronym() {
assert_eq!(to_csharp_name("http_status"), "HttpStatus");
}
#[test]
fn test_to_csharp_name_to_json_no_acronym() {
assert_eq!(to_csharp_name("to_json"), "ToJson");
}
#[test]
fn test_to_csharp_name_plain() {
assert_eq!(to_csharp_name("my_field"), "MyField");
}
#[test]
fn test_csharp_type_name_heck_corrupted() {
assert_eq!(csharp_type_name("GraphQlRouteConfig"), "GraphQLRouteConfig");
}
#[test]
fn test_csharp_type_name_already_correct() {
assert_eq!(csharp_type_name("GraphQLRouteConfig"), "GraphQLRouteConfig");
}
#[test]
fn test_csharp_type_name_http_status_no_acronym() {
assert_eq!(csharp_type_name("HttpStatus"), "HttpStatus");
}
#[test]
fn test_csharp_type_name_three_letter_acronyms() {
assert_eq!(csharp_type_name("Uri"), "Uri");
assert_eq!(csharp_type_name("URI"), "Uri");
assert_eq!(csharp_type_name("Xml"), "Xml");
assert_eq!(csharp_type_name("XML"), "Xml");
assert_eq!(csharp_type_name("Json"), "Json");
assert_eq!(csharp_type_name("JSON"), "Json");
}
}