1use crate::api::response::{MoveFunction, MoveModuleABI, MoveStructDef, MoveStructField};
4use crate::codegen::move_parser::{EnrichedFunctionInfo, MoveModuleInfo};
5use crate::codegen::types::{MoveTypeMapper, to_pascal_case, to_snake_case};
6use crate::error::{AptosError, AptosResult};
7use std::fmt::Write;
8
9fn sanitize_abi_string(s: &str) -> String {
18 s.replace('\\', "\\\\")
19 .replace('"', "\\\"")
20 .replace(['\n', '\r'], " ")
21}
22
23#[derive(Debug, Clone)]
25#[allow(clippy::struct_excessive_bools)] pub struct GeneratorConfig {
27 pub module_name: Option<String>,
29 pub generate_entry_functions: bool,
31 pub generate_view_functions: bool,
33 pub generate_structs: bool,
35 pub generate_events: bool,
37 pub async_functions: bool,
39 pub type_mapper: MoveTypeMapper,
41 pub include_address_constant: bool,
43 pub use_builder_pattern: bool,
45}
46
47impl Default for GeneratorConfig {
48 fn default() -> Self {
49 Self {
50 module_name: None,
51 generate_entry_functions: true,
52 generate_view_functions: true,
53 generate_structs: true,
54 generate_events: true,
55 async_functions: true,
56 type_mapper: MoveTypeMapper::new(),
57 include_address_constant: true,
58 use_builder_pattern: false,
59 }
60 }
61}
62
63impl GeneratorConfig {
64 #[must_use]
66 pub fn new() -> Self {
67 Self::default()
68 }
69
70 #[must_use]
72 pub fn with_module_name(mut self, name: impl Into<String>) -> Self {
73 self.module_name = Some(name.into());
74 self
75 }
76
77 #[must_use]
79 pub fn with_entry_functions(mut self, enabled: bool) -> Self {
80 self.generate_entry_functions = enabled;
81 self
82 }
83
84 #[must_use]
86 pub fn with_view_functions(mut self, enabled: bool) -> Self {
87 self.generate_view_functions = enabled;
88 self
89 }
90
91 #[must_use]
93 pub fn with_structs(mut self, enabled: bool) -> Self {
94 self.generate_structs = enabled;
95 self
96 }
97
98 #[must_use]
100 pub fn with_events(mut self, enabled: bool) -> Self {
101 self.generate_events = enabled;
102 self
103 }
104
105 #[must_use]
107 pub fn with_async(mut self, enabled: bool) -> Self {
108 self.async_functions = enabled;
109 self
110 }
111
112 #[must_use]
114 pub fn with_builder_pattern(mut self, enabled: bool) -> Self {
115 self.use_builder_pattern = enabled;
116 self
117 }
118}
119
120#[allow(missing_debug_implementations)] pub struct ModuleGenerator<'a> {
123 abi: &'a MoveModuleABI,
124 config: GeneratorConfig,
125 source_info: Option<MoveModuleInfo>,
126}
127
128impl<'a> ModuleGenerator<'a> {
129 #[must_use]
131 pub fn new(abi: &'a MoveModuleABI, config: GeneratorConfig) -> Self {
132 Self {
133 abi,
134 config,
135 source_info: None,
136 }
137 }
138
139 #[must_use]
141 pub fn with_source_info(mut self, source_info: MoveModuleInfo) -> Self {
142 self.source_info = Some(source_info);
143 self
144 }
145
146 fn get_enriched_function(&self, func: &MoveFunction) -> EnrichedFunctionInfo {
148 let source_func = self
149 .source_info
150 .as_ref()
151 .and_then(|s| s.functions.get(&func.name));
152
153 EnrichedFunctionInfo::from_abi_and_source(
154 &func.name,
155 &func.params,
156 func.generic_type_params.len(),
157 source_func,
158 )
159 }
160
161 pub fn generate(&self) -> AptosResult<String> {
167 let mut output = String::new();
168
169 self.write_all(&mut output)
170 .map_err(|e| AptosError::Internal(format!("Code generation failed: {e}")))?;
171
172 Ok(output)
173 }
174
175 fn write_all(&self, output: &mut String) -> std::fmt::Result {
177 self.write_header(output)?;
179
180 self.write_imports(output)?;
182
183 if self.config.include_address_constant {
185 self.write_address_constant(output)?;
186 }
187
188 if self.config.generate_structs {
190 self.write_structs(output)?;
191 }
192
193 if self.config.generate_events {
195 self.write_events(output)?;
196 }
197
198 if self.config.generate_entry_functions {
200 self.write_entry_functions(output)?;
201 }
202
203 if self.config.generate_view_functions {
205 self.write_view_functions(output)?;
206 }
207
208 Ok(())
209 }
210
211 fn write_events(&self, output: &mut String) -> std::fmt::Result {
213 let event_structs: Vec<_> = self
215 .abi
216 .structs
217 .iter()
218 .filter(|s| s.name.ends_with("Event") && !s.is_native)
219 .collect();
220
221 if event_structs.is_empty() {
222 return Ok(());
223 }
224
225 writeln!(output, "// =============================================")?;
226 writeln!(output, "// Event Types")?;
227 writeln!(output, "// =============================================")?;
228 writeln!(output)?;
229
230 writeln!(output, "/// All event types defined in this module.")?;
232 writeln!(output, "#[derive(Debug, Clone, PartialEq)]")?;
233 writeln!(output, "pub enum ModuleEvent {{")?;
234 for struct_def in &event_structs {
235 let variant_name = to_pascal_case(&struct_def.name);
236 writeln!(output, " {variant_name}({variant_name}),")?;
237 }
238 writeln!(output, " /// Unknown event type.")?;
239 writeln!(output, " Unknown(serde_json::Value),")?;
240 writeln!(output, "}}")?;
241 writeln!(output)?;
242
243 writeln!(output, "/// Event type strings for this module.")?;
245 writeln!(output, "pub mod event_types {{")?;
246 for struct_def in &event_structs {
247 let const_name = to_snake_case(&struct_def.name).to_uppercase();
248 writeln!(
249 output,
250 " pub const {}: &str = \"{}::{}::{}\";",
251 const_name,
252 sanitize_abi_string(&self.abi.address),
253 sanitize_abi_string(&self.abi.name),
254 sanitize_abi_string(&struct_def.name)
255 )?;
256 }
257 writeln!(output, "}}")?;
258 writeln!(output)?;
259
260 writeln!(output, "/// Parses a raw event into a typed ModuleEvent.")?;
262 writeln!(output, "///")?;
263 writeln!(output, "/// # Arguments")?;
264 writeln!(output, "///")?;
265 writeln!(output, "/// * `event_type` - The event type string")?;
266 writeln!(output, "/// * `data` - The event data as JSON")?;
267 writeln!(
268 output,
269 "pub fn parse_event(event_type: &str, data: serde_json::Value) -> AptosResult<ModuleEvent> {{"
270 )?;
271 writeln!(output, " match event_type {{")?;
272 for struct_def in &event_structs {
273 let const_name = to_snake_case(&struct_def.name).to_uppercase();
274 let variant_name = to_pascal_case(&struct_def.name);
275 writeln!(output, " event_types::{const_name} => {{")?;
276 writeln!(
277 output,
278 " let event: {variant_name} = serde_json::from_value(data)"
279 )?;
280 writeln!(
281 output,
282 " .map_err(|e| AptosError::Internal(format!(\"Failed to parse {}: {{}}\", e)))?;",
283 struct_def.name
284 )?;
285 writeln!(output, " Ok(ModuleEvent::{variant_name}(event))")?;
286 writeln!(output, " }}")?;
287 }
288 writeln!(output, " _ => Ok(ModuleEvent::Unknown(data)),")?;
289 writeln!(output, " }}")?;
290 writeln!(output, "}}")?;
291 writeln!(output)?;
292
293 writeln!(
295 output,
296 "/// Checks if an event type belongs to this module."
297 )?;
298 writeln!(
299 output,
300 "pub fn is_module_event(event_type: &str) -> bool {{"
301 )?;
302 writeln!(
303 output,
304 " event_type.starts_with(\"{}::{}::\")",
305 sanitize_abi_string(&self.abi.address),
306 sanitize_abi_string(&self.abi.name)
307 )?;
308 writeln!(output, "}}")?;
309 writeln!(output)
310 }
311
312 fn write_header(&self, output: &mut String) -> std::fmt::Result {
314 writeln!(
315 output,
316 "//! Generated Rust bindings for `{}::{}`.",
317 sanitize_abi_string(&self.abi.address),
318 sanitize_abi_string(&self.abi.name)
319 )?;
320 writeln!(output, "//!")?;
321 writeln!(
322 output,
323 "//! This file was auto-generated by aptos-sdk codegen."
324 )?;
325 writeln!(output, "//! Do not edit manually.")?;
326 writeln!(output)?;
327 writeln!(output, "#![allow(dead_code)]")?;
328 writeln!(output, "#![allow(unused_imports)]")?;
329 writeln!(output)
330 }
331
332 #[allow(clippy::unused_self)] fn write_imports(&self, output: &mut String) -> std::fmt::Result {
335 writeln!(output, "use aptos_sdk::{{")?;
336 writeln!(output, " account::Account,")?;
337 writeln!(output, " error::{{AptosError, AptosResult}},")?;
338 writeln!(
339 output,
340 " transaction::{{EntryFunction, TransactionPayload}},"
341 )?;
342 writeln!(output, " types::{{AccountAddress, TypeTag}},")?;
343 writeln!(output, " Aptos,")?;
344 writeln!(output, "}};")?;
345 writeln!(output, "use serde::{{Deserialize, Serialize}};")?;
346 writeln!(output)
347 }
348
349 fn write_address_constant(&self, output: &mut String) -> std::fmt::Result {
351 writeln!(output, "/// The address where this module is deployed.")?;
352 writeln!(
353 output,
354 "pub const MODULE_ADDRESS: &str = \"{}\";",
355 sanitize_abi_string(&self.abi.address)
356 )?;
357 writeln!(output)?;
358 writeln!(output, "/// The module name.")?;
359 writeln!(
360 output,
361 "pub const MODULE_NAME: &str = \"{}\";",
362 sanitize_abi_string(&self.abi.name)
363 )?;
364 writeln!(output)
365 }
366
367 fn write_structs(&self, output: &mut String) -> std::fmt::Result {
369 if self.abi.structs.is_empty() {
370 return Ok(());
371 }
372
373 writeln!(output, "// =============================================")?;
374 writeln!(output, "// Struct Definitions")?;
375 writeln!(output, "// =============================================")?;
376 writeln!(output)?;
377
378 for struct_def in &self.abi.structs {
379 self.write_struct(output, struct_def)?;
380 }
381
382 Ok(())
383 }
384
385 fn write_struct(&self, output: &mut String, struct_def: &MoveStructDef) -> std::fmt::Result {
387 if struct_def.is_native {
389 return Ok(());
390 }
391
392 let rust_name = to_pascal_case(&struct_def.name);
393
394 writeln!(
397 output,
398 "/// Move struct: `{}::{}`",
399 sanitize_abi_string(&self.abi.name),
400 sanitize_abi_string(&struct_def.name)
401 )?;
402 if !struct_def.abilities.is_empty() {
403 let abilities = struct_def
404 .abilities
405 .iter()
406 .map(|a| sanitize_abi_string(a))
407 .collect::<Vec<_>>()
408 .join(", ");
409 writeln!(output, "///")?;
410 writeln!(output, "/// Abilities: {abilities}")?;
411 }
412
413 writeln!(
415 output,
416 "#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]"
417 )?;
418
419 if struct_def.generic_type_params.is_empty() {
421 writeln!(output, "pub struct {rust_name} {{")?;
422 } else {
423 let type_params: Vec<String> = struct_def
424 .generic_type_params
425 .iter()
426 .enumerate()
427 .map(|(i, _)| format!("T{i}"))
428 .collect();
429
430 writeln!(
431 output,
432 "pub struct {}<{}> {{",
433 rust_name,
434 type_params.join(", ")
435 )?;
436 }
437
438 for field in &struct_def.fields {
440 self.write_struct_field(output, field)?;
441 }
442
443 writeln!(output, "}}")?;
444 writeln!(output)
445 }
446
447 fn write_struct_field(&self, output: &mut String, field: &MoveStructField) -> std::fmt::Result {
449 let rust_type = self.config.type_mapper.map_type(&field.typ);
450 let rust_name = to_snake_case(&field.name);
451
452 let rust_name = match rust_name.as_str() {
454 "type" => "r#type".to_string(),
455 "self" => "r#self".to_string(),
456 "move" => "r#move".to_string(),
457 _ => rust_name,
458 };
459
460 if let Some(doc) = &rust_type.doc {
461 writeln!(output, " /// {}", sanitize_abi_string(doc))?;
463 }
464
465 if rust_name != field.name && !rust_name.starts_with("r#") {
467 writeln!(
469 output,
470 " #[serde(rename = \"{}\")]",
471 sanitize_abi_string(&field.name)
472 )?;
473 }
474
475 writeln!(output, " pub {}: {},", rust_name, rust_type.path)
476 }
477
478 fn write_entry_functions(&self, output: &mut String) -> std::fmt::Result {
480 let entry_functions: Vec<_> = self
481 .abi
482 .exposed_functions
483 .iter()
484 .filter(|f| f.is_entry)
485 .collect();
486
487 if entry_functions.is_empty() {
488 return Ok(());
489 }
490
491 writeln!(output, "// =============================================")?;
492 writeln!(output, "// Entry Functions")?;
493 writeln!(output, "// =============================================")?;
494 writeln!(output)?;
495
496 for func in entry_functions {
497 self.write_entry_function(output, func)?;
498 }
499
500 Ok(())
501 }
502
503 fn write_entry_function(&self, output: &mut String, func: &MoveFunction) -> std::fmt::Result {
505 let rust_name = to_snake_case(&func.name);
506 let enriched = self.get_enriched_function(func);
507
508 let params: Vec<_> = enriched
510 .non_signer_params()
511 .into_iter()
512 .map(|p| {
513 let rust_type = self.config.type_mapper.map_type(&p.move_type);
514 (p.name.clone(), p.move_type.clone(), rust_type)
515 })
516 .collect();
517
518 if let Some(doc) = &enriched.doc {
521 for line in doc.lines() {
522 writeln!(output, "/// {}", sanitize_abi_string(line))?;
523 }
524 writeln!(output, "///")?;
525 } else {
526 writeln!(
527 output,
528 "/// Entry function: `{}::{}`",
529 sanitize_abi_string(&self.abi.name),
530 sanitize_abi_string(&func.name)
531 )?;
532 writeln!(output, "///")?;
533 }
534
535 if !params.is_empty() {
537 writeln!(output, "/// # Arguments")?;
538 writeln!(output, "///")?;
539 for (name, move_type, rust_type) in ¶ms {
540 writeln!(
541 output,
542 "/// * `{}` - {} (Move type: `{}`)",
543 sanitize_abi_string(name),
544 sanitize_abi_string(&rust_type.path),
545 sanitize_abi_string(move_type)
546 )?;
547 }
548 }
549 if !enriched.type_param_names.is_empty() {
550 let type_params = enriched
551 .type_param_names
552 .iter()
553 .map(|s| sanitize_abi_string(s))
554 .collect::<Vec<_>>()
555 .join(", ");
556 writeln!(output, "/// * `type_args` - Type arguments: {type_params}")?;
557 }
558
559 write!(output, "pub fn {rust_name}(")?;
561
562 let mut param_strs = Vec::new();
564 for (name, _, rust_type) in ¶ms {
565 let safe_name = Self::safe_param_name(name);
567 param_strs.push(format!("{}: {}", safe_name, rust_type.as_arg_type()));
568 }
569 if !enriched.type_param_names.is_empty() {
570 param_strs.push("type_args: Vec<TypeTag>".to_string());
571 }
572 write!(output, "{}", param_strs.join(", "))?;
573
574 writeln!(output, ") -> AptosResult<TransactionPayload> {{")?;
575
576 writeln!(output, " let function_id = format!(")?;
579 writeln!(
580 output,
581 " \"{}::{}::{}\",",
582 sanitize_abi_string(&self.abi.address),
583 sanitize_abi_string(&self.abi.name),
584 sanitize_abi_string(&func.name)
585 )?;
586 writeln!(output, " );")?;
587 writeln!(output)?;
588
589 writeln!(output, " let args = vec![")?;
591 for (name, move_type, _) in ¶ms {
592 let safe_name = Self::safe_param_name(name);
593 let bcs_expr = self.config.type_mapper.to_bcs_arg(move_type, &safe_name);
594 writeln!(output, " {bcs_expr},")?;
595 }
596 writeln!(output, " ];")?;
597 writeln!(output)?;
598
599 if func.generic_type_params.is_empty() {
601 writeln!(output, " let type_args = vec![];")?;
602 }
603 writeln!(output)?;
604
605 writeln!(
607 output,
608 " let entry_fn = EntryFunction::from_function_id(&function_id, type_args, args)?;"
609 )?;
610 writeln!(
611 output,
612 " Ok(TransactionPayload::EntryFunction(entry_fn))"
613 )?;
614 writeln!(output, "}}")?;
615 writeln!(output)
616 }
617
618 fn write_view_functions(&self, output: &mut String) -> std::fmt::Result {
620 let view_functions: Vec<_> = self
621 .abi
622 .exposed_functions
623 .iter()
624 .filter(|f| f.is_view)
625 .collect();
626
627 if view_functions.is_empty() {
628 return Ok(());
629 }
630
631 writeln!(output, "// =============================================")?;
632 writeln!(output, "// View Functions")?;
633 writeln!(output, "// =============================================")?;
634 writeln!(output)?;
635
636 for func in view_functions {
637 self.write_view_function(output, func)?;
638 }
639
640 Ok(())
641 }
642
643 fn safe_param_name(name: &str) -> String {
645 let snake = to_snake_case(name);
646 match snake.as_str() {
647 "type" => "r#type".to_string(),
648 "self" => "r#self".to_string(),
649 "move" => "r#move".to_string(),
650 "ref" => "r#ref".to_string(),
651 "mut" => "r#mut".to_string(),
652 "fn" => "r#fn".to_string(),
653 "mod" => "r#mod".to_string(),
654 "use" => "r#use".to_string(),
655 "pub" => "r#pub".to_string(),
656 "let" => "r#let".to_string(),
657 "if" => "r#if".to_string(),
658 "else" => "r#else".to_string(),
659 "match" => "r#match".to_string(),
660 "loop" => "r#loop".to_string(),
661 "while" => "r#while".to_string(),
662 "for" => "r#for".to_string(),
663 "in" => "r#in".to_string(),
664 "return" => "r#return".to_string(),
665 "break" => "r#break".to_string(),
666 "continue" => "r#continue".to_string(),
667 "async" => "r#async".to_string(),
668 "await" => "r#await".to_string(),
669 "struct" => "r#struct".to_string(),
670 "enum" => "r#enum".to_string(),
671 "trait" => "r#trait".to_string(),
672 "impl" => "r#impl".to_string(),
673 "dyn" => "r#dyn".to_string(),
674 "const" => "r#const".to_string(),
675 "static" => "r#static".to_string(),
676 "unsafe" => "r#unsafe".to_string(),
677 "extern" => "r#extern".to_string(),
678 "crate" => "r#crate".to_string(),
679 "super" => "r#super".to_string(),
680 "where" => "r#where".to_string(),
681 "as" => "r#as".to_string(),
682 "true" => "r#true".to_string(),
683 "false" => "r#false".to_string(),
684 _ => snake,
685 }
686 }
687
688 fn write_view_function(&self, output: &mut String, func: &MoveFunction) -> std::fmt::Result {
690 let rust_name = format!("view_{}", to_snake_case(&func.name));
691 let enriched = self.get_enriched_function(func);
692
693 let params: Vec<_> = enriched
695 .params
696 .iter()
697 .map(|p| {
698 let rust_type = self.config.type_mapper.map_type(&p.move_type);
699 (p.name.clone(), p.move_type.clone(), rust_type)
700 })
701 .collect();
702
703 let return_type = if func.returns.is_empty() {
705 "()".to_string()
706 } else if func.returns.len() == 1 {
707 self.config.type_mapper.map_type(&func.returns[0]).path
708 } else {
709 let types: Vec<String> = func
710 .returns
711 .iter()
712 .map(|r| self.config.type_mapper.map_type(r).path)
713 .collect();
714 format!("({})", types.join(", "))
715 };
716
717 if let Some(doc) = &enriched.doc {
720 for line in doc.lines() {
721 writeln!(output, "/// {}", sanitize_abi_string(line))?;
722 }
723 writeln!(output, "///")?;
724 } else {
725 writeln!(
726 output,
727 "/// View function: `{}::{}`",
728 sanitize_abi_string(&self.abi.name),
729 sanitize_abi_string(&func.name)
730 )?;
731 writeln!(output, "///")?;
732 }
733
734 if !params.is_empty() {
736 writeln!(output, "/// # Arguments")?;
737 writeln!(output, "///")?;
738 for (name, move_type, rust_type) in ¶ms {
739 writeln!(
740 output,
741 "/// * `{}` - {} (Move type: `{}`)",
742 sanitize_abi_string(name),
743 sanitize_abi_string(&rust_type.path),
744 sanitize_abi_string(move_type)
745 )?;
746 }
747 }
748 if !enriched.type_param_names.is_empty() {
749 let type_params = enriched
750 .type_param_names
751 .iter()
752 .map(|s| sanitize_abi_string(s))
753 .collect::<Vec<_>>()
754 .join(", ");
755 writeln!(output, "/// * `type_args` - Type arguments: {type_params}")?;
756 }
757 if !func.returns.is_empty() {
758 writeln!(output, "///")?;
759 writeln!(output, "/// # Returns")?;
760 writeln!(output, "///")?;
761 writeln!(output, "/// `{}`", sanitize_abi_string(&return_type))?;
762 }
763
764 let async_kw = if self.config.async_functions {
766 "async "
767 } else {
768 ""
769 };
770
771 write!(output, "pub {async_kw}fn {rust_name}(aptos: &Aptos")?;
772
773 for (name, _, rust_type) in ¶ms {
775 let safe_name = Self::safe_param_name(name);
776 write!(output, ", {}: {}", safe_name, rust_type.as_arg_type())?;
777 }
778 if !enriched.type_param_names.is_empty() {
779 write!(output, ", type_args: Vec<String>")?;
780 }
781
782 writeln!(output, ") -> AptosResult<Vec<serde_json::Value>> {{")?;
783
784 writeln!(output, " let function_id = format!(")?;
787 writeln!(
788 output,
789 " \"{}::{}::{}\",",
790 sanitize_abi_string(&self.abi.address),
791 sanitize_abi_string(&self.abi.name),
792 sanitize_abi_string(&func.name)
793 )?;
794 writeln!(output, " );")?;
795 writeln!(output)?;
796
797 if enriched.type_param_names.is_empty() {
799 writeln!(output, " let type_args: Vec<String> = vec![];")?;
800 }
801
802 writeln!(output, " let args = vec![")?;
804 for (name, move_type, _) in ¶ms {
805 let safe_name = Self::safe_param_name(name);
806 let arg_expr = self.view_arg_json_expr(move_type, &safe_name);
807 writeln!(output, " {arg_expr},")?;
808 }
809 writeln!(output, " ];")?;
810 writeln!(output)?;
811
812 let await_kw = if self.config.async_functions {
814 ".await"
815 } else {
816 ""
817 };
818 writeln!(
819 output,
820 " aptos.view(&function_id, type_args, args){await_kw}"
821 )?;
822 writeln!(output, "}}")?;
823 writeln!(output)
824 }
825
826 #[allow(clippy::unused_self)] fn view_arg_json_expr(&self, move_type: &str, var_name: &str) -> String {
829 match move_type {
830 "address" => format!("serde_json::json!({var_name}.to_string())"),
831 "bool" | "u8" | "u16" | "u32" | "u64" | "u128" => {
832 format!("serde_json::json!({var_name}.to_string())")
833 }
834 _ if move_type.starts_with("vector<u8>") => {
835 format!("serde_json::json!(::aptos_sdk::const_hex::encode({var_name}))")
836 }
837 "0x1::string::String" => format!("serde_json::json!({var_name})"),
838 _ if move_type.ends_with("::string::String") => {
839 format!("serde_json::json!({var_name})")
840 }
841 _ => format!("serde_json::json!({var_name})"),
842 }
843 }
844}
845
846#[cfg(test)]
847mod tests {
848 use super::*;
849 use crate::api::response::{MoveFunctionGenericTypeParam, MoveStructGenericTypeParam};
850
851 fn sample_abi() -> MoveModuleABI {
852 MoveModuleABI {
853 address: "0x1".to_string(),
854 name: "coin".to_string(),
855 exposed_functions: vec![
856 MoveFunction {
857 name: "transfer".to_string(),
858 visibility: "public".to_string(),
859 is_entry: true,
860 is_view: false,
861 generic_type_params: vec![MoveFunctionGenericTypeParam {
862 constraints: vec![],
863 }],
864 params: vec![
865 "&signer".to_string(),
866 "address".to_string(),
867 "u64".to_string(),
868 ],
869 returns: vec![],
870 },
871 MoveFunction {
872 name: "balance".to_string(),
873 visibility: "public".to_string(),
874 is_entry: false,
875 is_view: true,
876 generic_type_params: vec![MoveFunctionGenericTypeParam {
877 constraints: vec![],
878 }],
879 params: vec!["address".to_string()],
880 returns: vec!["u64".to_string()],
881 },
882 ],
883 structs: vec![MoveStructDef {
884 name: "Coin".to_string(),
885 is_native: false,
886 abilities: vec!["store".to_string()],
887 generic_type_params: vec![MoveStructGenericTypeParam {
888 constraints: vec![],
889 }],
890 fields: vec![MoveStructField {
891 name: "value".to_string(),
892 typ: "u64".to_string(),
893 }],
894 }],
895 }
896 }
897
898 fn sample_abi_with_events() -> MoveModuleABI {
899 MoveModuleABI {
900 address: "0x1".to_string(),
901 name: "token".to_string(),
902 exposed_functions: vec![],
903 structs: vec![
904 MoveStructDef {
905 name: "MintEvent".to_string(),
906 is_native: false,
907 abilities: vec!["drop".to_string(), "store".to_string()],
908 generic_type_params: vec![],
909 fields: vec![
910 MoveStructField {
911 name: "id".to_string(),
912 typ: "u64".to_string(),
913 },
914 MoveStructField {
915 name: "creator".to_string(),
916 typ: "address".to_string(),
917 },
918 ],
919 },
920 MoveStructDef {
921 name: "BurnEvent".to_string(),
922 is_native: false,
923 abilities: vec!["drop".to_string(), "store".to_string()],
924 generic_type_params: vec![],
925 fields: vec![MoveStructField {
926 name: "id".to_string(),
927 typ: "u64".to_string(),
928 }],
929 },
930 MoveStructDef {
931 name: "Token".to_string(),
932 is_native: false,
933 abilities: vec!["key".to_string()],
934 generic_type_params: vec![],
935 fields: vec![MoveStructField {
936 name: "value".to_string(),
937 typ: "u64".to_string(),
938 }],
939 },
940 ],
941 }
942 }
943
944 #[test]
945 fn test_generate_module() {
946 let abi = sample_abi();
947 let generator = ModuleGenerator::new(&abi, GeneratorConfig::default());
948 let code = generator.generate().unwrap();
949
950 assert!(code.contains("Generated Rust bindings"));
952 assert!(code.contains("0x1::coin"));
953
954 assert!(code.contains("MODULE_ADDRESS"));
956 assert!(code.contains("MODULE_NAME"));
957
958 assert!(code.contains("pub struct Coin"));
960 assert!(code.contains("pub value: u64"));
961
962 assert!(code.contains("pub fn transfer"));
964
965 assert!(code.contains("pub async fn view_balance"));
967 }
968
969 #[test]
970 fn test_entry_function_excludes_signer() {
971 let abi = sample_abi();
972 let generator = ModuleGenerator::new(&abi, GeneratorConfig::default());
973 let code = generator.generate().unwrap();
974
975 assert!(code.contains("addr: AccountAddress"));
978 assert!(code.contains("amount: u64"));
979 assert!(!code.contains("account: AccountAddress"));
981 assert!(code.contains("pub fn transfer("));
983 }
984
985 #[test]
986 fn test_entry_function_with_source_info() {
987 use crate::codegen::move_parser::MoveSourceParser;
988
989 let abi = sample_abi();
990 let source = r"
991 module 0x1::coin {
992 /// Transfers coins from sender to recipient.
993 public entry fun transfer<CoinType>(
994 from: &signer,
995 to: address,
996 value: u64,
997 ) { }
998 }
999 ";
1000 let source_info = MoveSourceParser::parse(source);
1001
1002 let generator =
1003 ModuleGenerator::new(&abi, GeneratorConfig::default()).with_source_info(source_info);
1004 let code = generator.generate().unwrap();
1005
1006 assert!(code.contains("to: AccountAddress"));
1008 assert!(code.contains("value: u64"));
1009 assert!(code.contains("Transfers coins from sender to recipient"));
1011 }
1012
1013 #[test]
1014 fn test_generate_events() {
1015 let abi = sample_abi_with_events();
1016 let generator = ModuleGenerator::new(&abi, GeneratorConfig::default());
1017 let code = generator.generate().unwrap();
1018
1019 assert!(code.contains("pub enum ModuleEvent"));
1021 assert!(code.contains("MintEvent(MintEvent)"));
1022 assert!(code.contains("BurnEvent(BurnEvent)"));
1023 assert!(code.contains("Unknown(serde_json::Value)"));
1024
1025 assert!(code.contains("pub mod event_types"));
1027 assert!(code.contains("MINT_EVENT"));
1028 assert!(code.contains("BURN_EVENT"));
1029
1030 assert!(code.contains("pub fn parse_event"));
1032 assert!(code.contains("is_module_event"));
1033
1034 assert!(!code.contains("Token(Token)"));
1036 }
1037
1038 #[test]
1039 fn test_config_disable_events() {
1040 let abi = sample_abi_with_events();
1041 let config = GeneratorConfig::default().with_events(false);
1042 let generator = ModuleGenerator::new(&abi, config);
1043 let code = generator.generate().unwrap();
1044
1045 assert!(!code.contains("pub enum ModuleEvent"));
1047 assert!(!code.contains("pub fn parse_event"));
1048 }
1049
1050 #[test]
1051 fn test_config_disable_structs() {
1052 let abi = sample_abi();
1053 let config = GeneratorConfig::default().with_structs(false);
1054 let generator = ModuleGenerator::new(&abi, config);
1055 let code = generator.generate().unwrap();
1056
1057 assert!(!code.contains("pub struct Coin"));
1059 }
1060
1061 #[test]
1062 fn test_config_sync_functions() {
1063 let abi = sample_abi();
1064 let config = GeneratorConfig::default().with_async(false);
1065 let generator = ModuleGenerator::new(&abi, config);
1066 let code = generator.generate().unwrap();
1067
1068 assert!(code.contains("pub fn view_balance"));
1070 assert!(!code.contains("pub async fn view_balance"));
1071 }
1072
1073 #[test]
1074 fn test_config_no_address_constant() {
1075 let abi = sample_abi();
1076 let config = GeneratorConfig {
1077 include_address_constant: false,
1078 ..Default::default()
1079 };
1080 let generator = ModuleGenerator::new(&abi, config);
1081 let code = generator.generate().unwrap();
1082
1083 assert!(!code.contains("MODULE_ADDRESS"));
1085 }
1086
1087 #[test]
1088 fn test_generator_config_builder() {
1089 let config = GeneratorConfig::new()
1090 .with_module_name("custom_name")
1091 .with_entry_functions(false)
1092 .with_view_functions(false)
1093 .with_structs(false)
1094 .with_events(false)
1095 .with_async(false)
1096 .with_builder_pattern(true);
1097
1098 assert_eq!(config.module_name, Some("custom_name".to_string()));
1099 assert!(!config.generate_entry_functions);
1100 assert!(!config.generate_view_functions);
1101 assert!(!config.generate_structs);
1102 assert!(!config.generate_events);
1103 assert!(!config.async_functions);
1104 assert!(config.use_builder_pattern);
1105 }
1106
1107 #[test]
1108 fn test_empty_module() {
1109 let abi = MoveModuleABI {
1110 address: "0x1".to_string(),
1111 name: "empty".to_string(),
1112 exposed_functions: vec![],
1113 structs: vec![],
1114 };
1115 let generator = ModuleGenerator::new(&abi, GeneratorConfig::default());
1116 let code = generator.generate().unwrap();
1117
1118 assert!(code.contains("Generated Rust bindings"));
1120 assert!(code.contains("MODULE_ADDRESS"));
1121 assert!(code.contains("MODULE_NAME"));
1122 }
1123}