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