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