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 name: CamelCaseString,
14 pub is_writable: bool,
15 pub is_signer: IsAccountSigner,
16 pub is_optional: bool,
17 pub default_value: Option<InstructionInputValueNode>,
18 }
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}