Skip to main content

aptos_sdk/codegen/
generator.rs

1//! Code generator for Move modules.
2
3use 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/// Configuration for code generation.
10#[derive(Debug, Clone)]
11#[allow(clippy::struct_excessive_bools)] // Config structs often have boolean options
12pub struct GeneratorConfig {
13    /// The module name for the generated code (defaults to module name from ABI).
14    pub module_name: Option<String>,
15    /// Whether to generate entry function wrappers.
16    pub generate_entry_functions: bool,
17    /// Whether to generate view function wrappers.
18    pub generate_view_functions: bool,
19    /// Whether to generate struct definitions.
20    pub generate_structs: bool,
21    /// Whether to generate event type helpers.
22    pub generate_events: bool,
23    /// Whether to generate async functions (vs sync).
24    pub async_functions: bool,
25    /// Custom type mapper.
26    pub type_mapper: MoveTypeMapper,
27    /// Whether to include the module address constant.
28    pub include_address_constant: bool,
29    /// Whether to generate builder pattern for entry functions.
30    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    /// Creates a new configuration.
51    #[must_use]
52    pub fn new() -> Self {
53        Self::default()
54    }
55
56    /// Sets the module name.
57    #[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    /// Enables or disables entry function generation.
64    #[must_use]
65    pub fn with_entry_functions(mut self, enabled: bool) -> Self {
66        self.generate_entry_functions = enabled;
67        self
68    }
69
70    /// Enables or disables view function generation.
71    #[must_use]
72    pub fn with_view_functions(mut self, enabled: bool) -> Self {
73        self.generate_view_functions = enabled;
74        self
75    }
76
77    /// Enables or disables struct generation.
78    #[must_use]
79    pub fn with_structs(mut self, enabled: bool) -> Self {
80        self.generate_structs = enabled;
81        self
82    }
83
84    /// Enables or disables event type generation.
85    #[must_use]
86    pub fn with_events(mut self, enabled: bool) -> Self {
87        self.generate_events = enabled;
88        self
89    }
90
91    /// Enables or disables async functions.
92    #[must_use]
93    pub fn with_async(mut self, enabled: bool) -> Self {
94        self.async_functions = enabled;
95        self
96    }
97
98    /// Enables builder pattern for entry functions.
99    #[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/// Generates Rust code from a Move module ABI.
107#[allow(missing_debug_implementations)] // Contains references that don't implement Debug
108pub struct ModuleGenerator<'a> {
109    abi: &'a MoveModuleABI,
110    config: GeneratorConfig,
111    source_info: Option<MoveModuleInfo>,
112}
113
114impl<'a> ModuleGenerator<'a> {
115    /// Creates a new generator for the given ABI.
116    #[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    /// Adds Move source information for better parameter names and documentation.
126    #[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    /// Gets enriched function info by combining ABI and source data.
133    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    /// Generates the complete Rust module code.
148    ///
149    /// # Errors
150    ///
151    /// Returns an error if code generation fails (e.g., formatting errors).
152    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    /// Writes all code to the output string.
162    fn write_all(&self, output: &mut String) -> std::fmt::Result {
163        // File header
164        self.write_header(output)?;
165
166        // Imports
167        self.write_imports(output)?;
168
169        // Module address constant
170        if self.config.include_address_constant {
171            self.write_address_constant(output)?;
172        }
173
174        // Struct definitions
175        if self.config.generate_structs {
176            self.write_structs(output)?;
177        }
178
179        // Event types
180        if self.config.generate_events {
181            self.write_events(output)?;
182        }
183
184        // Entry functions
185        if self.config.generate_entry_functions {
186            self.write_entry_functions(output)?;
187        }
188
189        // View functions
190        if self.config.generate_view_functions {
191            self.write_view_functions(output)?;
192        }
193
194        Ok(())
195    }
196
197    /// Writes event type helpers.
198    fn write_events(&self, output: &mut String) -> std::fmt::Result {
199        // Find structs that are likely events (heuristic: name ends with "Event")
200        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        // Generate event type enum
217        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        // Generate event type string constants
230        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        // Generate parse_event function
244        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        // Generate is_module_event helper
277        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    /// Writes the file header comment.
295    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    /// Writes import statements.
314    #[allow(clippy::unused_self)] // Some methods take &self for future extensibility
315    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    /// Writes the module address constant.
331    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    /// Writes struct definitions.
349    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    /// Writes a single struct definition.
367    fn write_struct(&self, output: &mut String, struct_def: &MoveStructDef) -> std::fmt::Result {
368        // Skip native structs
369        if struct_def.is_native {
370            return Ok(());
371        }
372
373        let rust_name = to_pascal_case(&struct_def.name);
374
375        // Documentation
376        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        // Derive macros
387        writeln!(
388            output,
389            "#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]"
390        )?;
391
392        // Generic type parameters
393        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        // Fields
412        for field in &struct_def.fields {
413            self.write_struct_field(output, field)?;
414        }
415
416        writeln!(output, "}}")?;
417        writeln!(output)
418    }
419
420    /// Writes a struct field.
421    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        // Handle reserved Rust keywords
426        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        // Add serde rename if field name differs
438        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    /// Writes entry function wrappers.
446    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    /// Writes a single entry function wrapper.
471    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        // Get non-signer parameters with their enriched names
476        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        // Documentation from source or generated
486        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        // Arguments documentation
501        if !params.is_empty() {
502            writeln!(output, "/// # Arguments")?;
503            writeln!(output, "///")?;
504            for (name, move_type, rust_type) in &params {
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        // Function signature
521        write!(output, "pub fn {rust_name}(")?;
522
523        // Parameters with meaningful names
524        let mut param_strs = Vec::new();
525        for (name, _, rust_type) in &params {
526            // Convert to snake_case and handle reserved words
527            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        // Function body
538        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        // Build arguments using the parameter names
548        writeln!(output, "    let args = vec![")?;
549        for (name, move_type, _) in &params {
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        // Type arguments
558        if func.generic_type_params.is_empty() {
559            writeln!(output, "    let type_args = vec![];")?;
560        }
561        writeln!(output)?;
562
563        // Create entry function
564        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    /// Writes view function wrappers.
577    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    /// Converts a parameter name to a safe Rust identifier.
602    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    /// Writes a single view function wrapper.
647    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        // Get all parameters with their enriched names (view functions can have any params)
652        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        // Return type
662        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        // Documentation from source or generated
676        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        // Arguments documentation
691        if !params.is_empty() {
692            writeln!(output, "/// # Arguments")?;
693            writeln!(output, "///")?;
694            for (name, move_type, rust_type) in &params {
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        // Function signature
717        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        // Parameters with meaningful names
726        for (name, _, rust_type) in &params {
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        // Function body
737        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        // Type arguments
747        if enriched.type_param_names.is_empty() {
748            writeln!(output, "    let type_args: Vec<String> = vec![];")?;
749        }
750
751        // Build view arguments as JSON using parameter names
752        writeln!(output, "    let args = vec![")?;
753        for (name, move_type, _) in &params {
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        // Call view function
762        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    /// Creates a JSON expression for a view function argument.
776    #[allow(clippy::unused_self)] // Some methods take &self for future extensibility
777    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        // Verify header
900        assert!(code.contains("Generated Rust bindings"));
901        assert!(code.contains("0x1::coin"));
902
903        // Verify constants
904        assert!(code.contains("MODULE_ADDRESS"));
905        assert!(code.contains("MODULE_NAME"));
906
907        // Verify struct
908        assert!(code.contains("pub struct Coin"));
909        assert!(code.contains("pub value: u64"));
910
911        // Verify entry function
912        assert!(code.contains("pub fn transfer"));
913
914        // Verify view function
915        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        // transfer should have 2 args (address, u64), not 3 (signer is excluded)
925        // Without source info, names are generated from types: addr, amount
926        assert!(code.contains("addr: AccountAddress"));
927        assert!(code.contains("amount: u64"));
928        // Should not have account (signer) as an argument
929        assert!(!code.contains("account: AccountAddress"));
930        // Function should exist
931        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        // With source info, should have the actual parameter names
956        assert!(code.contains("to: AccountAddress"));
957        assert!(code.contains("value: u64"));
958        // Should have the documentation
959        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        // Should generate event enum
969        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        // Should generate event type constants
975        assert!(code.contains("pub mod event_types"));
976        assert!(code.contains("MINT_EVENT"));
977        assert!(code.contains("BURN_EVENT"));
978
979        // Should generate parse_event function
980        assert!(code.contains("pub fn parse_event"));
981        assert!(code.contains("is_module_event"));
982
983        // Non-event struct (Token) should not be in event enum
984        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        // Should not generate event helpers
995        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        // Should not generate struct definitions
1007        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        // Should generate sync view function, not async
1018        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        // Should not include module address constant
1033        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        // Should still generate valid code
1068        assert!(code.contains("Generated Rust bindings"));
1069        assert!(code.contains("MODULE_ADDRESS"));
1070        assert!(code.contains("MODULE_NAME"));
1071    }
1072}