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/// Sanitizes an ABI-derived string for safe embedding in generated Rust code.
10///
11/// # Security
12///
13/// Prevents code injection via crafted ABI JSON by:
14/// - Replacing newlines (which could escape doc comments and inject code)
15/// - Escaping double quotes (which could break string literals)
16/// - Escaping backslashes (which could create escape sequences)
17fn sanitize_abi_string(s: &str) -> String {
18    s.replace('\\', "\\\\")
19        .replace('"', "\\\"")
20        .replace(['\n', '\r'], " ")
21}
22
23/// Configuration for code generation.
24#[derive(Debug, Clone)]
25#[allow(clippy::struct_excessive_bools)] // Config structs often have boolean options
26pub struct GeneratorConfig {
27    /// The module name for the generated code (defaults to module name from ABI).
28    pub module_name: Option<String>,
29    /// Whether to generate entry function wrappers.
30    pub generate_entry_functions: bool,
31    /// Whether to generate view function wrappers.
32    pub generate_view_functions: bool,
33    /// Whether to generate struct definitions.
34    pub generate_structs: bool,
35    /// Whether to generate event type helpers.
36    pub generate_events: bool,
37    /// Whether to generate async functions (vs sync).
38    pub async_functions: bool,
39    /// Custom type mapper.
40    pub type_mapper: MoveTypeMapper,
41    /// Whether to include the module address constant.
42    pub include_address_constant: bool,
43    /// Whether to generate builder pattern for entry functions.
44    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    /// Creates a new configuration.
65    #[must_use]
66    pub fn new() -> Self {
67        Self::default()
68    }
69
70    /// Sets the module name.
71    #[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    /// Enables or disables entry function generation.
78    #[must_use]
79    pub fn with_entry_functions(mut self, enabled: bool) -> Self {
80        self.generate_entry_functions = enabled;
81        self
82    }
83
84    /// Enables or disables view function generation.
85    #[must_use]
86    pub fn with_view_functions(mut self, enabled: bool) -> Self {
87        self.generate_view_functions = enabled;
88        self
89    }
90
91    /// Enables or disables struct generation.
92    #[must_use]
93    pub fn with_structs(mut self, enabled: bool) -> Self {
94        self.generate_structs = enabled;
95        self
96    }
97
98    /// Enables or disables event type generation.
99    #[must_use]
100    pub fn with_events(mut self, enabled: bool) -> Self {
101        self.generate_events = enabled;
102        self
103    }
104
105    /// Enables or disables async functions.
106    #[must_use]
107    pub fn with_async(mut self, enabled: bool) -> Self {
108        self.async_functions = enabled;
109        self
110    }
111
112    /// Enables builder pattern for entry functions.
113    #[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/// Generates Rust code from a Move module ABI.
121#[allow(missing_debug_implementations)] // Contains references that don't implement Debug
122pub struct ModuleGenerator<'a> {
123    abi: &'a MoveModuleABI,
124    config: GeneratorConfig,
125    source_info: Option<MoveModuleInfo>,
126}
127
128impl<'a> ModuleGenerator<'a> {
129    /// Creates a new generator for the given ABI.
130    #[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    /// Adds Move source information for better parameter names and documentation.
140    #[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    /// Gets enriched function info by combining ABI and source data.
147    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    /// Generates the complete Rust module code.
162    ///
163    /// # Errors
164    ///
165    /// Returns an error if code generation fails (e.g., formatting errors).
166    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    /// Writes all code to the output string.
176    fn write_all(&self, output: &mut String) -> std::fmt::Result {
177        // File header
178        self.write_header(output)?;
179
180        // Imports
181        self.write_imports(output)?;
182
183        // Module address constant
184        if self.config.include_address_constant {
185            self.write_address_constant(output)?;
186        }
187
188        // Struct definitions
189        if self.config.generate_structs {
190            self.write_structs(output)?;
191        }
192
193        // Event types
194        if self.config.generate_events {
195            self.write_events(output)?;
196        }
197
198        // Entry functions
199        if self.config.generate_entry_functions {
200            self.write_entry_functions(output)?;
201        }
202
203        // View functions
204        if self.config.generate_view_functions {
205            self.write_view_functions(output)?;
206        }
207
208        Ok(())
209    }
210
211    /// Writes event type helpers.
212    fn write_events(&self, output: &mut String) -> std::fmt::Result {
213        // Find structs that are likely events (heuristic: name ends with "Event")
214        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        // Generate event type enum
231        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        // Generate event type string constants
244        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        // Generate parse_event function
261        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        // Generate is_module_event helper
294        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    /// Writes the file header comment.
313    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    /// Writes import statements.
333    #[allow(clippy::unused_self)] // Some methods take &self for future extensibility
334    fn write_imports(&self, output: &mut String) -> std::fmt::Result {
335        writeln!(output, "use aptos_sdk::{{")?;
336        writeln!(output, "    account::Account,")?;
337        writeln!(output, "    error::{{AptosError, AptosResult}},")?;
338        writeln!(
339            output,
340            "    transaction::{{EntryFunction, TransactionPayload}},"
341        )?;
342        writeln!(output, "    types::{{AccountAddress, TypeTag}},")?;
343        writeln!(output, "    Aptos,")?;
344        writeln!(output, "}};")?;
345        writeln!(output, "use serde::{{Deserialize, Serialize}};")?;
346        writeln!(output)
347    }
348
349    /// Writes the module address constant.
350    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    /// Writes struct definitions.
368    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    /// Writes a single struct definition.
386    fn write_struct(&self, output: &mut String, struct_def: &MoveStructDef) -> std::fmt::Result {
387        // Skip native structs
388        if struct_def.is_native {
389            return Ok(());
390        }
391
392        let rust_name = to_pascal_case(&struct_def.name);
393
394        // Documentation
395        // SECURITY: Sanitize ABI-derived strings to prevent code injection via newlines
396        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        // Derive macros
414        writeln!(
415            output,
416            "#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]"
417        )?;
418
419        // Generic type parameters
420        if struct_def.generic_type_params.is_empty() {
421            writeln!(output, "pub struct {rust_name} {{")?;
422        } else {
423            let type_params: Vec<String> = struct_def
424                .generic_type_params
425                .iter()
426                .enumerate()
427                .map(|(i, _)| format!("T{i}"))
428                .collect();
429
430            writeln!(
431                output,
432                "pub struct {}<{}> {{",
433                rust_name,
434                type_params.join(", ")
435            )?;
436        }
437
438        // Fields
439        for field in &struct_def.fields {
440            self.write_struct_field(output, field)?;
441        }
442
443        writeln!(output, "}}")?;
444        writeln!(output)
445    }
446
447    /// Writes a struct field.
448    fn write_struct_field(&self, output: &mut String, field: &MoveStructField) -> std::fmt::Result {
449        let rust_type = self.config.type_mapper.map_type(&field.typ);
450        let rust_name = to_snake_case(&field.name);
451
452        // Handle reserved Rust keywords
453        let rust_name = match rust_name.as_str() {
454            "type" => "r#type".to_string(),
455            "self" => "r#self".to_string(),
456            "move" => "r#move".to_string(),
457            _ => rust_name,
458        };
459
460        if let Some(doc) = &rust_type.doc {
461            // SECURITY: Sanitize doc strings derived from ABI to prevent code injection
462            writeln!(output, "    /// {}", sanitize_abi_string(doc))?;
463        }
464
465        // Add serde rename if field name differs
466        if rust_name != field.name && !rust_name.starts_with("r#") {
467            // SECURITY: Escape field names to prevent breaking the serde attribute syntax
468            writeln!(
469                output,
470                "    #[serde(rename = \"{}\")]",
471                sanitize_abi_string(&field.name)
472            )?;
473        }
474
475        writeln!(output, "    pub {}: {},", rust_name, rust_type.path)
476    }
477
478    /// Writes entry function wrappers.
479    fn write_entry_functions(&self, output: &mut String) -> std::fmt::Result {
480        let entry_functions: Vec<_> = self
481            .abi
482            .exposed_functions
483            .iter()
484            .filter(|f| f.is_entry)
485            .collect();
486
487        if entry_functions.is_empty() {
488            return Ok(());
489        }
490
491        writeln!(output, "// =============================================")?;
492        writeln!(output, "// Entry Functions")?;
493        writeln!(output, "// =============================================")?;
494        writeln!(output)?;
495
496        for func in entry_functions {
497            self.write_entry_function(output, func)?;
498        }
499
500        Ok(())
501    }
502
503    /// Writes a single entry function wrapper.
504    fn write_entry_function(&self, output: &mut String, func: &MoveFunction) -> std::fmt::Result {
505        let rust_name = to_snake_case(&func.name);
506        let enriched = self.get_enriched_function(func);
507
508        // Get non-signer parameters with their enriched names
509        let params: Vec<_> = enriched
510            .non_signer_params()
511            .into_iter()
512            .map(|p| {
513                let rust_type = self.config.type_mapper.map_type(&p.move_type);
514                (p.name.clone(), p.move_type.clone(), rust_type)
515            })
516            .collect();
517
518        // Documentation from source or generated
519        // SECURITY: Sanitize ABI-derived strings to prevent code injection via newlines
520        if let Some(doc) = &enriched.doc {
521            for line in doc.lines() {
522                writeln!(output, "/// {}", sanitize_abi_string(line))?;
523            }
524            writeln!(output, "///")?;
525        } else {
526            writeln!(
527                output,
528                "/// Entry function: `{}::{}`",
529                sanitize_abi_string(&self.abi.name),
530                sanitize_abi_string(&func.name)
531            )?;
532            writeln!(output, "///")?;
533        }
534
535        // Arguments documentation
536        if !params.is_empty() {
537            writeln!(output, "/// # Arguments")?;
538            writeln!(output, "///")?;
539            for (name, move_type, rust_type) in &params {
540                writeln!(
541                    output,
542                    "/// * `{}` - {} (Move type: `{}`)",
543                    sanitize_abi_string(name),
544                    sanitize_abi_string(&rust_type.path),
545                    sanitize_abi_string(move_type)
546                )?;
547            }
548        }
549        if !enriched.type_param_names.is_empty() {
550            let type_params = enriched
551                .type_param_names
552                .iter()
553                .map(|s| sanitize_abi_string(s))
554                .collect::<Vec<_>>()
555                .join(", ");
556            writeln!(output, "/// * `type_args` - Type arguments: {type_params}")?;
557        }
558
559        // Function signature
560        write!(output, "pub fn {rust_name}(")?;
561
562        // Parameters with meaningful names
563        let mut param_strs = Vec::new();
564        for (name, _, rust_type) in &params {
565            // Convert to snake_case and handle reserved words
566            let safe_name = Self::safe_param_name(name);
567            param_strs.push(format!("{}: {}", safe_name, rust_type.as_arg_type()));
568        }
569        if !enriched.type_param_names.is_empty() {
570            param_strs.push("type_args: Vec<TypeTag>".to_string());
571        }
572        write!(output, "{}", param_strs.join(", "))?;
573
574        writeln!(output, ") -> AptosResult<TransactionPayload> {{")?;
575
576        // Function body
577        // SECURITY: Sanitize ABI-derived values embedded in string literals
578        writeln!(output, "    let function_id = format!(")?;
579        writeln!(
580            output,
581            "        \"{}::{}::{}\",",
582            sanitize_abi_string(&self.abi.address),
583            sanitize_abi_string(&self.abi.name),
584            sanitize_abi_string(&func.name)
585        )?;
586        writeln!(output, "    );")?;
587        writeln!(output)?;
588
589        // Build arguments using the parameter names
590        writeln!(output, "    let args = vec![")?;
591        for (name, move_type, _) in &params {
592            let safe_name = Self::safe_param_name(name);
593            let bcs_expr = self.config.type_mapper.to_bcs_arg(move_type, &safe_name);
594            writeln!(output, "        {bcs_expr},")?;
595        }
596        writeln!(output, "    ];")?;
597        writeln!(output)?;
598
599        // Type arguments
600        if func.generic_type_params.is_empty() {
601            writeln!(output, "    let type_args = vec![];")?;
602        }
603        writeln!(output)?;
604
605        // Create entry function
606        writeln!(
607            output,
608            "    let entry_fn = EntryFunction::from_function_id(&function_id, type_args, args)?;"
609        )?;
610        writeln!(
611            output,
612            "    Ok(TransactionPayload::EntryFunction(entry_fn))"
613        )?;
614        writeln!(output, "}}")?;
615        writeln!(output)
616    }
617
618    /// Writes view function wrappers.
619    fn write_view_functions(&self, output: &mut String) -> std::fmt::Result {
620        let view_functions: Vec<_> = self
621            .abi
622            .exposed_functions
623            .iter()
624            .filter(|f| f.is_view)
625            .collect();
626
627        if view_functions.is_empty() {
628            return Ok(());
629        }
630
631        writeln!(output, "// =============================================")?;
632        writeln!(output, "// View Functions")?;
633        writeln!(output, "// =============================================")?;
634        writeln!(output)?;
635
636        for func in view_functions {
637            self.write_view_function(output, func)?;
638        }
639
640        Ok(())
641    }
642
643    /// Converts a parameter name to a safe Rust identifier.
644    fn safe_param_name(name: &str) -> String {
645        let snake = to_snake_case(name);
646        match snake.as_str() {
647            "type" => "r#type".to_string(),
648            "self" => "r#self".to_string(),
649            "move" => "r#move".to_string(),
650            "ref" => "r#ref".to_string(),
651            "mut" => "r#mut".to_string(),
652            "fn" => "r#fn".to_string(),
653            "mod" => "r#mod".to_string(),
654            "use" => "r#use".to_string(),
655            "pub" => "r#pub".to_string(),
656            "let" => "r#let".to_string(),
657            "if" => "r#if".to_string(),
658            "else" => "r#else".to_string(),
659            "match" => "r#match".to_string(),
660            "loop" => "r#loop".to_string(),
661            "while" => "r#while".to_string(),
662            "for" => "r#for".to_string(),
663            "in" => "r#in".to_string(),
664            "return" => "r#return".to_string(),
665            "break" => "r#break".to_string(),
666            "continue" => "r#continue".to_string(),
667            "async" => "r#async".to_string(),
668            "await" => "r#await".to_string(),
669            "struct" => "r#struct".to_string(),
670            "enum" => "r#enum".to_string(),
671            "trait" => "r#trait".to_string(),
672            "impl" => "r#impl".to_string(),
673            "dyn" => "r#dyn".to_string(),
674            "const" => "r#const".to_string(),
675            "static" => "r#static".to_string(),
676            "unsafe" => "r#unsafe".to_string(),
677            "extern" => "r#extern".to_string(),
678            "crate" => "r#crate".to_string(),
679            "super" => "r#super".to_string(),
680            "where" => "r#where".to_string(),
681            "as" => "r#as".to_string(),
682            "true" => "r#true".to_string(),
683            "false" => "r#false".to_string(),
684            _ => snake,
685        }
686    }
687
688    /// Writes a single view function wrapper.
689    fn write_view_function(&self, output: &mut String, func: &MoveFunction) -> std::fmt::Result {
690        let rust_name = format!("view_{}", to_snake_case(&func.name));
691        let enriched = self.get_enriched_function(func);
692
693        // Get all parameters with their enriched names (view functions can have any params)
694        let params: Vec<_> = enriched
695            .params
696            .iter()
697            .map(|p| {
698                let rust_type = self.config.type_mapper.map_type(&p.move_type);
699                (p.name.clone(), p.move_type.clone(), rust_type)
700            })
701            .collect();
702
703        // Return type
704        let return_type = if func.returns.is_empty() {
705            "()".to_string()
706        } else if func.returns.len() == 1 {
707            self.config.type_mapper.map_type(&func.returns[0]).path
708        } else {
709            let types: Vec<String> = func
710                .returns
711                .iter()
712                .map(|r| self.config.type_mapper.map_type(r).path)
713                .collect();
714            format!("({})", types.join(", "))
715        };
716
717        // Documentation from source or generated
718        // SECURITY: Sanitize ABI-derived strings to prevent code injection via newlines
719        if let Some(doc) = &enriched.doc {
720            for line in doc.lines() {
721                writeln!(output, "/// {}", sanitize_abi_string(line))?;
722            }
723            writeln!(output, "///")?;
724        } else {
725            writeln!(
726                output,
727                "/// View function: `{}::{}`",
728                sanitize_abi_string(&self.abi.name),
729                sanitize_abi_string(&func.name)
730            )?;
731            writeln!(output, "///")?;
732        }
733
734        // Arguments documentation
735        if !params.is_empty() {
736            writeln!(output, "/// # Arguments")?;
737            writeln!(output, "///")?;
738            for (name, move_type, rust_type) in &params {
739                writeln!(
740                    output,
741                    "/// * `{}` - {} (Move type: `{}`)",
742                    sanitize_abi_string(name),
743                    sanitize_abi_string(&rust_type.path),
744                    sanitize_abi_string(move_type)
745                )?;
746            }
747        }
748        if !enriched.type_param_names.is_empty() {
749            let type_params = enriched
750                .type_param_names
751                .iter()
752                .map(|s| sanitize_abi_string(s))
753                .collect::<Vec<_>>()
754                .join(", ");
755            writeln!(output, "/// * `type_args` - Type arguments: {type_params}")?;
756        }
757        if !func.returns.is_empty() {
758            writeln!(output, "///")?;
759            writeln!(output, "/// # Returns")?;
760            writeln!(output, "///")?;
761            writeln!(output, "/// `{}`", sanitize_abi_string(&return_type))?;
762        }
763
764        // Function signature
765        let async_kw = if self.config.async_functions {
766            "async "
767        } else {
768            ""
769        };
770
771        write!(output, "pub {async_kw}fn {rust_name}(aptos: &Aptos")?;
772
773        // Parameters with meaningful names
774        for (name, _, rust_type) in &params {
775            let safe_name = Self::safe_param_name(name);
776            write!(output, ", {}: {}", safe_name, rust_type.as_arg_type())?;
777        }
778        if !enriched.type_param_names.is_empty() {
779            write!(output, ", type_args: Vec<String>")?;
780        }
781
782        writeln!(output, ") -> AptosResult<Vec<serde_json::Value>> {{")?;
783
784        // Function body
785        // SECURITY: Sanitize ABI-derived values embedded in string literals
786        writeln!(output, "    let function_id = format!(")?;
787        writeln!(
788            output,
789            "        \"{}::{}::{}\",",
790            sanitize_abi_string(&self.abi.address),
791            sanitize_abi_string(&self.abi.name),
792            sanitize_abi_string(&func.name)
793        )?;
794        writeln!(output, "    );")?;
795        writeln!(output)?;
796
797        // Type arguments
798        if enriched.type_param_names.is_empty() {
799            writeln!(output, "    let type_args: Vec<String> = vec![];")?;
800        }
801
802        // Build view arguments as JSON using parameter names
803        writeln!(output, "    let args = vec![")?;
804        for (name, move_type, _) in &params {
805            let safe_name = Self::safe_param_name(name);
806            let arg_expr = self.view_arg_json_expr(move_type, &safe_name);
807            writeln!(output, "        {arg_expr},")?;
808        }
809        writeln!(output, "    ];")?;
810        writeln!(output)?;
811
812        // Call view function
813        let await_kw = if self.config.async_functions {
814            ".await"
815        } else {
816            ""
817        };
818        writeln!(
819            output,
820            "    aptos.view(&function_id, type_args, args){await_kw}"
821        )?;
822        writeln!(output, "}}")?;
823        writeln!(output)
824    }
825
826    /// Creates a JSON expression for a view function argument.
827    #[allow(clippy::unused_self)] // Some methods take &self for future extensibility
828    fn view_arg_json_expr(&self, move_type: &str, var_name: &str) -> String {
829        match move_type {
830            "address" => format!("serde_json::json!({var_name}.to_string())"),
831            "bool" | "u8" | "u16" | "u32" | "u64" | "u128" => {
832                format!("serde_json::json!({var_name}.to_string())")
833            }
834            _ if move_type.starts_with("vector<u8>") => {
835                format!("serde_json::json!(::aptos_sdk::const_hex::encode({var_name}))")
836            }
837            "0x1::string::String" => format!("serde_json::json!({var_name})"),
838            _ if move_type.ends_with("::string::String") => {
839                format!("serde_json::json!({var_name})")
840            }
841            _ => format!("serde_json::json!({var_name})"),
842        }
843    }
844}
845
846#[cfg(test)]
847mod tests {
848    use super::*;
849    use crate::api::response::{MoveFunctionGenericTypeParam, MoveStructGenericTypeParam};
850
851    fn sample_abi() -> MoveModuleABI {
852        MoveModuleABI {
853            address: "0x1".to_string(),
854            name: "coin".to_string(),
855            exposed_functions: vec![
856                MoveFunction {
857                    name: "transfer".to_string(),
858                    visibility: "public".to_string(),
859                    is_entry: true,
860                    is_view: false,
861                    generic_type_params: vec![MoveFunctionGenericTypeParam {
862                        constraints: vec![],
863                    }],
864                    params: vec![
865                        "&signer".to_string(),
866                        "address".to_string(),
867                        "u64".to_string(),
868                    ],
869                    returns: vec![],
870                },
871                MoveFunction {
872                    name: "balance".to_string(),
873                    visibility: "public".to_string(),
874                    is_entry: false,
875                    is_view: true,
876                    generic_type_params: vec![MoveFunctionGenericTypeParam {
877                        constraints: vec![],
878                    }],
879                    params: vec!["address".to_string()],
880                    returns: vec!["u64".to_string()],
881                },
882            ],
883            structs: vec![MoveStructDef {
884                name: "Coin".to_string(),
885                is_native: false,
886                abilities: vec!["store".to_string()],
887                generic_type_params: vec![MoveStructGenericTypeParam {
888                    constraints: vec![],
889                }],
890                fields: vec![MoveStructField {
891                    name: "value".to_string(),
892                    typ: "u64".to_string(),
893                }],
894            }],
895        }
896    }
897
898    fn sample_abi_with_events() -> MoveModuleABI {
899        MoveModuleABI {
900            address: "0x1".to_string(),
901            name: "token".to_string(),
902            exposed_functions: vec![],
903            structs: vec![
904                MoveStructDef {
905                    name: "MintEvent".to_string(),
906                    is_native: false,
907                    abilities: vec!["drop".to_string(), "store".to_string()],
908                    generic_type_params: vec![],
909                    fields: vec![
910                        MoveStructField {
911                            name: "id".to_string(),
912                            typ: "u64".to_string(),
913                        },
914                        MoveStructField {
915                            name: "creator".to_string(),
916                            typ: "address".to_string(),
917                        },
918                    ],
919                },
920                MoveStructDef {
921                    name: "BurnEvent".to_string(),
922                    is_native: false,
923                    abilities: vec!["drop".to_string(), "store".to_string()],
924                    generic_type_params: vec![],
925                    fields: vec![MoveStructField {
926                        name: "id".to_string(),
927                        typ: "u64".to_string(),
928                    }],
929                },
930                MoveStructDef {
931                    name: "Token".to_string(),
932                    is_native: false,
933                    abilities: vec!["key".to_string()],
934                    generic_type_params: vec![],
935                    fields: vec![MoveStructField {
936                        name: "value".to_string(),
937                        typ: "u64".to_string(),
938                    }],
939                },
940            ],
941        }
942    }
943
944    #[test]
945    fn test_generate_module() {
946        let abi = sample_abi();
947        let generator = ModuleGenerator::new(&abi, GeneratorConfig::default());
948        let code = generator.generate().unwrap();
949
950        // Verify header
951        assert!(code.contains("Generated Rust bindings"));
952        assert!(code.contains("0x1::coin"));
953
954        // Verify constants
955        assert!(code.contains("MODULE_ADDRESS"));
956        assert!(code.contains("MODULE_NAME"));
957
958        // Verify struct
959        assert!(code.contains("pub struct Coin"));
960        assert!(code.contains("pub value: u64"));
961
962        // Verify entry function
963        assert!(code.contains("pub fn transfer"));
964
965        // Verify view function
966        assert!(code.contains("pub async fn view_balance"));
967    }
968
969    #[test]
970    fn test_entry_function_excludes_signer() {
971        let abi = sample_abi();
972        let generator = ModuleGenerator::new(&abi, GeneratorConfig::default());
973        let code = generator.generate().unwrap();
974
975        // transfer should have 2 args (address, u64), not 3 (signer is excluded)
976        // Without source info, names are generated from types: addr, amount
977        assert!(code.contains("addr: AccountAddress"));
978        assert!(code.contains("amount: u64"));
979        // Should not have account (signer) as an argument
980        assert!(!code.contains("account: AccountAddress"));
981        // Function should exist
982        assert!(code.contains("pub fn transfer("));
983    }
984
985    #[test]
986    fn test_entry_function_with_source_info() {
987        use crate::codegen::move_parser::MoveSourceParser;
988
989        let abi = sample_abi();
990        let source = r"
991            module 0x1::coin {
992                /// Transfers coins from sender to recipient.
993                public entry fun transfer<CoinType>(
994                    from: &signer,
995                    to: address,
996                    value: u64,
997                ) { }
998            }
999        ";
1000        let source_info = MoveSourceParser::parse(source);
1001
1002        let generator =
1003            ModuleGenerator::new(&abi, GeneratorConfig::default()).with_source_info(source_info);
1004        let code = generator.generate().unwrap();
1005
1006        // With source info, should have the actual parameter names
1007        assert!(code.contains("to: AccountAddress"));
1008        assert!(code.contains("value: u64"));
1009        // Should have the documentation
1010        assert!(code.contains("Transfers coins from sender to recipient"));
1011    }
1012
1013    #[test]
1014    fn test_generate_events() {
1015        let abi = sample_abi_with_events();
1016        let generator = ModuleGenerator::new(&abi, GeneratorConfig::default());
1017        let code = generator.generate().unwrap();
1018
1019        // Should generate event enum
1020        assert!(code.contains("pub enum ModuleEvent"));
1021        assert!(code.contains("MintEvent(MintEvent)"));
1022        assert!(code.contains("BurnEvent(BurnEvent)"));
1023        assert!(code.contains("Unknown(serde_json::Value)"));
1024
1025        // Should generate event type constants
1026        assert!(code.contains("pub mod event_types"));
1027        assert!(code.contains("MINT_EVENT"));
1028        assert!(code.contains("BURN_EVENT"));
1029
1030        // Should generate parse_event function
1031        assert!(code.contains("pub fn parse_event"));
1032        assert!(code.contains("is_module_event"));
1033
1034        // Non-event struct (Token) should not be in event enum
1035        assert!(!code.contains("Token(Token)"));
1036    }
1037
1038    #[test]
1039    fn test_config_disable_events() {
1040        let abi = sample_abi_with_events();
1041        let config = GeneratorConfig::default().with_events(false);
1042        let generator = ModuleGenerator::new(&abi, config);
1043        let code = generator.generate().unwrap();
1044
1045        // Should not generate event helpers
1046        assert!(!code.contains("pub enum ModuleEvent"));
1047        assert!(!code.contains("pub fn parse_event"));
1048    }
1049
1050    #[test]
1051    fn test_config_disable_structs() {
1052        let abi = sample_abi();
1053        let config = GeneratorConfig::default().with_structs(false);
1054        let generator = ModuleGenerator::new(&abi, config);
1055        let code = generator.generate().unwrap();
1056
1057        // Should not generate struct definitions
1058        assert!(!code.contains("pub struct Coin"));
1059    }
1060
1061    #[test]
1062    fn test_config_sync_functions() {
1063        let abi = sample_abi();
1064        let config = GeneratorConfig::default().with_async(false);
1065        let generator = ModuleGenerator::new(&abi, config);
1066        let code = generator.generate().unwrap();
1067
1068        // Should generate sync view function, not async
1069        assert!(code.contains("pub fn view_balance"));
1070        assert!(!code.contains("pub async fn view_balance"));
1071    }
1072
1073    #[test]
1074    fn test_config_no_address_constant() {
1075        let abi = sample_abi();
1076        let config = GeneratorConfig {
1077            include_address_constant: false,
1078            ..Default::default()
1079        };
1080        let generator = ModuleGenerator::new(&abi, config);
1081        let code = generator.generate().unwrap();
1082
1083        // Should not include module address constant
1084        assert!(!code.contains("MODULE_ADDRESS"));
1085    }
1086
1087    #[test]
1088    fn test_generator_config_builder() {
1089        let config = GeneratorConfig::new()
1090            .with_module_name("custom_name")
1091            .with_entry_functions(false)
1092            .with_view_functions(false)
1093            .with_structs(false)
1094            .with_events(false)
1095            .with_async(false)
1096            .with_builder_pattern(true);
1097
1098        assert_eq!(config.module_name, Some("custom_name".to_string()));
1099        assert!(!config.generate_entry_functions);
1100        assert!(!config.generate_view_functions);
1101        assert!(!config.generate_structs);
1102        assert!(!config.generate_events);
1103        assert!(!config.async_functions);
1104        assert!(config.use_builder_pattern);
1105    }
1106
1107    #[test]
1108    fn test_empty_module() {
1109        let abi = MoveModuleABI {
1110            address: "0x1".to_string(),
1111            name: "empty".to_string(),
1112            exposed_functions: vec![],
1113            structs: vec![],
1114        };
1115        let generator = ModuleGenerator::new(&abi, GeneratorConfig::default());
1116        let code = generator.generate().unwrap();
1117
1118        // Should still generate valid code
1119        assert!(code.contains("Generated Rust bindings"));
1120        assert!(code.contains("MODULE_ADDRESS"));
1121        assert!(code.contains("MODULE_NAME"));
1122    }
1123}