codama_attributes/codama_directives/
account_directive.rs1use crate::{
2 utils::{FromMeta, SetOnce},
3 AttributeContext,
4};
5use codama_nodes::{CamelCaseString, Docs, InstructionAccountNode, IsAccountSigner};
6use codama_syn_helpers::{extensions::*, Meta};
7
8#[derive(Debug, PartialEq)]
9pub struct AccountDirective {
10 pub name: CamelCaseString,
11 pub is_writable: bool,
12 pub is_signer: IsAccountSigner,
13 pub is_optional: bool,
14 }
16
17impl AccountDirective {
18 pub fn parse(meta: &Meta, ctx: &AttributeContext) -> syn::Result<Self> {
19 meta.assert_directive("account")?;
20 let mut name = SetOnce::<CamelCaseString>::new("name");
21 if let AttributeContext::Field(syn::Field {
22 ident: Some(ident), ..
23 }) = ctx
24 {
25 name = name.initial_value(ident.to_string().into())
26 }
27 let mut is_writable = SetOnce::<bool>::new("writable").initial_value(false);
28 let mut is_signer = SetOnce::<IsAccountSigner>::new("signer").initial_value(false.into());
29 let mut is_optional = SetOnce::<bool>::new("optional").initial_value(false);
30 match meta.is_path_or_empty_list() {
31 true => (),
32 false => meta
33 .as_path_list()?
34 .each(|ref meta| match meta.path_str().as_str() {
35 "name" => name.set(String::from_meta(meta)?.into(), meta),
36 "writable" => is_writable.set(bool::from_meta(meta)?, meta),
37 "signer" => is_signer.set(IsAccountSigner::from_meta(meta)?, meta),
38 "optional" => is_optional.set(bool::from_meta(meta)?, meta),
39 _ => Err(meta.error("unrecognized attribute")),
40 })?,
41 }
42 Ok(AccountDirective {
43 name: name.take(meta)?,
44 is_writable: is_writable.take(meta)?,
45 is_signer: is_signer.take(meta)?,
46 is_optional: is_optional.take(meta)?,
47 })
48 }
49}
50
51impl From<&AccountDirective> for InstructionAccountNode {
52 fn from(value: &AccountDirective) -> Self {
53 Self {
54 name: value.name.clone(),
55 is_writable: value.is_writable,
56 is_signer: value.is_signer,
57 is_optional: value.is_optional,
58 docs: Docs::default(),
59 default_value: None,
60 }
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn fully_set() {
70 let meta: Meta = syn::parse_quote! { account(name = "payer", writable, signer, optional) };
71 let item = syn::parse_quote! { struct Foo; };
72 let ctx = AttributeContext::Item(&item);
73 let directive = AccountDirective::parse(&meta, &ctx).unwrap();
74 assert_eq!(
75 directive,
76 AccountDirective {
77 name: "payer".into(),
78 is_writable: true,
79 is_signer: IsAccountSigner::True,
80 is_optional: true,
81 }
82 );
83 }
84
85 #[test]
86 fn fully_set_with_explicit_values() {
87 let meta: Meta = syn::parse_quote! { account(name = "payer", writable = true, signer = "either", optional = false) };
88 let item = syn::parse_quote! { struct Foo; };
89 let ctx = AttributeContext::Item(&item);
90 let directive = AccountDirective::parse(&meta, &ctx).unwrap();
91 assert_eq!(
92 directive,
93 AccountDirective {
94 name: "payer".into(),
95 is_writable: true,
96 is_signer: IsAccountSigner::Either,
97 is_optional: false,
98 }
99 );
100 }
101
102 #[test]
103 fn empty_on_nammed_field() {
104 let meta: Meta = syn::parse_quote! { account };
105 let field = syn::parse_quote! { authority: AccountMeta };
106 let ctx = AttributeContext::Field(&field);
107 let directive = AccountDirective::parse(&meta, &ctx).unwrap();
108 assert_eq!(
109 directive,
110 AccountDirective {
111 name: "authority".into(),
112 is_writable: false,
113 is_signer: IsAccountSigner::False,
114 is_optional: false,
115 }
116 );
117 }
118
119 #[test]
120 fn empty_on_struct() {
121 let meta: Meta = syn::parse_quote! { account };
122 let item = syn::parse_quote! { struct Foo; };
123 let ctx = AttributeContext::Item(&item);
124 let error = AccountDirective::parse(&meta, &ctx).unwrap_err();
125 assert_eq!(error.to_string(), "name is missing");
126 }
127}