turdle_client/
program_client_generator.rs1use crate::idl::Idl;
2use quote::{format_ident, ToTokens};
3use syn::{parse_quote, parse_str};
4
5pub fn generate_source_code(idl: Idl, use_modules: &[syn::ItemUse]) -> String {
10 let mut output = "// DO NOT EDIT - automatically generated file (except `use` statements inside the `*_instruction` module\n".to_owned();
11 let code = idl
12 .programs
13 .into_iter()
14 .map(|idl_program| {
15 let program_name = idl_program.name.snake_case.replace('-', "_");
16 let instruction_module_name = format_ident!("{}_instruction", program_name);
17 let module_name: syn::Ident = parse_str(&program_name).unwrap();
18 let pubkey_bytes: syn::ExprArray = parse_str(&idl_program.id).unwrap();
19
20 let instructions = idl_program
21 .instruction_account_pairs
22 .into_iter()
23 .fold(
24 Vec::new(),
25 |mut instructions, (idl_instruction, idl_account_group)| {
26 let instruction_fn_name: syn::Ident =
27 parse_str(&idl_instruction.name.snake_case).unwrap();
28 let instruction_struct_name: syn::Ident =
29 parse_str(&idl_instruction.name.upper_camel_case).unwrap();
30 let account_struct_name: syn::Ident =
31 parse_str(&idl_account_group.name.upper_camel_case).unwrap();
32 let instruction_name: syn::Ident =
33 parse_str(&(idl_instruction.name.snake_case + "_ix")).unwrap();
34
35 let parameters = idl_instruction
36 .parameters
37 .iter()
38 .map(|(name, ty)| {
39 let name = format_ident!("i_{name}");
40 let ty: syn::Type = parse_str(ty).unwrap();
41 let parameter: syn::FnArg = parse_quote!(#name: #ty);
42 parameter
43 })
44 .collect::<Vec<_>>();
45
46 let accounts = idl_account_group
47 .accounts
48 .iter()
49 .map(|(name, ty)| {
50 let name = format_ident!("a_{name}");
51 let ty: syn::Type = parse_str(ty).unwrap();
52 let account: syn::FnArg = parse_quote!(#name: #ty);
53 account
54 })
55 .collect::<Vec<_>>();
56
57 let field_parameters = idl_instruction
58 .parameters
59 .iter()
60 .map(|(name, _)| {
61 let name: syn::Ident = parse_str(name).unwrap();
62 let value = format_ident!("i_{name}");
63 let parameter: syn::FieldValue = parse_quote!(#name: #value);
64 parameter
65 })
66 .collect::<Vec<_>>();
67
68 let field_accounts = idl_account_group
69 .accounts
70 .iter()
71 .map(|(name, _)| {
72 let name: syn::Ident = parse_str(name).unwrap();
73 let value = format_ident!("a_{name}");
74 let account: syn::FieldValue = parse_quote!(#name: #value);
75 account
76 })
77 .collect::<Vec<_>>();
78
79 let instruction: syn::ItemFn = parse_quote! {
80 pub async fn #instruction_fn_name(
81 client: &Client,
82 #(#parameters,)*
83 #(#accounts,)*
84 signers: impl IntoIterator<Item = Keypair> + Send + 'static,
85 ) -> Result<EncodedConfirmedTransactionWithStatusMeta, ClientError> {
86 Ok(client.send_instruction(
87 PROGRAM_ID,
88 #module_name::instruction::#instruction_struct_name {
89 #(#field_parameters,)*
90 },
91 #module_name::accounts::#account_struct_name {
92 #(#field_accounts,)*
93 },
94 signers,
95 ).await?)
96 }
97 };
98
99 let instruction_raw: syn::ItemFn = parse_quote! {
100 pub fn #instruction_name(
101 #(#parameters,)*
102 #(#accounts,)*
103 ) -> Instruction {
104 Instruction{
105 program_id: PROGRAM_ID,
106 data: #module_name::instruction::#instruction_struct_name {
107 #(#field_parameters,)*
108 }.data(),
109 accounts: #module_name::accounts::#account_struct_name {
110 #(#field_accounts,)*
111 }.to_account_metas(None),
112 }
113 }
114 };
115
116 instructions.push(instruction);
117 instructions.push(instruction_raw);
118 instructions
119 },
120 )
121 .into_iter();
122
123 let program_module: syn::ItemMod = parse_quote! {
124 pub mod #instruction_module_name {
125 #(#use_modules)*
126 pub static PROGRAM_ID: Pubkey = Pubkey::new_from_array(#pubkey_bytes);
127 #(#instructions)*
128 }
129 };
130 program_module.into_token_stream().to_string()
131 })
132 .collect::<String>();
133 output.push_str(&code);
134 output
135}