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