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 let mut docs = SetOnce::<Docs>::new("docs");
31 match meta.is_path_or_empty_list() {
32 true => (),
33 false => meta
34 .as_path_list()?
35 .each(|ref meta| match meta.path_str().as_str() {
36 "name" => name.set(meta.as_value()?.as_expr()?.as_string()?.into(), meta),
37 "writable" => is_writable.set(bool::from_meta(meta)?, meta),
38 "signer" => is_signer.set(IsAccountSigner::from_meta(meta)?, meta),
39 "optional" => is_optional.set(bool::from_meta(meta)?, meta),
40 "default_value" => default_value.set(
41 InstructionInputValueNode::from_meta(meta.as_value()?)?,
42 meta,
43 ),
44 "docs" => docs.set(Docs::from_meta(meta)?, meta),
45 _ => Err(meta.error("unrecognized attribute")),
46 })?,
47 }
48 Ok(AccountDirective {
49 account: InstructionAccountNode {
50 name: name.take(meta)?,
51 is_writable: is_writable.take(meta)?,
52 is_signer: is_signer.take(meta)?,
53 is_optional: is_optional.take(meta)?,
54 docs: docs.option().unwrap_or_default(),
55 default_value: default_value.option(),
56 },
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.as_ref() {
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
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use codama_nodes::PayerValueNode;
87
88 #[test]
89 fn fully_set() {
90 let meta: Meta = syn::parse_quote! { account(name = "payer", writable, signer, optional, default_value = payer) };
91 let item = syn::parse_quote! { struct Foo; };
92 let ctx = AttributeContext::Item(&item);
93 let directive = AccountDirective::parse(&meta, &ctx).unwrap();
94 assert_eq!(
95 directive,
96 AccountDirective {
97 account: InstructionAccountNode {
98 name: "payer".into(),
99 is_writable: true,
100 is_signer: IsAccountSigner::True,
101 is_optional: true,
102 default_value: Some(PayerValueNode::new().into()),
103 docs: Docs::default(),
104 },
105 }
106 );
107 }
108
109 #[test]
110 fn fully_set_with_explicit_values() {
111 let meta: Meta = syn::parse_quote! { account(
112 name = "payer",
113 writable = true,
114 signer = "either",
115 optional = false,
116 default_value = payer
117 ) };
118 let item = syn::parse_quote! { struct Foo; };
119 let ctx = AttributeContext::Item(&item);
120 let directive = AccountDirective::parse(&meta, &ctx).unwrap();
121 assert_eq!(
122 directive,
123 AccountDirective {
124 account: InstructionAccountNode {
125 name: "payer".into(),
126 is_writable: true,
127 is_signer: IsAccountSigner::Either,
128 is_optional: false,
129 default_value: Some(PayerValueNode::new().into()),
130 docs: Docs::default(),
131 },
132 }
133 );
134 }
135
136 #[test]
137 fn empty_on_named_field() {
138 let meta: Meta = syn::parse_quote! { account };
139 let field = syn::parse_quote! { authority: AccountMeta };
140 let ctx = AttributeContext::Field(&field);
141 let directive = AccountDirective::parse(&meta, &ctx).unwrap();
142 assert_eq!(
143 directive,
144 AccountDirective {
145 account: InstructionAccountNode {
146 name: "authority".into(),
147 is_writable: false,
148 is_signer: IsAccountSigner::False,
149 is_optional: false,
150 default_value: None,
151 docs: Docs::default(),
152 },
153 }
154 );
155 }
156
157 #[test]
158 fn empty_on_struct() {
159 let meta: Meta = syn::parse_quote! { account };
160 let item = syn::parse_quote! { struct Foo; };
161 let ctx = AttributeContext::Item(&item);
162 let error = AccountDirective::parse(&meta, &ctx).unwrap_err();
163 assert_eq!(error.to_string(), "name is missing");
164 }
165
166 #[test]
167 fn with_docs() {
168 let meta: Meta = syn::parse_quote! { account(name = "stake", writable, docs = "what this account is for") };
169 let item = syn::parse_quote! { struct Foo; };
170 let ctx = AttributeContext::Item(&item);
171 let directive = AccountDirective::parse(&meta, &ctx).unwrap();
172 assert_eq!(
173 directive,
174 AccountDirective {
175 account: InstructionAccountNode {
176 name: "stake".into(),
177 is_writable: true,
178 is_signer: IsAccountSigner::False,
179 is_optional: false,
180 default_value: None,
181 docs: vec!["what this account is for".to_string()].into(),
182 },
183 }
184 );
185 }
186
187 #[test]
188 fn with_docs_array() {
189 let meta: Meta = syn::parse_quote! { account(name = "authority", signer, docs = ["Line 1", "Line 2", "Line 3"]) };
190 let item = syn::parse_quote! { struct Foo; };
191 let ctx = AttributeContext::Item(&item);
192 let directive = AccountDirective::parse(&meta, &ctx).unwrap();
193 assert_eq!(
194 directive,
195 AccountDirective {
196 account: InstructionAccountNode {
197 name: "authority".into(),
198 is_writable: false,
199 is_signer: IsAccountSigner::True,
200 is_optional: false,
201 default_value: None,
202 docs: vec![
203 "Line 1".to_string(),
204 "Line 2".to_string(),
205 "Line 3".to_string()
206 ]
207 .into(),
208 },
209 }
210 );
211 }
212}