codama_attributes/codama_directives/
account_directive.rs

1use crate::{
2    utils::{FromMeta, SetOnce},
3    Attribute, AttributeContext, CodamaAttribute, CodamaDirective,
4};
5use codama_errors::CodamaError;
6use codama_nodes::{
7    CamelCaseString, Docs, InstructionAccountNode, InstructionInputValueNode, IsAccountSigner,
8};
9use codama_syn_helpers::{extensions::*, Meta};
10
11#[derive(Debug, PartialEq)]
12pub struct AccountDirective {
13    pub account: InstructionAccountNode,
14}
15
16impl AccountDirective {
17    pub fn parse(meta: &Meta, ctx: &AttributeContext) -> syn::Result<Self> {
18        meta.assert_directive("account")?;
19        let mut name = SetOnce::<CamelCaseString>::new("name");
20        if let AttributeContext::Field(syn::Field {
21            ident: Some(ident), ..
22        }) = ctx
23        {
24            name = name.initial_value(ident.to_string().into())
25        }
26        let mut is_writable = SetOnce::<bool>::new("writable").initial_value(false);
27        let mut is_signer = SetOnce::<IsAccountSigner>::new("signer").initial_value(false.into());
28        let mut is_optional = SetOnce::<bool>::new("optional").initial_value(false);
29        let mut default_value = SetOnce::<InstructionInputValueNode>::new("default_value");
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                    "default_value" => {
40                        let value = &meta.as_path_value()?.value;
41                        default_value.set(InstructionInputValueNode::from_meta(value)?, meta)
42                    }
43                    _ => Err(meta.error("unrecognized attribute")),
44                })?,
45        }
46        Ok(AccountDirective {
47            account: InstructionAccountNode {
48                name: name.take(meta)?,
49                is_writable: is_writable.take(meta)?,
50                is_signer: is_signer.take(meta)?,
51                is_optional: is_optional.take(meta)?,
52                // TODO: `docs` for account directives not attached to fields.
53                docs: Docs::default(),
54                default_value: default_value.option(),
55            },
56        })
57    }
58}
59
60impl<'a> TryFrom<&'a CodamaAttribute<'a>> for &'a AccountDirective {
61    type Error = CodamaError;
62
63    fn try_from(attribute: &'a CodamaAttribute) -> Result<Self, Self::Error> {
64        match attribute.directive {
65            CodamaDirective::Account(ref a) => Ok(a),
66            _ => Err(CodamaError::InvalidCodamaDirective {
67                expected: "account".to_string(),
68                actual: attribute.directive.name().to_string(),
69            }),
70        }
71    }
72}
73
74impl<'a> TryFrom<&'a Attribute<'a>> for &'a AccountDirective {
75    type Error = CodamaError;
76
77    fn try_from(attribute: &'a Attribute) -> Result<Self, Self::Error> {
78        <&CodamaAttribute>::try_from(attribute)?.try_into()
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use codama_nodes::PayerValueNode;
86
87    #[test]
88    fn fully_set() {
89        let meta: Meta = syn::parse_quote! { account(name = "payer", writable, signer, optional, default_value = payer) };
90        let item = syn::parse_quote! { struct Foo; };
91        let ctx = AttributeContext::Item(&item);
92        let directive = AccountDirective::parse(&meta, &ctx).unwrap();
93        assert_eq!(
94            directive,
95            AccountDirective {
96                account: InstructionAccountNode {
97                    name: "payer".into(),
98                    is_writable: true,
99                    is_signer: IsAccountSigner::True,
100                    is_optional: true,
101                    default_value: Some(PayerValueNode::new().into()),
102                    docs: Docs::default(),
103                },
104            }
105        );
106    }
107
108    #[test]
109    fn fully_set_with_explicit_values() {
110        let meta: Meta = syn::parse_quote! { account(
111            name = "payer",
112            writable = true,
113            signer = "either",
114            optional = false,
115            default_value = payer
116        ) };
117        let item = syn::parse_quote! { struct Foo; };
118        let ctx = AttributeContext::Item(&item);
119        let directive = AccountDirective::parse(&meta, &ctx).unwrap();
120        assert_eq!(
121            directive,
122            AccountDirective {
123                account: InstructionAccountNode {
124                    name: "payer".into(),
125                    is_writable: true,
126                    is_signer: IsAccountSigner::Either,
127                    is_optional: false,
128                    default_value: Some(PayerValueNode::new().into()),
129                    docs: Docs::default(),
130                },
131            }
132        );
133    }
134
135    #[test]
136    fn empty_on_named_field() {
137        let meta: Meta = syn::parse_quote! { account };
138        let field = syn::parse_quote! { authority: AccountMeta };
139        let ctx = AttributeContext::Field(&field);
140        let directive = AccountDirective::parse(&meta, &ctx).unwrap();
141        assert_eq!(
142            directive,
143            AccountDirective {
144                account: InstructionAccountNode {
145                    name: "authority".into(),
146                    is_writable: false,
147                    is_signer: IsAccountSigner::False,
148                    is_optional: false,
149                    default_value: None,
150                    docs: Docs::default(),
151                },
152            }
153        );
154    }
155
156    #[test]
157    fn empty_on_struct() {
158        let meta: Meta = syn::parse_quote! { account };
159        let item = syn::parse_quote! { struct Foo; };
160        let ctx = AttributeContext::Item(&item);
161        let error = AccountDirective::parse(&meta, &ctx).unwrap_err();
162        assert_eq!(error.to_string(), "name is missing");
163    }
164}