use convert_case::{Case, Casing};
pub fn extract_simple_type_name(name: &str) -> String {
name.split('.').next_back().unwrap_or(name).to_string()
}
pub fn extract_qualified_type_name(name: &str) -> String {
let segments: Vec<&str> = name.split('.').filter(|s| !s.is_empty()).collect();
let start = segments
.iter()
.position(|s| s.starts_with(|c: char| c.is_ascii_uppercase()));
match start {
Some(i) => segments[i..].concat(),
None => extract_simple_type_name(name),
}
}
pub(crate) fn metadata_key(name: &str) -> String {
if name.starts_with('.') {
name.to_string()
} else {
format!(".{name}")
}
}
pub mod strings {
use super::*;
pub fn lower_camel(name: &str) -> String {
let mut out = String::with_capacity(name.len());
let mut upper_next = false;
for ch in name.chars() {
if ch == '_' {
upper_next = true;
} else if upper_next {
out.push(ch.to_ascii_uppercase());
upper_next = false;
} else {
out.push(ch);
}
}
out
}
pub fn service_to_handler_name(service_name: &str) -> String {
if let Some(base) = service_name.strip_suffix("Service") {
format!("{}Handler", singularize(base))
} else {
format!("{service_name}Handler")
}
}
fn singularize(word: &str) -> String {
if let Some(stem) = word.strip_suffix("ies") {
format!("{stem}y")
} else {
word.trim_end_matches('s').to_string()
}
}
pub fn operation_to_method_name(operation_id: &str) -> String {
operation_id.to_case(Case::Snake)
}
pub fn service_to_base_path(service_name: &str) -> String {
if let Some(base) = service_name.strip_suffix("Service") {
base.to_case(Case::Snake)
} else {
service_name.to_case(Case::Snake)
}
}
}
pub(crate) fn field_ident(name: &str) -> proc_macro2::Ident {
const KEYWORDS: &[&str] = &[
"abstract", "as", "async", "await", "become", "box", "break", "const", "continue", "do",
"dyn", "else", "enum", "extern", "false", "final", "fn", "for", "gen", "if", "impl", "in",
"let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", "pub", "ref",
"return", "static", "struct", "trait", "true", "try", "type", "typeof", "unsafe",
"unsized", "use", "virtual", "where", "while", "yield",
];
if KEYWORDS.contains(&name) {
proc_macro2::Ident::new_raw(name, proc_macro2::Span::call_site())
} else {
proc_macro2::Ident::new(name, proc_macro2::Span::call_site())
}
}
#[cfg(test)]
mod tests {
use super::*;
mod string_tests {
use super::*;
#[test]
fn test_service_to_handler_name() {
assert_eq!(
strings::service_to_handler_name("CatalogsService"),
"CatalogHandler"
);
assert_eq!(
strings::service_to_handler_name("RecipientsService"),
"RecipientHandler"
);
assert_eq!(
strings::service_to_handler_name("SchemasService"),
"SchemaHandler"
);
assert_eq!(
strings::service_to_handler_name("TagPoliciesService"),
"TagPolicyHandler"
);
}
#[test]
fn test_operation_to_method_name() {
assert_eq!(
strings::operation_to_method_name("ListCatalogs"),
"list_catalogs"
);
assert_eq!(
strings::operation_to_method_name("CreateCatalog"),
"create_catalog"
);
assert_eq!(
strings::operation_to_method_name("GetCatalog"),
"get_catalog"
);
}
#[test]
fn test_service_to_base_path() {
assert_eq!(strings::service_to_base_path("CatalogsService"), "catalogs");
assert_eq!(
strings::service_to_base_path("RecipientsService"),
"recipients"
);
}
}
mod qualified_type_name {
use super::*;
#[test]
fn top_level_type_is_unchanged() {
assert_eq!(
extract_qualified_type_name(".unitycatalog.catalog.v1.Catalog"),
"Catalog"
);
assert_eq!(extract_qualified_type_name(".pkg.v1.UpdateX"), "UpdateX");
}
#[test]
fn nested_type_is_prefixed_by_parent() {
assert_eq!(
extract_qualified_type_name(
".unitycatalog.temporary_credentials.v1.GenerateTemporaryTableCredentialsRequest.Operation"
),
"GenerateTemporaryTableCredentialsRequestOperation"
);
}
#[test]
fn distinct_parents_yield_distinct_names() {
let a = extract_qualified_type_name(".pkg.v1.GenerateTableCredsRequest.Operation");
let b = extract_qualified_type_name(".pkg.v1.GenerateVolumeCredsRequest.Operation");
assert_ne!(a, b);
}
#[test]
fn no_leading_dot_or_package() {
assert_eq!(extract_qualified_type_name("Catalog"), "Catalog");
}
}
}