use std::collections::HashMap;
use quote::format_ident;
use syn::{parse_quote, Variant};
use trident_idl_spec::{
idl_type_to_syn_type, Idl, IdlDefinedFields, IdlEnumVariant, IdlField, IdlType, IdlTypeDef,
IdlTypeDefTy,
};
pub(crate) fn get_types(idl: &Idl, program_accounts: HashMap<String, Vec<u8>>) -> Vec<syn::Item> {
idl.types.iter().fold(Vec::new(), |mut types, type_def| {
match &type_def.ty {
IdlTypeDefTy::Struct {
fields: struct_fields,
} => {
process_struct(type_def, struct_fields, &mut types, &program_accounts);
}
IdlTypeDefTy::Enum {
variants: enum_variants,
} => {
process_enum(type_def, enum_variants, &mut types, &program_accounts);
}
IdlTypeDefTy::Type { alias: _ } => process_type(),
}
types
})
}
fn process_struct(
type_def: &IdlTypeDef,
struct_fields: &Option<IdlDefinedFields>,
types: &mut Vec<syn::Item>,
program_accounts: &HashMap<String, Vec<u8>>,
) {
let is_program_account = program_accounts.get(&type_def.name);
let type_name = &type_def.name;
let type_ident = format_ident!("{}", type_name);
match struct_fields {
Some(fields) => match fields {
IdlDefinedFields::Named(idl_fields) => {
process_struct_named(type_def, idl_fields, types, program_accounts);
}
IdlDefinedFields::Tuple(idl_types) => {
process_struct_tuple(type_def, idl_types, types, program_accounts);
}
},
None => {
let type_item: syn::Item = match is_program_account {
Some(_) => {
parse_quote! {
#[derive(Debug, BorshDeserialize, BorshSerialize, Clone)]
pub struct #type_ident;
}
}
None => {
parse_quote! {
#[derive(Arbitrary, Debug, BorshDeserialize, BorshSerialize, Clone)]
pub struct #type_ident;
}
}
};
types.push(type_item);
}
}
}
fn process_enum(
type_def: &IdlTypeDef,
enum_variants: &[IdlEnumVariant],
types: &mut Vec<syn::Item>,
program_accounts: &HashMap<String, Vec<u8>>,
) {
let enum_variants = enum_variants
.iter()
.fold(Vec::new(), |mut variants, variant| {
match &variant.fields {
Some(fields) => match fields {
IdlDefinedFields::Named(idl_fields) => {
process_enum_variant_fields_named(
type_def,
idl_fields,
variant,
&mut variants,
program_accounts,
);
}
IdlDefinedFields::Tuple(idl_types) => {
process_enum_variant_fields_tuple(
type_def,
idl_types,
variant,
&mut variants,
program_accounts,
);
}
},
None => {
process_empty_variant(variant, &mut variants);
}
}
variants
});
let type_name = &type_def.name;
let type_ident = format_ident!("{}", type_name);
let type_item: syn::Item = parse_quote! {
#[derive(Arbitrary, Debug, BorshDeserialize, BorshSerialize, Clone)]
pub enum #type_ident {
#(#enum_variants),*
}
};
types.push(type_item);
}
fn process_type() {
panic!("Parsing Type is not implemented yet")
}
fn process_struct_named(
type_def: &IdlTypeDef,
idl_fields: &[IdlField],
types: &mut Vec<syn::Item>,
program_accounts: &HashMap<String, Vec<u8>>,
) {
let is_program_account = program_accounts.get(&type_def.name);
let type_name = &type_def.name;
let type_ident = format_ident!("{}", type_name);
let fields = idl_fields
.iter()
.fold(Vec::new(), |mut named_fields, field| {
process_struct_field(field, &mut named_fields, is_program_account);
named_fields
});
let struct_definition: syn::Item = match is_program_account {
Some(_) => parse_quote! {
#[derive(Debug, BorshDeserialize, BorshSerialize, Clone)]
pub struct #type_ident {
#(#fields),*
}
},
None => parse_quote! {
#[derive(Arbitrary, Debug, BorshDeserialize, BorshSerialize, Clone)]
pub struct #type_ident {
#(#fields),*
}
},
};
types.push(struct_definition);
}
fn process_struct_field(
field: &IdlField,
named_fields: &mut Vec<syn::FnArg>,
is_program_account: Option<&Vec<u8>>,
) {
let field_name = &field.name;
let field_ident = format_ident!("{}", field_name);
let (field_type, _is_custom) = match is_program_account {
Some(_) => idl_type_to_syn_type(&field.ty, 0, false),
None => idl_type_to_syn_type(&field.ty, 0, true),
};
let field: syn::FnArg = parse_quote!(#field_ident: #field_type);
named_fields.push(field);
}
fn process_struct_tuple(
type_def: &IdlTypeDef,
idl_types: &[IdlType],
types: &mut Vec<syn::Item>,
program_accounts: &HashMap<String, Vec<u8>>,
) {
let is_program_account = program_accounts.get(&type_def.name);
let type_name = &type_def.name;
let type_ident = format_ident!("{}", type_name);
let tuple_fields: Vec<syn::Type> = idl_types
.iter()
.map(|idl_type| {
let (field_type, _is_custom) = match is_program_account {
Some(_) => idl_type_to_syn_type(idl_type, 0, false),
None => idl_type_to_syn_type(idl_type, 0, true),
};
field_type
})
.collect();
let struct_definition: syn::Item = match is_program_account {
Some(_) => parse_quote! {
#[derive(Debug, BorshDeserialize, BorshSerialize)]
struct #type_ident(#(pub #tuple_fields),*);
},
None => parse_quote! {
#[derive(Arbitrary, Debug, BorshDeserialize, BorshSerialize, Clone)]
struct #type_ident(#(pub #tuple_fields),*);
},
};
types.push(struct_definition);
}
fn process_enum_variant_fields_named(
type_def: &IdlTypeDef,
idl_fields: &[IdlField],
variant: &IdlEnumVariant,
variants: &mut Vec<Variant>,
program_accounts: &HashMap<String, Vec<u8>>,
) {
let is_program_account = program_accounts.get(&type_def.name);
let variant_name = &variant.name;
let variant_ident = format_ident!("{}", variant_name);
let fields = idl_fields
.iter()
.fold(Vec::new(), |mut named_fields, field| {
process_struct_field(field, &mut named_fields, is_program_account);
named_fields
});
let variant = parse_quote!(#variant_ident { #(#fields),* });
variants.push(variant);
}
fn process_enum_variant_fields_tuple(
type_def: &IdlTypeDef,
idl_types: &[IdlType],
variant: &IdlEnumVariant,
variants: &mut Vec<Variant>,
program_accounts: &HashMap<String, Vec<u8>>,
) {
let is_program_account = program_accounts.get(&type_def.name);
let variant_name = &variant.name;
let variant_ident = format_ident!("{}", variant_name);
let tuple_fields: Vec<syn::Type> = idl_types
.iter()
.map(|idl_type| {
let (syn_type, _is_custom) = match is_program_account {
Some(_) => idl_type_to_syn_type(idl_type, 0, false),
None => idl_type_to_syn_type(idl_type, 0, true),
};
syn_type
})
.collect();
let variant = parse_quote!(#variant_ident(#(#tuple_fields),*));
variants.push(variant);
}
fn process_empty_variant(variant: &IdlEnumVariant, variants: &mut Vec<Variant>) {
let variant_name = &variant.name;
let variant_ident = format_ident!("{}", variant_name);
let variant: syn::Variant = parse_quote!(#variant_ident);
variants.push(variant);
}