codama_attributes/codama_directives/
account_directive.rs

1use 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    // TODO: `docs` for account directives not attached to fields.
15}
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}