Skip to main content

codama_attributes/codama_directives/
program_directive.rs

1use crate::{utils::SetOnce, Attribute, CodamaAttribute, CodamaDirective, TryFromFilter};
2use codama_errors::CodamaError;
3use codama_nodes::{CamelCaseString, Node, ProgramNode};
4use codama_syn_helpers::{extensions::*, Meta};
5
6#[derive(Debug, PartialEq)]
7pub struct ProgramDirective {
8    pub name: CamelCaseString,
9    pub address: String,
10}
11
12impl ProgramDirective {
13    pub fn parse(meta: &Meta) -> syn::Result<Self> {
14        let pl = meta.assert_directive("program")?.as_path_list()?;
15
16        let mut name = SetOnce::<CamelCaseString>::new("name");
17        let mut address = SetOnce::<String>::new("address");
18
19        pl.each(|ref meta| match meta.path_str().as_str() {
20            "name" => name.set(meta.as_value()?.as_expr()?.as_string()?.into(), meta),
21            "address" => address.set(meta.as_value()?.as_expr()?.as_string()?, meta),
22            _ => Err(meta.error("unrecognized attribute")),
23        })?;
24
25        Ok(Self {
26            name: name.take(meta)?,
27            address: address.take(meta)?,
28        })
29    }
30
31    pub fn apply(attributes: &crate::Attributes, node: Node) -> Node {
32        match attributes.get_last(Self::filter) {
33            Some(pd) => pd.update_or_wrap_program_node(node),
34            None => node,
35        }
36    }
37
38    pub fn update_or_wrap_program_node(&self, node: Node) -> Node {
39        match node {
40            Node::Root(mut root) => {
41                root.program.name = self.name.clone();
42                root.program.public_key = self.address.clone();
43                root.into()
44            }
45            Node::Program(mut program) => {
46                program.name = self.name.clone();
47                program.public_key = self.address.clone();
48                program.into()
49            }
50            Node::Account(account) => ProgramNode {
51                name: self.name.clone(),
52                public_key: self.address.clone(),
53                accounts: vec![account],
54                ..ProgramNode::default()
55            }
56            .into(),
57            Node::Constant(constant) => ProgramNode {
58                name: self.name.clone(),
59                public_key: self.address.clone(),
60                constants: vec![constant],
61                ..ProgramNode::default()
62            }
63            .into(),
64            Node::Instruction(instruction) => ProgramNode {
65                name: self.name.clone(),
66                public_key: self.address.clone(),
67                instructions: vec![instruction],
68                ..ProgramNode::default()
69            }
70            .into(),
71            Node::Error(error) => ProgramNode {
72                name: self.name.clone(),
73                public_key: self.address.clone(),
74                errors: vec![error],
75                ..ProgramNode::default()
76            }
77            .into(),
78            Node::Pda(pda) => ProgramNode {
79                name: self.name.clone(),
80                public_key: self.address.clone(),
81                pdas: vec![pda],
82                ..ProgramNode::default()
83            }
84            .into(),
85            Node::Event(event) => ProgramNode {
86                name: self.name.clone(),
87                public_key: self.address.clone(),
88                events: vec![event],
89                ..ProgramNode::default()
90            }
91            .into(),
92            other => other,
93        }
94    }
95}
96
97impl<'a> TryFrom<&'a CodamaAttribute<'a>> for &'a ProgramDirective {
98    type Error = CodamaError;
99
100    fn try_from(attribute: &'a CodamaAttribute) -> Result<Self, Self::Error> {
101        match attribute.directive.as_ref() {
102            CodamaDirective::Program(ref a) => Ok(a),
103            _ => Err(CodamaError::InvalidCodamaDirective {
104                expected: "program".to_string(),
105                actual: attribute.directive.name().to_string(),
106            }),
107        }
108    }
109}
110
111impl<'a> TryFrom<&'a Attribute<'a>> for &'a ProgramDirective {
112    type Error = CodamaError;
113
114    fn try_from(attribute: &'a Attribute) -> Result<Self, Self::Error> {
115        <&CodamaAttribute>::try_from(attribute)?.try_into()
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn ok() {
125        let meta: Meta = syn::parse_quote! { program(name = "associatedToken", address = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") };
126        let directive = ProgramDirective::parse(&meta).unwrap();
127        assert_eq!(
128            directive,
129            ProgramDirective {
130                name: CamelCaseString::from("associatedToken"),
131                address: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL".to_string(),
132            }
133        );
134    }
135
136    #[test]
137    fn name_missing() {
138        let meta: Meta =
139            syn::parse_quote! { program(address = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") };
140        let error = ProgramDirective::parse(&meta).unwrap_err();
141        assert_eq!(error.to_string(), "name is missing");
142    }
143
144    #[test]
145    fn address_missing() {
146        let meta: Meta = syn::parse_quote! { program(name = "associatedToken") };
147        let error = ProgramDirective::parse(&meta).unwrap_err();
148        assert_eq!(error.to_string(), "address is missing");
149    }
150}