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