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, MoveU256, 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 let type_params: Vec<String> = struct_def
421 .generic_type_params
422 .iter()
423 .enumerate()
424 .map(|(i, _)| format!("T{i}"))
425 .collect();
426
427 if type_params.is_empty() {
428 writeln!(output, "pub struct {rust_name} {{")?;
429 } else {
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 if !type_params.is_empty() {
448 let phantom_ty = if type_params.len() == 1 {
451 type_params[0].clone()
452 } else {
453 format!("({})", type_params.join(", "))
454 };
455 writeln!(output, " #[serde(skip)]")?;
456 writeln!(
457 output,
458 " _phantom: ::core::marker::PhantomData<{phantom_ty}>,"
459 )?;
460 }
461
462 writeln!(output, "}}")?;
463 writeln!(output)
464 }
465
466 fn write_struct_field(&self, output: &mut String, field: &MoveStructField) -> std::fmt::Result {
468 let rust_type = self.config.type_mapper.map_type(&field.typ);
469 let rust_name = to_snake_case(&field.name);
470
471 let rust_name = match rust_name.as_str() {
475 "type" => "r#type".to_string(),
476 "self" => "self_".to_string(),
477 "move" => "r#move".to_string(),
478 _ => rust_name,
479 };
480
481 if let Some(doc) = &rust_type.doc {
482 writeln!(output, " /// {}", sanitize_abi_string(doc))?;
484 }
485
486 if rust_name != field.name && !rust_name.starts_with("r#") {
488 writeln!(
490 output,
491 " #[serde(rename = \"{}\")]",
492 sanitize_abi_string(&field.name)
493 )?;
494 }
495
496 writeln!(output, " pub {}: {},", rust_name, rust_type.path)
497 }
498
499 fn write_entry_functions(&self, output: &mut String) -> std::fmt::Result {
501 let entry_functions: Vec<_> = self
502 .abi
503 .exposed_functions
504 .iter()
505 .filter(|f| f.is_entry)
506 .collect();
507
508 if entry_functions.is_empty() {
509 return Ok(());
510 }
511
512 writeln!(output, "// =============================================")?;
513 writeln!(output, "// Entry Functions")?;
514 writeln!(output, "// =============================================")?;
515 writeln!(output)?;
516
517 for func in entry_functions {
518 self.write_entry_function(output, func)?;
519 }
520
521 Ok(())
522 }
523
524 fn write_entry_function(&self, output: &mut String, func: &MoveFunction) -> std::fmt::Result {
526 let rust_name = to_snake_case(&func.name);
527 let enriched = self.get_enriched_function(func);
528
529 let params: Vec<_> = enriched
531 .non_signer_params()
532 .into_iter()
533 .map(|p| {
534 let rust_type = self.config.type_mapper.map_type(&p.move_type);
535 (p.name.clone(), p.move_type.clone(), rust_type)
536 })
537 .collect();
538
539 if let Some(doc) = &enriched.doc {
542 for line in doc.lines() {
543 writeln!(output, "/// {}", sanitize_abi_string(line))?;
544 }
545 writeln!(output, "///")?;
546 } else {
547 writeln!(
548 output,
549 "/// Entry function: `{}::{}`",
550 sanitize_abi_string(&self.abi.name),
551 sanitize_abi_string(&func.name)
552 )?;
553 writeln!(output, "///")?;
554 }
555
556 if !params.is_empty() {
558 writeln!(output, "/// # Arguments")?;
559 writeln!(output, "///")?;
560 for (name, move_type, rust_type) in ¶ms {
561 writeln!(
562 output,
563 "/// * `{}` - {} (Move type: `{}`)",
564 sanitize_abi_string(name),
565 sanitize_abi_string(&rust_type.path),
566 sanitize_abi_string(move_type)
567 )?;
568 }
569 }
570 if !enriched.type_param_names.is_empty() {
571 let type_params = enriched
572 .type_param_names
573 .iter()
574 .map(|s| sanitize_abi_string(s))
575 .collect::<Vec<_>>()
576 .join(", ");
577 writeln!(output, "/// * `type_args` - Type arguments: {type_params}")?;
578 }
579
580 write!(output, "pub fn {rust_name}(")?;
582
583 let mut param_strs = Vec::new();
585 for (name, _, rust_type) in ¶ms {
586 let safe_name = Self::safe_param_name(name);
588 param_strs.push(format!("{}: {}", safe_name, rust_type.as_arg_type()));
589 }
590 if !enriched.type_param_names.is_empty() {
591 param_strs.push("type_args: Vec<TypeTag>".to_string());
592 }
593 write!(output, "{}", param_strs.join(", "))?;
594
595 writeln!(output, ") -> AptosResult<TransactionPayload> {{")?;
596
597 writeln!(output, " let function_id = format!(")?;
600 writeln!(
601 output,
602 " \"{}::{}::{}\",",
603 sanitize_abi_string(&self.abi.address),
604 sanitize_abi_string(&self.abi.name),
605 sanitize_abi_string(&func.name)
606 )?;
607 writeln!(output, " );")?;
608 writeln!(output)?;
609
610 writeln!(output, " let args = vec![")?;
612 for (name, move_type, _) in ¶ms {
613 let safe_name = Self::safe_param_name(name);
614 let bcs_expr = self.config.type_mapper.to_bcs_arg(move_type, &safe_name);
615 writeln!(output, " {bcs_expr},")?;
616 }
617 writeln!(output, " ];")?;
618 writeln!(output)?;
619
620 if func.generic_type_params.is_empty() {
622 writeln!(output, " let type_args = vec![];")?;
623 }
624 writeln!(output)?;
625
626 writeln!(
628 output,
629 " let entry_fn = EntryFunction::from_function_id(&function_id, type_args, args)?;"
630 )?;
631 writeln!(
632 output,
633 " Ok(TransactionPayload::EntryFunction(entry_fn))"
634 )?;
635 writeln!(output, "}}")?;
636 writeln!(output)
637 }
638
639 fn write_view_functions(&self, output: &mut String) -> std::fmt::Result {
641 let view_functions: Vec<_> = self
642 .abi
643 .exposed_functions
644 .iter()
645 .filter(|f| f.is_view)
646 .collect();
647
648 if view_functions.is_empty() {
649 return Ok(());
650 }
651
652 writeln!(output, "// =============================================")?;
653 writeln!(output, "// View Functions")?;
654 writeln!(output, "// =============================================")?;
655 writeln!(output)?;
656
657 for func in view_functions {
658 self.write_view_function(output, func)?;
659 }
660
661 Ok(())
662 }
663
664 fn safe_param_name(name: &str) -> String {
666 let snake = to_snake_case(name);
667 match snake.as_str() {
668 "type" => "r#type".to_string(),
669 "self" => "self_".to_string(),
671 "move" => "r#move".to_string(),
672 "ref" => "r#ref".to_string(),
673 "mut" => "r#mut".to_string(),
674 "fn" => "r#fn".to_string(),
675 "mod" => "r#mod".to_string(),
676 "use" => "r#use".to_string(),
677 "pub" => "r#pub".to_string(),
678 "let" => "r#let".to_string(),
679 "if" => "r#if".to_string(),
680 "else" => "r#else".to_string(),
681 "match" => "r#match".to_string(),
682 "loop" => "r#loop".to_string(),
683 "while" => "r#while".to_string(),
684 "for" => "r#for".to_string(),
685 "in" => "r#in".to_string(),
686 "return" => "r#return".to_string(),
687 "break" => "r#break".to_string(),
688 "continue" => "r#continue".to_string(),
689 "async" => "r#async".to_string(),
690 "await" => "r#await".to_string(),
691 "struct" => "r#struct".to_string(),
692 "enum" => "r#enum".to_string(),
693 "trait" => "r#trait".to_string(),
694 "impl" => "r#impl".to_string(),
695 "dyn" => "r#dyn".to_string(),
696 "const" => "r#const".to_string(),
697 "static" => "r#static".to_string(),
698 "unsafe" => "r#unsafe".to_string(),
699 "extern" => "r#extern".to_string(),
700 "crate" => "crate_".to_string(),
701 "super" => "super_".to_string(),
702 "where" => "r#where".to_string(),
703 "as" => "r#as".to_string(),
704 "true" => "r#true".to_string(),
705 "false" => "r#false".to_string(),
706 _ => snake,
707 }
708 }
709
710 fn write_view_function(&self, output: &mut String, func: &MoveFunction) -> std::fmt::Result {
712 let rust_name = format!("view_{}", to_snake_case(&func.name));
713 let enriched = self.get_enriched_function(func);
714
715 let params: Vec<_> = enriched
717 .params
718 .iter()
719 .map(|p| {
720 let rust_type = self.config.type_mapper.map_type(&p.move_type);
721 (p.name.clone(), p.move_type.clone(), rust_type)
722 })
723 .collect();
724
725 let return_type = if func.returns.is_empty() {
727 "()".to_string()
728 } else if func.returns.len() == 1 {
729 self.config.type_mapper.map_type(&func.returns[0]).path
730 } else {
731 let types: Vec<String> = func
732 .returns
733 .iter()
734 .map(|r| self.config.type_mapper.map_type(r).path)
735 .collect();
736 format!("({})", types.join(", "))
737 };
738
739 if let Some(doc) = &enriched.doc {
742 for line in doc.lines() {
743 writeln!(output, "/// {}", sanitize_abi_string(line))?;
744 }
745 writeln!(output, "///")?;
746 } else {
747 writeln!(
748 output,
749 "/// View function: `{}::{}`",
750 sanitize_abi_string(&self.abi.name),
751 sanitize_abi_string(&func.name)
752 )?;
753 writeln!(output, "///")?;
754 }
755
756 if !params.is_empty() {
758 writeln!(output, "/// # Arguments")?;
759 writeln!(output, "///")?;
760 for (name, move_type, rust_type) in ¶ms {
761 writeln!(
762 output,
763 "/// * `{}` - {} (Move type: `{}`)",
764 sanitize_abi_string(name),
765 sanitize_abi_string(&rust_type.path),
766 sanitize_abi_string(move_type)
767 )?;
768 }
769 }
770 if !enriched.type_param_names.is_empty() {
771 let type_params = enriched
772 .type_param_names
773 .iter()
774 .map(|s| sanitize_abi_string(s))
775 .collect::<Vec<_>>()
776 .join(", ");
777 writeln!(output, "/// * `type_args` - Type arguments: {type_params}")?;
778 }
779 if !func.returns.is_empty() {
780 writeln!(output, "///")?;
781 writeln!(output, "/// # Returns")?;
782 writeln!(output, "///")?;
783 writeln!(output, "/// `{}`", sanitize_abi_string(&return_type))?;
784 }
785
786 let async_kw = if self.config.async_functions {
788 "async "
789 } else {
790 ""
791 };
792
793 write!(output, "pub {async_kw}fn {rust_name}(aptos: &Aptos")?;
794
795 for (name, _, rust_type) in ¶ms {
797 let safe_name = Self::safe_param_name(name);
798 write!(output, ", {}: {}", safe_name, rust_type.as_arg_type())?;
799 }
800 if !enriched.type_param_names.is_empty() {
801 write!(output, ", type_args: Vec<String>")?;
802 }
803
804 writeln!(output, ") -> AptosResult<Vec<serde_json::Value>> {{")?;
805
806 writeln!(output, " let function_id = format!(")?;
809 writeln!(
810 output,
811 " \"{}::{}::{}\",",
812 sanitize_abi_string(&self.abi.address),
813 sanitize_abi_string(&self.abi.name),
814 sanitize_abi_string(&func.name)
815 )?;
816 writeln!(output, " );")?;
817 writeln!(output)?;
818
819 if enriched.type_param_names.is_empty() {
821 writeln!(output, " let type_args: Vec<String> = vec![];")?;
822 }
823
824 writeln!(output, " let args = vec![")?;
826 for (name, move_type, _) in ¶ms {
827 let safe_name = Self::safe_param_name(name);
828 let arg_expr = self.view_arg_json_expr(move_type, &safe_name);
829 writeln!(output, " {arg_expr},")?;
830 }
831 writeln!(output, " ];")?;
832 writeln!(output)?;
833
834 let await_kw = if self.config.async_functions {
836 ".await"
837 } else {
838 ""
839 };
840 writeln!(
841 output,
842 " aptos.view(&function_id, type_args, args){await_kw}"
843 )?;
844 writeln!(output, "}}")?;
845 writeln!(output)
846 }
847
848 #[allow(clippy::unused_self)] fn view_arg_json_expr(&self, move_type: &str, var_name: &str) -> String {
851 match move_type {
852 "address" => format!("serde_json::json!({var_name}.to_string())"),
853 "bool" | "u8" | "u16" | "u32" | "u64" | "u128" => {
854 format!("serde_json::json!({var_name}.to_string())")
855 }
856 _ if move_type.starts_with("vector<u8>") => {
857 format!("serde_json::json!(::aptos_sdk::const_hex::encode({var_name}))")
858 }
859 "0x1::string::String" => format!("serde_json::json!({var_name})"),
860 _ if move_type.ends_with("::string::String") => {
861 format!("serde_json::json!({var_name})")
862 }
863 _ => format!("serde_json::json!({var_name})"),
864 }
865 }
866}
867
868#[cfg(test)]
869mod tests {
870 use super::*;
871 use crate::api::response::{MoveFunctionGenericTypeParam, MoveStructGenericTypeParam};
872
873 fn sample_abi() -> MoveModuleABI {
874 MoveModuleABI {
875 address: "0x1".to_string(),
876 name: "coin".to_string(),
877 exposed_functions: vec![
878 MoveFunction {
879 name: "transfer".to_string(),
880 visibility: "public".to_string(),
881 is_entry: true,
882 is_view: false,
883 generic_type_params: vec![MoveFunctionGenericTypeParam {
884 constraints: vec![],
885 }],
886 params: vec![
887 "&signer".to_string(),
888 "address".to_string(),
889 "u64".to_string(),
890 ],
891 returns: vec![],
892 },
893 MoveFunction {
894 name: "balance".to_string(),
895 visibility: "public".to_string(),
896 is_entry: false,
897 is_view: true,
898 generic_type_params: vec![MoveFunctionGenericTypeParam {
899 constraints: vec![],
900 }],
901 params: vec!["address".to_string()],
902 returns: vec!["u64".to_string()],
903 },
904 ],
905 structs: vec![MoveStructDef {
906 name: "Coin".to_string(),
907 is_native: false,
908 abilities: vec!["store".to_string()],
909 generic_type_params: vec![MoveStructGenericTypeParam {
910 constraints: vec![],
911 }],
912 fields: vec![MoveStructField {
913 name: "value".to_string(),
914 typ: "u64".to_string(),
915 }],
916 }],
917 }
918 }
919
920 fn sample_abi_with_events() -> MoveModuleABI {
921 MoveModuleABI {
922 address: "0x1".to_string(),
923 name: "token".to_string(),
924 exposed_functions: vec![],
925 structs: vec![
926 MoveStructDef {
927 name: "MintEvent".to_string(),
928 is_native: false,
929 abilities: vec!["drop".to_string(), "store".to_string()],
930 generic_type_params: vec![],
931 fields: vec![
932 MoveStructField {
933 name: "id".to_string(),
934 typ: "u64".to_string(),
935 },
936 MoveStructField {
937 name: "creator".to_string(),
938 typ: "address".to_string(),
939 },
940 ],
941 },
942 MoveStructDef {
943 name: "BurnEvent".to_string(),
944 is_native: false,
945 abilities: vec!["drop".to_string(), "store".to_string()],
946 generic_type_params: vec![],
947 fields: vec![MoveStructField {
948 name: "id".to_string(),
949 typ: "u64".to_string(),
950 }],
951 },
952 MoveStructDef {
953 name: "Token".to_string(),
954 is_native: false,
955 abilities: vec!["key".to_string()],
956 generic_type_params: vec![],
957 fields: vec![MoveStructField {
958 name: "value".to_string(),
959 typ: "u64".to_string(),
960 }],
961 },
962 ],
963 }
964 }
965
966 #[test]
967 fn test_generate_module() {
968 let abi = sample_abi();
969 let generator = ModuleGenerator::new(&abi, GeneratorConfig::default());
970 let code = generator.generate().unwrap();
971
972 assert!(code.contains("Generated Rust bindings"));
974 assert!(code.contains("0x1::coin"));
975
976 assert!(code.contains("MODULE_ADDRESS"));
978 assert!(code.contains("MODULE_NAME"));
979
980 assert!(code.contains("pub struct Coin"));
982 assert!(code.contains("pub value: u64"));
983
984 assert!(code.contains("pub fn transfer"));
986
987 assert!(code.contains("pub async fn view_balance"));
989 }
990
991 #[test]
992 fn test_entry_function_excludes_signer() {
993 let abi = sample_abi();
994 let generator = ModuleGenerator::new(&abi, GeneratorConfig::default());
995 let code = generator.generate().unwrap();
996
997 assert!(code.contains("addr: AccountAddress"));
1000 assert!(code.contains("amount: u64"));
1001 assert!(!code.contains("account: AccountAddress"));
1003 assert!(code.contains("pub fn transfer("));
1005 }
1006
1007 #[test]
1008 fn test_entry_function_with_source_info() {
1009 use crate::codegen::move_parser::MoveSourceParser;
1010
1011 let abi = sample_abi();
1012 let source = r"
1013 module 0x1::coin {
1014 /// Transfers coins from sender to recipient.
1015 public entry fun transfer<CoinType>(
1016 from: &signer,
1017 to: address,
1018 value: u64,
1019 ) { }
1020 }
1021 ";
1022 let source_info = MoveSourceParser::parse(source);
1023
1024 let generator =
1025 ModuleGenerator::new(&abi, GeneratorConfig::default()).with_source_info(source_info);
1026 let code = generator.generate().unwrap();
1027
1028 assert!(code.contains("to: AccountAddress"));
1030 assert!(code.contains("value: u64"));
1031 assert!(code.contains("Transfers coins from sender to recipient"));
1033 }
1034
1035 #[test]
1036 fn test_generate_events() {
1037 let abi = sample_abi_with_events();
1038 let generator = ModuleGenerator::new(&abi, GeneratorConfig::default());
1039 let code = generator.generate().unwrap();
1040
1041 assert!(code.contains("pub enum ModuleEvent"));
1043 assert!(code.contains("MintEvent(MintEvent)"));
1044 assert!(code.contains("BurnEvent(BurnEvent)"));
1045 assert!(code.contains("Unknown(serde_json::Value)"));
1046
1047 assert!(code.contains("pub mod event_types"));
1049 assert!(code.contains("MINT_EVENT"));
1050 assert!(code.contains("BURN_EVENT"));
1051
1052 assert!(code.contains("pub fn parse_event"));
1054 assert!(code.contains("is_module_event"));
1055
1056 assert!(!code.contains("Token(Token)"));
1058 }
1059
1060 #[test]
1061 fn test_config_disable_events() {
1062 let abi = sample_abi_with_events();
1063 let config = GeneratorConfig::default().with_events(false);
1064 let generator = ModuleGenerator::new(&abi, config);
1065 let code = generator.generate().unwrap();
1066
1067 assert!(!code.contains("pub enum ModuleEvent"));
1069 assert!(!code.contains("pub fn parse_event"));
1070 }
1071
1072 #[test]
1073 fn test_config_disable_structs() {
1074 let abi = sample_abi();
1075 let config = GeneratorConfig::default().with_structs(false);
1076 let generator = ModuleGenerator::new(&abi, config);
1077 let code = generator.generate().unwrap();
1078
1079 assert!(!code.contains("pub struct Coin"));
1081 }
1082
1083 #[test]
1084 fn test_config_sync_functions() {
1085 let abi = sample_abi();
1086 let config = GeneratorConfig::default().with_async(false);
1087 let generator = ModuleGenerator::new(&abi, config);
1088 let code = generator.generate().unwrap();
1089
1090 assert!(code.contains("pub fn view_balance"));
1092 assert!(!code.contains("pub async fn view_balance"));
1093 }
1094
1095 #[test]
1096 fn test_config_no_address_constant() {
1097 let abi = sample_abi();
1098 let config = GeneratorConfig {
1099 include_address_constant: false,
1100 ..Default::default()
1101 };
1102 let generator = ModuleGenerator::new(&abi, config);
1103 let code = generator.generate().unwrap();
1104
1105 assert!(!code.contains("MODULE_ADDRESS"));
1107 }
1108
1109 #[test]
1110 fn test_generator_config_builder() {
1111 let config = GeneratorConfig::new()
1112 .with_module_name("custom_name")
1113 .with_entry_functions(false)
1114 .with_view_functions(false)
1115 .with_structs(false)
1116 .with_events(false)
1117 .with_async(false)
1118 .with_builder_pattern(true);
1119
1120 assert_eq!(config.module_name, Some("custom_name".to_string()));
1121 assert!(!config.generate_entry_functions);
1122 assert!(!config.generate_view_functions);
1123 assert!(!config.generate_structs);
1124 assert!(!config.generate_events);
1125 assert!(!config.async_functions);
1126 assert!(config.use_builder_pattern);
1127 }
1128
1129 #[test]
1130 fn test_empty_module() {
1131 let abi = MoveModuleABI {
1132 address: "0x1".to_string(),
1133 name: "empty".to_string(),
1134 exposed_functions: vec![],
1135 structs: vec![],
1136 };
1137 let generator = ModuleGenerator::new(&abi, GeneratorConfig::default());
1138 let code = generator.generate().unwrap();
1139
1140 assert!(code.contains("Generated Rust bindings"));
1142 assert!(code.contains("MODULE_ADDRESS"));
1143 assert!(code.contains("MODULE_NAME"));
1144 }
1145}