codama_attributes/codama_directives/
program_directive.rs1use crate::{
2 utils::SetOnce, Attribute, AttributeContext, CodamaAttribute, CodamaDirective, TryFromFilter,
3};
4use codama_errors::CodamaError;
5use codama_nodes::{CamelCaseString, Node, ProgramNode};
6use codama_syn_helpers::{extensions::*, Meta};
7
8#[derive(Debug, PartialEq)]
20pub struct ProgramDirective {
21 pub name: Option<CamelCaseString>,
22 pub address: Option<String>,
23}
24
25impl ProgramDirective {
26 pub fn parse(meta: &Meta, ctx: &AttributeContext) -> syn::Result<Self> {
27 let pl = meta.assert_directive("program")?.as_path_list()?;
28
29 let mut name = SetOnce::<CamelCaseString>::new("name");
30 let mut address = SetOnce::<String>::new("address");
31
32 pl.each(|ref meta| match meta.path_str().as_str() {
33 "name" => name.set(meta.as_value()?.as_expr()?.as_string()?.into(), meta),
34 "address" => address.set(meta.as_value()?.as_expr()?.as_string()?, meta),
35 _ => Err(meta.error("unrecognized attribute")),
36 })?;
37
38 if let AttributeContext::Crate(_) = ctx {
40 Ok(Self {
41 name: name.option(),
42 address: address.option(),
43 })
44 } else {
45 Ok(Self {
46 name: Some(name.take(meta)?),
47 address: Some(address.take(meta)?),
48 })
49 }
50 }
51
52 pub fn apply(attributes: &crate::Attributes, node: Node) -> Node {
53 match attributes.get_last(Self::filter) {
54 Some(pd) => pd.update_or_wrap_program_node(node),
55 None => node,
56 }
57 }
58
59 pub fn update_or_wrap_program_node(&self, node: Node) -> Node {
60 let name = self.name.clone().unwrap_or_default();
62 let public_key = self.address.clone().unwrap_or_default();
63 match node {
64 Node::Root(mut root) => {
66 if let Some(name) = self.name.clone() {
67 root.program.name = name;
68 }
69 if let Some(public_key) = self.address.clone() {
70 root.program.public_key = public_key;
71 }
72 root.into()
73 }
74 Node::Program(mut program) => {
75 if let Some(name) = self.name.clone() {
76 program.name = name;
77 }
78 if let Some(public_key) = self.address.clone() {
79 program.public_key = public_key;
80 }
81 program.into()
82 }
83 Node::Account(account) => ProgramNode {
84 name,
85 public_key,
86 accounts: vec![account],
87 ..ProgramNode::default()
88 }
89 .into(),
90 Node::Constant(constant) => ProgramNode {
91 name,
92 public_key,
93 constants: vec![constant],
94 ..ProgramNode::default()
95 }
96 .into(),
97 Node::Instruction(instruction) => ProgramNode {
98 name,
99 public_key,
100 instructions: vec![instruction],
101 ..ProgramNode::default()
102 }
103 .into(),
104 Node::Error(error) => ProgramNode {
105 name,
106 public_key,
107 errors: vec![error],
108 ..ProgramNode::default()
109 }
110 .into(),
111 Node::Pda(pda) => ProgramNode {
112 name,
113 public_key,
114 pdas: vec![pda],
115 ..ProgramNode::default()
116 }
117 .into(),
118 Node::Event(event) => ProgramNode {
119 name,
120 public_key,
121 events: vec![event],
122 ..ProgramNode::default()
123 }
124 .into(),
125 other => other,
126 }
127 }
128}
129
130impl<'a> TryFrom<&'a CodamaAttribute<'a>> for &'a ProgramDirective {
131 type Error = CodamaError;
132
133 fn try_from(attribute: &'a CodamaAttribute) -> Result<Self, Self::Error> {
134 match attribute.directive.as_ref() {
135 CodamaDirective::Program(ref a) => Ok(a),
136 _ => Err(CodamaError::InvalidCodamaDirective {
137 expected: "program".to_string(),
138 actual: attribute.directive.name().to_string(),
139 }),
140 }
141 }
142}
143
144impl<'a> TryFrom<&'a Attribute<'a>> for &'a ProgramDirective {
145 type Error = CodamaError;
146
147 fn try_from(attribute: &'a Attribute) -> Result<Self, Self::Error> {
148 <&CodamaAttribute>::try_from(attribute)?.try_into()
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 fn item_ctx() -> syn::Item {
157 syn::parse_quote! { struct Foo; }
158 }
159
160 fn crate_file() -> syn::File {
161 syn::parse_quote! {}
162 }
163
164 #[test]
165 fn ok() {
166 let item = item_ctx();
167 let ctx = AttributeContext::Item(&item);
168 let meta: Meta = syn::parse_quote! { program(name = "associatedToken", address = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") };
169 let directive = ProgramDirective::parse(&meta, &ctx).unwrap();
170 assert_eq!(
171 directive,
172 ProgramDirective {
173 name: Some(CamelCaseString::from("associatedToken")),
174 address: Some("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL".to_string()),
175 }
176 );
177 }
178
179 #[test]
180 fn name_missing_at_item_scope() {
181 let item = item_ctx();
182 let ctx = AttributeContext::Item(&item);
183 let meta: Meta =
184 syn::parse_quote! { program(address = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") };
185 let error = ProgramDirective::parse(&meta, &ctx).unwrap_err();
186 assert_eq!(error.to_string(), "name is missing");
187 }
188
189 #[test]
190 fn address_missing_at_item_scope() {
191 let item = item_ctx();
192 let ctx = AttributeContext::Item(&item);
193 let meta: Meta = syn::parse_quote! { program(name = "associatedToken") };
194 let error = ProgramDirective::parse(&meta, &ctx).unwrap_err();
195 assert_eq!(error.to_string(), "address is missing");
196 }
197
198 #[test]
199 fn name_only_at_crate_scope() {
200 let file = crate_file();
201 let ctx = AttributeContext::Crate(&file);
202 let meta: Meta = syn::parse_quote! { program(name = "associatedToken") };
203 let directive = ProgramDirective::parse(&meta, &ctx).unwrap();
204 assert_eq!(
205 directive,
206 ProgramDirective {
207 name: Some(CamelCaseString::from("associatedToken")),
208 address: None,
209 }
210 );
211 }
212
213 #[test]
214 fn address_only_at_crate_scope() {
215 let file = crate_file();
216 let ctx = AttributeContext::Crate(&file);
217 let meta: Meta =
218 syn::parse_quote! { program(address = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") };
219 let directive = ProgramDirective::parse(&meta, &ctx).unwrap();
220 assert_eq!(
221 directive,
222 ProgramDirective {
223 name: None,
224 address: Some("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL".to_string()),
225 }
226 );
227 }
228
229 #[test]
230 fn empty_at_crate_scope() {
231 let file = crate_file();
232 let ctx = AttributeContext::Crate(&file);
233 let meta: Meta = syn::parse_quote! { program() };
234 let directive = ProgramDirective::parse(&meta, &ctx).unwrap();
235 assert_eq!(
236 directive,
237 ProgramDirective {
238 name: None,
239 address: None,
240 }
241 );
242 }
243}