1use convert_case::{Case, Casing};
6
7pub fn extract_simple_type_name(name: &str) -> String {
12 name.split('.').next_back().unwrap_or(name).to_string()
13}
14
15pub fn extract_qualified_type_name(name: &str) -> String {
29 let segments: Vec<&str> = name.split('.').filter(|s| !s.is_empty()).collect();
30 let start = segments
33 .iter()
34 .position(|s| s.starts_with(|c: char| c.is_ascii_uppercase()));
35 match start {
36 Some(i) => segments[i..].concat(),
37 None => extract_simple_type_name(name),
39 }
40}
41
42pub(crate) fn metadata_key(name: &str) -> String {
45 if name.starts_with('.') {
46 name.to_string()
47 } else {
48 format!(".{name}")
49 }
50}
51
52pub mod strings {
54 use super::*;
55
56 pub fn lower_camel(name: &str) -> String {
58 let mut out = String::with_capacity(name.len());
59 let mut upper_next = false;
60 for ch in name.chars() {
61 if ch == '_' {
62 upper_next = true;
63 } else if upper_next {
64 out.push(ch.to_ascii_uppercase());
65 upper_next = false;
66 } else {
67 out.push(ch);
68 }
69 }
70 out
71 }
72
73 pub fn service_to_handler_name(service_name: &str) -> String {
76 if let Some(base) = service_name.strip_suffix("Service") {
77 format!("{}Handler", singularize(base))
78 } else {
79 format!("{service_name}Handler")
80 }
81 }
82
83 fn singularize(word: &str) -> String {
88 if let Some(stem) = word.strip_suffix("ies") {
89 format!("{stem}y")
90 } else {
91 word.trim_end_matches('s').to_string()
92 }
93 }
94
95 pub fn operation_to_method_name(operation_id: &str) -> String {
98 operation_id.to_case(Case::Snake)
99 }
100
101 pub fn service_to_base_path(service_name: &str) -> String {
104 if let Some(base) = service_name.strip_suffix("Service") {
105 base.to_case(Case::Snake)
106 } else {
107 service_name.to_case(Case::Snake)
108 }
109 }
110}
111
112pub(crate) fn field_ident(name: &str) -> proc_macro2::Ident {
115 const KEYWORDS: &[&str] = &[
116 "abstract", "as", "async", "await", "become", "box", "break", "const", "continue", "do",
117 "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "gen", "if", "impl", "in",
118 "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", "pub", "ref",
119 "return", "static", "struct", "trait", "true", "try", "type", "typeof", "unsafe",
120 "unsized", "use", "virtual", "where", "while", "yield",
121 ];
122 if KEYWORDS.contains(&name) {
123 proc_macro2::Ident::new_raw(name, proc_macro2::Span::call_site())
124 } else {
125 proc_macro2::Ident::new(name, proc_macro2::Span::call_site())
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 mod string_tests {
134 use super::*;
135
136 #[test]
137 fn test_service_to_handler_name() {
138 assert_eq!(
139 strings::service_to_handler_name("CatalogsService"),
140 "CatalogHandler"
141 );
142 assert_eq!(
143 strings::service_to_handler_name("RecipientsService"),
144 "RecipientHandler"
145 );
146 assert_eq!(
147 strings::service_to_handler_name("SchemasService"),
148 "SchemaHandler"
149 );
150 assert_eq!(
151 strings::service_to_handler_name("TagPoliciesService"),
152 "TagPolicyHandler"
153 );
154 }
155
156 #[test]
157 fn test_operation_to_method_name() {
158 assert_eq!(
159 strings::operation_to_method_name("ListCatalogs"),
160 "list_catalogs"
161 );
162 assert_eq!(
163 strings::operation_to_method_name("CreateCatalog"),
164 "create_catalog"
165 );
166 assert_eq!(
167 strings::operation_to_method_name("GetCatalog"),
168 "get_catalog"
169 );
170 }
171
172 #[test]
173 fn test_service_to_base_path() {
174 assert_eq!(strings::service_to_base_path("CatalogsService"), "catalogs");
175 assert_eq!(
176 strings::service_to_base_path("RecipientsService"),
177 "recipients"
178 );
179 }
180 }
181
182 mod qualified_type_name {
183 use super::*;
184
185 #[test]
186 fn top_level_type_is_unchanged() {
187 assert_eq!(
188 extract_qualified_type_name(".unitycatalog.catalog.v1.Catalog"),
189 "Catalog"
190 );
191 assert_eq!(extract_qualified_type_name(".pkg.v1.UpdateX"), "UpdateX");
192 }
193
194 #[test]
195 fn nested_type_is_prefixed_by_parent() {
196 assert_eq!(
197 extract_qualified_type_name(
198 ".unitycatalog.temporary_credentials.v1.GenerateTemporaryTableCredentialsRequest.Operation"
199 ),
200 "GenerateTemporaryTableCredentialsRequestOperation"
201 );
202 }
203
204 #[test]
205 fn distinct_parents_yield_distinct_names() {
206 let a = extract_qualified_type_name(".pkg.v1.GenerateTableCredsRequest.Operation");
207 let b = extract_qualified_type_name(".pkg.v1.GenerateVolumeCredsRequest.Operation");
208 assert_ne!(a, b);
209 }
210
211 #[test]
212 fn no_leading_dot_or_package() {
213 assert_eq!(extract_qualified_type_name("Catalog"), "Catalog");
215 }
216 }
217}