codama_attributes/codama_directives/
account_directive.rs1use 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 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}