1use convert_case::{Case, Casing};
7use proc_macro2::TokenStream;
8use quote::quote;
9
10use crate::utils::extract_simple_type_name;
11
12#[derive(Debug, Clone, Copy)]
14pub enum RenderContext {
15 Constructor,
17 Extractor,
20 QueryExtractor,
23 Parameter,
25 ReturnType,
27 FieldType,
29 BuilderMethod,
31 PythonParameter,
33 NapiParameter,
35}
36
37pub trait TypeRenderer {
43 fn render(&self, ty: &UnifiedType) -> String;
44}
45
46pub struct RustRenderer(pub RenderContext);
48
49impl TypeRenderer for RustRenderer {
50 fn render(&self, ty: &UnifiedType) -> String {
51 unified_to_rust(ty, self.0)
52 }
53}
54
55pub struct PythonRenderer;
57
58impl TypeRenderer for PythonRenderer {
59 fn render(&self, ty: &UnifiedType) -> String {
60 unified_to_python_type(ty)
61 }
62}
63
64pub struct NapiRenderer;
66
67impl TypeRenderer for NapiRenderer {
68 fn render(&self, ty: &UnifiedType) -> String {
69 unified_to_napi(ty)
70 }
71}
72
73pub struct TypeScriptRenderer;
75
76impl TypeRenderer for TypeScriptRenderer {
77 fn render(&self, ty: &UnifiedType) -> String {
78 unified_to_typescript(ty)
79 }
80}
81
82pub fn unified_to_rust(unified_type: &UnifiedType, context: RenderContext) -> String {
84 let base_type_str = match &unified_type.base_type {
85 BaseType::String => {
86 if matches!(context, RenderContext::Constructor) && !unified_type.is_optional {
87 "impl Into<String>".to_string()
88 } else {
89 "String".to_string()
90 }
91 }
92 BaseType::Int32 => "i32".to_string(),
93 BaseType::Int64 => "i64".to_string(),
94 BaseType::Bool => "bool".to_string(),
95 BaseType::Float64 => "f64".to_string(),
96 BaseType::Float32 => "f32".to_string(),
97 BaseType::Bytes => "Vec<u8>".to_string(),
98 BaseType::Unit => "()".to_string(),
99 BaseType::Message(name) => extract_simple_type_name(name),
100 BaseType::Enum(name) => {
101 if matches!(
102 context,
103 RenderContext::Extractor | RenderContext::NapiParameter
104 ) {
105 "i32".to_string()
106 } else {
107 convert_protobuf_enum_to_rust_type(&format!("TYPE_ENUM:{}", name))
108 }
109 }
110 BaseType::OneOf(name) => extract_simple_type_name(name),
111 BaseType::Map(key_type, value_type) => {
112 if matches!(context, RenderContext::Constructor) && !unified_type.is_optional {
113 "impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>".to_string()
114 } else {
115 let key_str = unified_to_rust(key_type, context);
116 let value_str = unified_to_rust(value_type, context);
117 format!("HashMap<{}, {}>", key_str, value_str)
118 }
119 }
120 };
121
122 let mut result = base_type_str;
123
124 if unified_type.is_repeated && !matches!(context, RenderContext::BuilderMethod) {
126 result = format!("Vec<{}>", result);
127 }
128
129 if should_wrap_in_option(context, unified_type) {
130 result = format!("Option<{}>", result);
131 }
132
133 result
134}
135
136fn should_wrap_in_option(ctx: RenderContext, ty: &UnifiedType) -> bool {
148 let is_optional_non_builder = ty.is_optional && !matches!(ctx, RenderContext::BuilderMethod);
149 let is_ffi_collection = matches!(
150 ctx,
151 RenderContext::PythonParameter | RenderContext::NapiParameter
152 ) && (matches!(ty.base_type, BaseType::Map(_, _)) || ty.is_repeated);
153 is_optional_non_builder || is_ffi_collection
154}
155
156pub fn field_assignment(
158 unified_type: &UnifiedType,
159 field_ident: &proc_macro2::Ident,
160 ctx: &RenderContext,
161) -> TokenStream {
162 if matches!(ctx, RenderContext::BuilderMethod) {
163 return flexible_optional_field_assignment(unified_type, field_ident);
164 }
165 match &unified_type.base_type {
166 BaseType::String if !unified_type.is_optional => quote! { #field_ident.into() },
167 BaseType::Enum(_) => {
168 if unified_type.is_repeated {
169 quote! { #field_ident.into_iter().map(|v| v as i32).collect() }
170 } else {
171 quote! { #field_ident as i32 }
172 }
173 }
174 BaseType::Map(_, _) => quote! {
175 #field_ident.into_iter().map(|(k, v)| (k.into(), v.into())).collect()
176 },
177 _ => quote! { #field_ident },
178 }
179}
180
181pub fn unified_to_python_type(unified_type: &UnifiedType) -> String {
183 let base_type_str = match &unified_type.base_type {
184 BaseType::String => "str".to_string(),
185 BaseType::Int32 | BaseType::Int64 => "int".to_string(),
186 BaseType::Bool => "bool".to_string(),
187 BaseType::Float64 | BaseType::Float32 => "float".to_string(),
188 BaseType::Bytes => "bytes".to_string(),
189 BaseType::Unit => "None".to_string(),
190 BaseType::Message(name) => extract_simple_type_name(name),
191 BaseType::Enum(name) => extract_simple_type_name(name),
192 BaseType::OneOf(name) => extract_simple_type_name(name),
193 BaseType::Map(key_type, value_type) => {
194 let key_str = unified_to_python_type(key_type);
195 let value_str = unified_to_python_type(value_type);
196 format!("Dict[{}, {}]", key_str, value_str)
197 }
198 };
199
200 let mut result = base_type_str;
201
202 if unified_type.is_repeated {
203 result = format!("List[{}]", result);
204 }
205
206 if unified_type.is_optional {
207 result = format!("Optional[{}]", result);
208 }
209
210 result
211}
212
213pub fn unified_to_napi(unified_type: &UnifiedType) -> String {
218 let base_type_str = match &unified_type.base_type {
219 BaseType::String => "String".to_string(),
220 BaseType::Int32 => "i32".to_string(),
221 BaseType::Int64 => "i64".to_string(),
222 BaseType::Bool => "bool".to_string(),
223 BaseType::Float64 => "f64".to_string(),
224 BaseType::Float32 => "f32".to_string(),
225 BaseType::Bytes => "Vec<u8>".to_string(),
226 BaseType::Unit => "()".to_string(),
227 BaseType::Message(name) => extract_simple_type_name(name),
228 BaseType::Enum(name) => convert_protobuf_enum_to_rust_type(&format!("TYPE_ENUM:{}", name)),
229 BaseType::OneOf(name) => extract_simple_type_name(name),
230 BaseType::Map(key_type, value_type) => {
231 let key_str = unified_to_napi(key_type);
232 let value_str = unified_to_napi(value_type);
233 format!("HashMap<{}, {}>", key_str, value_str)
234 }
235 };
236
237 let mut result = base_type_str;
238
239 if unified_type.is_repeated {
240 result = format!("Vec<{}>", result);
241 }
242
243 if unified_type.is_optional
244 || matches!(unified_type.base_type, BaseType::Map(_, _))
245 || unified_type.is_repeated
246 {
247 result = format!("Option<{}>", result);
248 }
249
250 result
251}
252
253pub fn unified_to_typescript(unified_type: &UnifiedType) -> String {
257 let base_type_str = match &unified_type.base_type {
258 BaseType::String => "string".to_string(),
259 BaseType::Int32 | BaseType::Int64 => "number".to_string(),
260 BaseType::Bool => "boolean".to_string(),
261 BaseType::Float64 | BaseType::Float32 => "number".to_string(),
262 BaseType::Bytes => "Uint8Array".to_string(),
263 BaseType::Unit => "void".to_string(),
264 BaseType::Message(name) => extract_simple_type_name(name),
265 BaseType::Enum(_) => "number".to_string(),
266 BaseType::OneOf(name) => extract_simple_type_name(name),
267 BaseType::Map(key_type, value_type) => {
268 let key_str = unified_to_typescript(key_type);
269 let value_str = unified_to_typescript(value_type);
270 format!("Record<{}, {}>", key_str, value_str)
271 }
272 };
273
274 let mut result = base_type_str;
275
276 if unified_type.is_repeated {
277 result = format!("{}[]", result);
278 }
279
280 if unified_type.is_optional {
281 result = format!("{} | undefined", result);
282 }
283
284 result
285}
286
287fn flexible_optional_field_assignment(
288 unified_type: &UnifiedType,
289 field_ident: &proc_macro2::Ident,
290) -> TokenStream {
291 if unified_type.is_optional {
292 match &unified_type.base_type {
293 BaseType::Enum(_) => quote! { #field_ident.into().map(|e| e as i32) },
294 _ => quote! { #field_ident.into() },
295 }
296 } else {
297 match &unified_type.base_type {
298 BaseType::String => quote! { #field_ident.into() },
299 BaseType::Int32
300 | BaseType::Int64
301 | BaseType::Bool
302 | BaseType::Float64
303 | BaseType::Float32 => {
304 quote! { #field_ident.into() }
305 }
306 BaseType::Enum(_) => {
307 quote! { #field_ident as i32 }
308 }
309 _ => quote! { #field_ident },
312 }
313 }
314}
315
316fn convert_protobuf_enum_to_rust_type(proto_type: &str) -> String {
331 if let Some(enum_name) = proto_type.strip_prefix("TYPE_ENUM:") {
332 let enum_name = enum_name.trim_start_matches('.');
334
335 if let Some(last_dot) = enum_name.rfind('.') {
338 let parent_part = &enum_name[..last_dot];
339 let enum_simple_name = &enum_name[last_dot + 1..];
340
341 let parent_parts: Vec<&str> = parent_part.split('.').collect();
344 if let Some(last_part) = parent_parts.last() {
345 if last_part.chars().next().is_some_and(|c| c.is_uppercase())
348 && *last_part != "V1"
349 && !last_part
350 .chars()
351 .all(|c| c.is_lowercase() || c.is_numeric())
352 {
353 let snake_case_module = last_part.to_case(Case::Snake);
355
356 let enum_rust_name = if enum_simple_name.contains('_')
358 && enum_simple_name
359 .chars()
360 .all(|c| c.is_uppercase() || c == '_')
361 {
362 enum_simple_name.to_case(Case::Pascal)
363 } else {
364 enum_simple_name.to_string()
365 };
366
367 format!("{}::{}", snake_case_module, enum_rust_name)
368 } else {
369 convert_enum_name_to_rust(enum_simple_name)
371 }
372 } else {
373 convert_enum_name_to_rust(enum_simple_name)
375 }
376 } else {
377 convert_enum_name_to_rust(enum_name)
379 }
380 } else {
381 "i32".to_string() }
383}
384
385fn convert_enum_name_to_rust(enum_name: &str) -> String {
387 if enum_name.contains('_') && enum_name.chars().all(|c| c.is_uppercase() || c == '_') {
389 enum_name.to_case(Case::Pascal)
390 } else {
391 enum_name.to_string()
392 }
393}
394
395#[derive(Debug, Clone)]
397pub struct UnifiedType {
398 pub base_type: BaseType,
400 pub is_optional: bool,
402 pub is_repeated: bool,
404}
405
406#[derive(Debug, Clone)]
408pub enum BaseType {
409 String,
411 Int32,
413 Int64,
415 Bool,
417 Float64,
419 Float32,
421 Bytes,
423 Message(String),
425 Enum(String),
427 OneOf(String),
429 Map(Box<UnifiedType>, Box<UnifiedType>),
431 Unit,
433}
434
435impl UnifiedType {
436 pub fn type_ident(&self) -> syn::Ident {
438 let name = match &self.base_type {
439 BaseType::Message(n) | BaseType::Enum(n) | BaseType::OneOf(n) => {
440 n.split('.').next_back().unwrap_or(n)
441 }
442 BaseType::String => "String",
443 BaseType::Int32 => "i32",
444 BaseType::Int64 => "i64",
445 BaseType::Bool => "bool",
446 BaseType::Float64 => "f64",
447 BaseType::Float32 => "f32",
448 BaseType::Bytes => "Bytes",
449 BaseType::Unit => "()",
450 BaseType::Map(_, _) => "HashMap",
451 };
452 quote::format_ident!("{}", name)
453 }
454
455 pub fn string() -> Self {
457 Self {
458 base_type: BaseType::String,
459 is_optional: false,
460 is_repeated: false,
461 }
462 }
463
464 pub fn optional(mut self) -> Self {
466 self.is_optional = true;
467 self
468 }
469
470 pub fn repeated(mut self) -> Self {
472 self.is_repeated = true;
473 self
474 }
475
476 pub fn map(key: UnifiedType, value: UnifiedType) -> Self {
478 Self {
479 base_type: BaseType::Map(Box::new(key), Box::new(value)),
480 is_optional: false,
481 is_repeated: false,
482 }
483 }
484}