use super::cpi::instruction_cpi_rs;
use super::types::{idl_type_to_rust, safe_ident};
use anyhow::Result;
use heck::{ToPascalCase, ToSnakeCase};
use shank_idl::idl_instruction::{IdlAccountItem, IdlInstruction};
pub fn instruction_rs(ix: &IdlInstruction, generate_cpi: bool) -> Result<String> {
let name = ix.name.to_pascal_case();
let snake_name = safe_ident(&ix.name.to_snake_case());
let mut accounts = Vec::new();
for item in &ix.accounts {
match item {
IdlAccountItem::IdlAccount(acc) => accounts.push(acc),
IdlAccountItem::IdlAccounts(_) => anyhow::bail!(
"pinoc client generate does not support nested account groups yet ({name})"
),
}
}
let mut args_fields = String::new();
let mut args_params = String::new();
for field in &ix.args {
let field_name = safe_ident(&field.name.to_snake_case());
let field_ty = idl_type_to_rust(&field.ty)?;
args_fields.push_str(&format!(" pub {field_name}: {field_ty},\n"));
args_params.push_str(&format!("{field_name}: {field_ty}, "));
}
let args_names = ix
.args
.iter()
.map(|f| safe_ident(&f.name.to_snake_case()))
.collect::<Vec<_>>()
.join(", ");
let mut account_metas = String::new();
let mut fn_params = String::new();
for acc in &accounts {
let acc_name = safe_ident(&acc.name.to_snake_case());
fn_params.push_str(&format!("{acc_name}: solana_pubkey::Pubkey, "));
let meta = if acc.is_mut {
format!(
"solana_instruction::AccountMeta::new({acc_name}, {})",
acc.is_signer
)
} else {
format!(
"solana_instruction::AccountMeta::new_readonly({acc_name}, {})",
acc.is_signer
)
};
account_metas.push_str(&format!(" accounts.push({meta});\n"));
}
let discriminant = ix.discriminant.value;
let account_count = accounts.len();
let type_imports = super::types::type_imports(ix.args.iter().map(|f| &f.ty));
let cpi_block = if generate_cpi {
instruction_cpi_rs(ix, &accounts)?
} else {
String::new()
};
let accounts_mut = if accounts.is_empty() { "" } else { "mut " };
Ok(format!(
r#"//! Autogenerated by `pinoc client generate`. Do not edit by hand.
use borsh::BorshSerialize;
{type_imports}
pub const {upper_name}_DISCRIMINANT: u8 = {discriminant};
#[derive(BorshSerialize, Clone, Debug, PartialEq)]
pub struct {name}InstructionArgs {{
{args_fields}}}
/// Builds the `{name}` instruction.
///
/// ### Accounts:
{accounts_doc}
#[allow(clippy::too_many_arguments)]
pub fn {snake_name}(
{fn_params}{args_params}
) -> solana_instruction::Instruction {{
let {accounts_mut}accounts = Vec::with_capacity({account_count});
{account_metas}
let args = {name}InstructionArgs {{ {args_names} }};
let mut data = vec![{upper_name}_DISCRIMINANT];
data.extend(borsh::to_vec(&args).expect("{name} args should always serialize"));
solana_instruction::Instruction {{
program_id: crate::ID,
accounts,
data,
}}
}}
{cpi_block}"#,
upper_name = ix.name.to_snake_case().to_uppercase(),
accounts_doc = accounts
.iter()
.enumerate()
.map(|(i, acc)| {
let mut flags = Vec::new();
if acc.is_mut {
flags.push("writable");
}
if acc.is_signer {
flags.push("signer");
}
format!(
"/// {i}. `[{}]` {}",
flags.join(", "),
acc.name.to_snake_case()
)
})
.collect::<Vec<_>>()
.join("\n"),
))
}