1use {
2 crate::{constraints::Constraints, Docs},
3 proc_macro2::Span,
4 syn::{
5 parse_quote,
6 visit::{visit_path_segment, Visit},
7 Field, Ident, PathSegment,
8 },
9};
10
11#[derive(Default, Clone)]
12pub struct AccountMeta {
13 pub is_signer: bool,
14 pub is_mutable: bool,
15 pub is_optional: bool,
16}
17
18#[derive(Clone)]
19pub struct InstructionAccount {
20 pub name: Ident,
21 pub constraints: Constraints,
22 pub meta: AccountMeta,
23 pub docs: Vec<String>,
24 ty: Option<PathSegment>,
25 pub inner_ty: Ident,
26}
27
28impl InstructionAccount {
29 pub fn get_ty(&self) -> PathSegment {
30 let inner_ty = &self.inner_ty;
31 self.ty.clone().unwrap_or(parse_quote!(#inner_ty<'info>))
32 }
33}
34
35impl TryFrom<&Field> for InstructionAccount {
36 type Error = syn::Error;
37
38 fn try_from(value: &Field) -> Result<Self, Self::Error> {
39 let mut acc = InstructionAccount {
40 name: value
41 .ident
42 .clone()
43 .ok_or(syn::Error::new_spanned(value, "The field need to be named"))?,
44 docs: Docs::from(value.attrs.as_slice()).into_vec(),
45 constraints: Constraints::try_from(value.attrs.as_slice())?,
46 meta: AccountMeta::default(),
47 ty: None,
48 inner_ty: Ident::new("UncheckedAccount", Span::call_site()),
49 };
50
51 acc.visit_type(&value.ty);
52 Ok(acc)
53 }
54}
55
56impl Visit<'_> for InstructionAccount {
57 fn visit_path_segment(&mut self, i: &syn::PathSegment) {
58 let ty = i.ident.to_string();
59
60 if ty == "Option" {
61 self.meta.is_optional = true;
62 visit_path_segment(self, i);
63 } else if ty.starts_with("Mut") {
64 self.meta.is_mutable = true;
65 if self.ty.is_none() {
66 self.ty = Some(i.clone());
67 }
68 visit_path_segment(self, i);
69 } else if ty.ends_with("Signer") {
70 self.meta.is_signer = true;
71 if self.ty.is_none() {
72 self.ty = Some(i.clone());
73 }
74 visit_path_segment(self, i);
75 } else {
76 if self.ty.is_none() {
77 self.ty = Some(i.clone());
78 }
79 self.inner_ty = i.ident.clone();
80 visit_path_segment(self, i);
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use {super::*, syn::parse_quote};
88
89 #[test]
90 fn test_instruction_parse_with_context() {
91 let field: syn::Field = parse_quote!(pub random2: Option<Mut<Account<Random2>>>);
92 let account = InstructionAccount::try_from(&field).unwrap();
93 assert_eq!(account.inner_ty, "Random2");
94 assert!(account.meta.is_mutable);
95 assert!(account.meta.is_optional);
96 assert!(!account.meta.is_signer);
97
98 let field: syn::Field = parse_quote!(pub random2: UncheckedSigner<'info, Account<Random2>>);
99 let account = InstructionAccount::try_from(&field).unwrap();
100 assert_eq!(account.inner_ty, "Random2");
101 assert!(!account.meta.is_mutable);
102 assert!(!account.meta.is_optional);
103 assert!(account.meta.is_signer);
104
105 let field: syn::Field = parse_quote!(pub random2: Signer<'info, Account<Random2>>);
106 let account = InstructionAccount::try_from(&field).unwrap();
107 assert_eq!(account.inner_ty, "Random2");
108 assert!(!account.meta.is_mutable);
109 assert!(!account.meta.is_optional);
110 assert!(account.meta.is_signer);
111
112 let field: syn::Field = parse_quote!(pub random2: Account<TokenAccount>);
113 let account = InstructionAccount::try_from(&field).unwrap();
114 assert_eq!(account.inner_ty, "TokenAccount");
115 assert!(!account.meta.is_mutable);
116 assert!(!account.meta.is_optional);
117 assert!(!account.meta.is_signer);
118
119 let field: syn::Field = parse_quote!(pub random2: UncheckedAccount);
120 let account = InstructionAccount::try_from(&field).unwrap();
121 assert_eq!(account.inner_ty, "UncheckedAccount");
122 assert!(!account.meta.is_mutable);
123 assert!(!account.meta.is_optional);
124 assert!(!account.meta.is_signer);
125 }
126}