1pub mod base;
2pub mod ts;
3pub mod zod;
4
5use crate::analysis::CommandAnalyzer;
6use crate::models::{CommandInfo, EventInfo, StructInfo};
7use crate::GenerateConfig;
8use base::template_context::{CommandContext, EventContext, FieldContext, StructContext};
9use base::type_visitor::TypeVisitor;
10use std::collections::HashMap;
11
12pub use base::templates::GlobalContext;
13pub use base::BaseBindingsGenerator as BindingsGenerator;
14pub use ts::generator::TypeScriptBindingsGenerator;
15pub use zod::generator::ZodBindingsGenerator;
16
17#[macro_export]
19macro_rules! template {
20 ($tera:expr, $name:expr, $path:expr) => {
21 $tera
22 .add_raw_template($name, include_str!($path))
23 .map_err(|e| format!("Failed to register {}: {}", $name, e))?;
24 };
25}
26
27pub fn create_generator(validation_library: Option<String>) -> Box<dyn BindingsGenerator> {
30 match validation_library.as_deref().unwrap_or("none") {
31 "zod" => Box::new(ZodBindingsGenerator::new()),
32 _ => Box::new(TypeScriptBindingsGenerator::new()),
33 }
34}
35
36pub struct TypeCollector {
43 pub known_structs: HashMap<String, StructInfo>,
44}
45
46impl TypeCollector {
47 pub fn new() -> Self {
48 Self {
49 known_structs: HashMap::new(),
50 }
51 }
52
53 pub fn collect_used_types(
55 &self,
56 commands: &[CommandInfo],
57 all_structs: &HashMap<String, StructInfo>,
58 ) -> HashMap<String, StructInfo> {
59 let mut used_types = std::collections::HashSet::new();
60
61 for command in commands {
63 for param in &command.parameters {
65 Self::collect_referenced_types_from_structure(
66 ¶m.type_structure,
67 &mut used_types,
68 );
69 }
70 Self::collect_referenced_types_from_structure(
72 &command.return_type_structure,
73 &mut used_types,
74 );
75 for channel in &command.channels {
77 Self::collect_referenced_types_from_structure(
78 &channel.message_type_structure,
79 &mut used_types,
80 );
81 }
82 }
83
84 let initial_types = used_types.clone();
86
87 self.discover_nested_dependencies(&initial_types, all_structs, &mut used_types);
89
90 all_structs
92 .iter()
93 .filter(|(name, _)| used_types.contains(*name))
94 .map(|(k, v)| (k.clone(), v.clone()))
95 .collect()
96 }
97
98 fn discover_nested_dependencies(
100 &self,
101 initial_types: &std::collections::HashSet<String>,
102 all_structs: &HashMap<String, StructInfo>,
103 all_types: &mut std::collections::HashSet<String>,
104 ) {
105 let mut to_process: Vec<String> = initial_types.iter().cloned().collect();
106 let mut processed: std::collections::HashSet<String> = std::collections::HashSet::new();
107
108 while let Some(type_name) = to_process.pop() {
109 if processed.contains(&type_name) {
110 continue;
111 }
112 processed.insert(type_name.clone());
113
114 if let Some(struct_info) = all_structs.get(&type_name) {
115 for field in &struct_info.fields {
116 let mut nested_types = std::collections::HashSet::new();
117 Self::collect_referenced_types_from_structure(
119 &field.type_structure,
120 &mut nested_types,
121 );
122
123 for nested_type in nested_types {
124 if !all_types.contains(&nested_type)
125 && all_structs.contains_key(&nested_type)
126 {
127 all_types.insert(nested_type.clone());
128 to_process.push(nested_type);
129 }
130 }
131 }
132 }
133 }
134 }
135
136 pub fn collect_referenced_types_from_structure(
139 type_structure: &crate::TypeStructure,
140 used_types: &mut std::collections::HashSet<String>,
141 ) {
142 use crate::TypeStructure;
143
144 match type_structure {
145 TypeStructure::Custom(name) => {
146 used_types.insert(name.clone());
147 }
148 TypeStructure::Array(inner)
149 | TypeStructure::Set(inner)
150 | TypeStructure::Optional(inner)
151 | TypeStructure::Result(inner) => {
152 Self::collect_referenced_types_from_structure(inner, used_types);
153 }
154 TypeStructure::Map { key, value } => {
155 Self::collect_referenced_types_from_structure(key, used_types);
156 Self::collect_referenced_types_from_structure(value, used_types);
157 }
158 TypeStructure::Tuple(types) => {
159 for t in types {
160 Self::collect_referenced_types_from_structure(t, used_types);
161 }
162 }
163 TypeStructure::Primitive(_) => {
164 }
166 }
167 }
168
169 pub fn create_command_contexts<V: TypeVisitor>(
171 &self,
172 commands: &[CommandInfo],
173 visitor: &V,
174 analyzer: &CommandAnalyzer,
175 config: &GenerateConfig,
176 ) -> Vec<CommandContext> {
177 let type_resolver = analyzer.get_type_resolver();
178
179 commands
180 .iter()
181 .map(|cmd| {
182 CommandContext::new(config).from_command_info(cmd, visitor, &|rust_type: &str| {
183 type_resolver.borrow_mut().parse_type_structure(rust_type)
184 })
185 })
186 .collect()
187 }
188
189 pub fn create_event_contexts<V: TypeVisitor>(
191 &self,
192 events: &[EventInfo],
193 visitor: &V,
194 analyzer: &CommandAnalyzer,
195 config: &GenerateConfig,
196 ) -> Vec<EventContext> {
197 let type_resolver = analyzer.get_type_resolver();
198
199 events
200 .iter()
201 .map(|event| {
202 EventContext::new(config).from_event_info(event, visitor, &|rust_type: &str| {
203 type_resolver.borrow_mut().parse_type_structure(rust_type)
204 })
205 })
206 .collect()
207 }
208
209 pub fn create_struct_contexts<V: TypeVisitor>(
211 &self,
212 used_structs: &HashMap<String, StructInfo>,
213 visitor: &V,
214 config: &GenerateConfig,
215 ) -> Vec<StructContext> {
216 used_structs
217 .iter()
218 .map(|(name, struct_info)| {
219 StructContext::new(config).from_struct_info(name, struct_info, visitor)
220 })
221 .collect()
222 }
223
224 pub fn create_field_contexts<V: TypeVisitor>(
226 &self,
227 struct_info: &StructInfo,
228 visitor: &V,
229 config: &GenerateConfig,
230 ) -> Vec<FieldContext> {
231 struct_info
232 .fields
233 .iter()
234 .map(|field| {
235 FieldContext::new(config).from_field_info(
236 field,
237 &struct_info.serde_rename_all,
238 visitor,
239 )
240 })
241 .collect()
242 }
243}
244
245impl Default for TypeCollector {
246 fn default() -> Self {
247 Self::new()
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use crate::TypeStructure;
255 use std::collections::HashSet;
256
257 mod factory {
258 use super::*;
259
260 #[test]
261 fn test_create_generator_zod() {
262 let gen = create_generator(Some("zod".to_string()));
263 assert!(std::any::type_name_of_val(&gen).contains("Box"));
265 }
266
267 #[test]
268 fn test_create_generator_none() {
269 let gen = create_generator(Some("none".to_string()));
270 assert!(std::any::type_name_of_val(&gen).contains("Box"));
271 }
272
273 #[test]
274 fn test_create_generator_default() {
275 let gen = create_generator(None);
276 assert!(std::any::type_name_of_val(&gen).contains("Box"));
277 }
278
279 #[test]
280 fn test_create_generator_unknown_fallback() {
281 let gen = create_generator(Some("unknown".to_string()));
282 assert!(std::any::type_name_of_val(&gen).contains("Box"));
283 }
284 }
285
286 mod type_collector {
287 use super::*;
288
289 #[test]
290 fn test_new_creates_empty_collector() {
291 let collector = TypeCollector::new();
292 assert!(collector.known_structs.is_empty());
293 }
294
295 #[test]
296 fn test_default_creates_empty_collector() {
297 let collector = TypeCollector::default();
298 assert!(collector.known_structs.is_empty());
299 }
300 }
301
302 mod collect_referenced_types {
303 use super::*;
304
305 #[test]
306 fn test_collect_primitive() {
307 let mut used = HashSet::new();
308 let ts = TypeStructure::Primitive("string".to_string());
309 TypeCollector::collect_referenced_types_from_structure(&ts, &mut used);
310 assert!(used.is_empty());
311 }
312
313 #[test]
314 fn test_collect_custom() {
315 let mut used = HashSet::new();
316 let ts = TypeStructure::Custom("User".to_string());
317 TypeCollector::collect_referenced_types_from_structure(&ts, &mut used);
318 assert_eq!(used.len(), 1);
319 assert!(used.contains("User"));
320 }
321
322 #[test]
323 fn test_collect_array() {
324 let mut used = HashSet::new();
325 let ts = TypeStructure::Array(Box::new(TypeStructure::Custom("User".to_string())));
326 TypeCollector::collect_referenced_types_from_structure(&ts, &mut used);
327 assert_eq!(used.len(), 1);
328 assert!(used.contains("User"));
329 }
330
331 #[test]
332 fn test_collect_optional() {
333 let mut used = HashSet::new();
334 let ts = TypeStructure::Optional(Box::new(TypeStructure::Custom("User".to_string())));
335 TypeCollector::collect_referenced_types_from_structure(&ts, &mut used);
336 assert_eq!(used.len(), 1);
337 assert!(used.contains("User"));
338 }
339
340 #[test]
341 fn test_collect_result() {
342 let mut used = HashSet::new();
343 let ts = TypeStructure::Result(Box::new(TypeStructure::Custom("User".to_string())));
344 TypeCollector::collect_referenced_types_from_structure(&ts, &mut used);
345 assert_eq!(used.len(), 1);
346 assert!(used.contains("User"));
347 }
348
349 #[test]
350 fn test_collect_set() {
351 let mut used = HashSet::new();
352 let ts = TypeStructure::Set(Box::new(TypeStructure::Custom("User".to_string())));
353 TypeCollector::collect_referenced_types_from_structure(&ts, &mut used);
354 assert_eq!(used.len(), 1);
355 assert!(used.contains("User"));
356 }
357
358 #[test]
359 fn test_collect_map() {
360 let mut used = HashSet::new();
361 let ts = TypeStructure::Map {
362 key: Box::new(TypeStructure::Primitive("string".to_string())),
363 value: Box::new(TypeStructure::Custom("User".to_string())),
364 };
365 TypeCollector::collect_referenced_types_from_structure(&ts, &mut used);
366 assert_eq!(used.len(), 1);
367 assert!(used.contains("User"));
368 }
369
370 #[test]
371 fn test_collect_map_both_custom() {
372 let mut used = HashSet::new();
373 let ts = TypeStructure::Map {
374 key: Box::new(TypeStructure::Custom("UserId".to_string())),
375 value: Box::new(TypeStructure::Custom("User".to_string())),
376 };
377 TypeCollector::collect_referenced_types_from_structure(&ts, &mut used);
378 assert_eq!(used.len(), 2);
379 assert!(used.contains("User"));
380 assert!(used.contains("UserId"));
381 }
382
383 #[test]
384 fn test_collect_tuple() {
385 let mut used = HashSet::new();
386 let ts = TypeStructure::Tuple(vec![
387 TypeStructure::Custom("User".to_string()),
388 TypeStructure::Custom("Product".to_string()),
389 ]);
390 TypeCollector::collect_referenced_types_from_structure(&ts, &mut used);
391 assert_eq!(used.len(), 2);
392 assert!(used.contains("User"));
393 assert!(used.contains("Product"));
394 }
395
396 #[test]
397 fn test_collect_nested() {
398 let mut used = HashSet::new();
399 let ts = TypeStructure::Array(Box::new(TypeStructure::Optional(Box::new(
400 TypeStructure::Custom("User".to_string()),
401 ))));
402 TypeCollector::collect_referenced_types_from_structure(&ts, &mut used);
403 assert_eq!(used.len(), 1);
404 assert!(used.contains("User"));
405 }
406
407 #[test]
408 fn test_collect_multiple_calls_accumulate() {
409 let mut used = HashSet::new();
410 let ts1 = TypeStructure::Custom("User".to_string());
411 let ts2 = TypeStructure::Custom("Product".to_string());
412 TypeCollector::collect_referenced_types_from_structure(&ts1, &mut used);
413 TypeCollector::collect_referenced_types_from_structure(&ts2, &mut used);
414 assert_eq!(used.len(), 2);
415 }
416
417 #[test]
418 fn test_collect_duplicates_deduped() {
419 let mut used = HashSet::new();
420 let ts = TypeStructure::Custom("User".to_string());
421 TypeCollector::collect_referenced_types_from_structure(&ts, &mut used);
422 TypeCollector::collect_referenced_types_from_structure(&ts, &mut used);
423 assert_eq!(used.len(), 1);
424 }
425 }
426
427 mod collect_used_types {
428 use super::*;
429 use crate::models::{CommandInfo, ParameterInfo, StructInfo};
430
431 fn create_struct(name: &str) -> StructInfo {
432 StructInfo {
433 name: name.to_string(),
434 fields: vec![],
435 file_path: "test.rs".to_string(),
436 is_enum: false,
437 serde_rename_all: None,
438 serde_tag: None,
439 enum_variants: None,
440 }
441 }
442
443 fn create_param(
444 name: &str,
445 rust_type: &str,
446 type_structure: TypeStructure,
447 ) -> ParameterInfo {
448 ParameterInfo {
449 name: name.to_string(),
450 rust_type: rust_type.to_string(),
451 is_optional: false,
452 type_structure,
453 serde_rename: None,
454 }
455 }
456
457 #[test]
458 fn test_collect_from_empty_commands() {
459 let collector = TypeCollector::new();
460 let commands = vec![];
461 let all_structs = HashMap::new();
462 let used = collector.collect_used_types(&commands, &all_structs);
463 assert!(used.is_empty());
464 }
465
466 #[test]
467 fn test_collect_from_command_parameters() {
468 let collector = TypeCollector::new();
469 let mut all_structs = HashMap::new();
470 let user_struct = create_struct("User");
471 all_structs.insert("User".to_string(), user_struct.clone());
472
473 let param = create_param("user", "User", TypeStructure::Custom("User".to_string()));
474 let command = CommandInfo::new_for_test(
475 "greet",
476 "test.rs",
477 1,
478 vec![param],
479 "string",
480 false,
481 vec![],
482 );
483
484 let used = collector.collect_used_types(&[command], &all_structs);
485 assert_eq!(used.len(), 1);
486 assert!(used.contains_key("User"));
487 }
488
489 #[test]
490 fn test_collect_from_command_return_type() {
491 let collector = TypeCollector::new();
492 let mut all_structs = HashMap::new();
493 let result_struct = create_struct("ApiResult");
494 all_structs.insert("ApiResult".to_string(), result_struct.clone());
495
496 let mut command = CommandInfo::new_for_test(
498 "fetch_data",
499 "test.rs",
500 1,
501 vec![],
502 "ApiResult",
503 false,
504 vec![],
505 );
506 command.return_type_structure = TypeStructure::Custom("ApiResult".to_string());
508
509 let used = collector.collect_used_types(&[command], &all_structs);
510 assert_eq!(used.len(), 1);
511 assert!(used.contains_key("ApiResult"));
512 }
513
514 #[test]
515 fn test_filters_unused_types() {
516 let collector = TypeCollector::new();
517 let mut all_structs = HashMap::new();
518
519 let user_struct = create_struct("User");
521 let product_struct = create_struct("Product");
522 all_structs.insert("User".to_string(), user_struct);
523 all_structs.insert("Product".to_string(), product_struct);
524
525 let param = create_param("user", "User", TypeStructure::Custom("User".to_string()));
526 let command = CommandInfo::new_for_test(
527 "greet",
528 "test.rs",
529 1,
530 vec![param],
531 "string",
532 false,
533 vec![],
534 );
535
536 let used = collector.collect_used_types(&[command], &all_structs);
537 assert_eq!(used.len(), 1);
538 assert!(used.contains_key("User"));
539 assert!(!used.contains_key("Product"));
540 }
541 }
542
543 mod nested_dependencies {
544 use super::*;
545 use crate::models::{CommandInfo, FieldInfo, ParameterInfo, StructInfo};
546
547 fn create_field(name: &str, rust_type: &str, type_structure: TypeStructure) -> FieldInfo {
548 FieldInfo {
549 name: name.to_string(),
550 rust_type: rust_type.to_string(),
551 is_optional: false,
552 is_public: true,
553 validator_attributes: None,
554 serde_rename: None,
555 type_structure,
556 }
557 }
558
559 fn create_struct_with_fields(name: &str, fields: Vec<FieldInfo>) -> StructInfo {
560 StructInfo {
561 name: name.to_string(),
562 fields,
563 file_path: "test.rs".to_string(),
564 is_enum: false,
565 serde_rename_all: None,
566 serde_tag: None,
567 enum_variants: None,
568 }
569 }
570
571 fn create_param(
572 name: &str,
573 rust_type: &str,
574 type_structure: TypeStructure,
575 ) -> ParameterInfo {
576 ParameterInfo {
577 name: name.to_string(),
578 rust_type: rust_type.to_string(),
579 is_optional: false,
580 type_structure,
581 serde_rename: None,
582 }
583 }
584
585 #[test]
586 fn test_discovers_nested_dependencies() {
587 let collector = TypeCollector::new();
588 let mut all_structs = HashMap::new();
589
590 let address_field = create_field(
592 "address",
593 "Address",
594 TypeStructure::Custom("Address".to_string()),
595 );
596 let user_struct = create_struct_with_fields("User", vec![address_field]);
597 let address_struct = create_struct_with_fields("Address", vec![]);
598
599 all_structs.insert("User".to_string(), user_struct);
600 all_structs.insert("Address".to_string(), address_struct);
601
602 let param = create_param("user", "User", TypeStructure::Custom("User".to_string()));
604 let command = CommandInfo::new_for_test(
605 "greet",
606 "test.rs",
607 1,
608 vec![param],
609 "string",
610 false,
611 vec![],
612 );
613
614 let used = collector.collect_used_types(&[command], &all_structs);
615
616 assert_eq!(used.len(), 2);
618 assert!(used.contains_key("User"));
619 assert!(used.contains_key("Address"));
620 }
621
622 #[test]
623 fn test_handles_deep_nesting() {
624 let collector = TypeCollector::new();
625 let mut all_structs = HashMap::new();
626
627 let c_struct = create_struct_with_fields("C", vec![]);
629 let b_field = create_field("c", "C", TypeStructure::Custom("C".to_string()));
630 let b_struct = create_struct_with_fields("B", vec![b_field]);
631 let a_field = create_field("b", "B", TypeStructure::Custom("B".to_string()));
632 let a_struct = create_struct_with_fields("A", vec![a_field]);
633
634 all_structs.insert("A".to_string(), a_struct);
635 all_structs.insert("B".to_string(), b_struct);
636 all_structs.insert("C".to_string(), c_struct);
637
638 let param = create_param("data", "A", TypeStructure::Custom("A".to_string()));
639 let command = CommandInfo::new_for_test(
640 "process",
641 "test.rs",
642 1,
643 vec![param],
644 "void",
645 false,
646 vec![],
647 );
648
649 let used = collector.collect_used_types(&[command], &all_structs);
650
651 assert_eq!(used.len(), 3);
653 assert!(used.contains_key("A"));
654 assert!(used.contains_key("B"));
655 assert!(used.contains_key("C"));
656 }
657 }
658}