codama_nodes/
instruction_node.rs

1use crate::{
2    CamelCaseString, DiscriminatorNode, Docs, InstructionAccountNode, InstructionArgumentNode,
3    InstructionByteDeltaNode, InstructionRemainingAccountsNode,
4};
5use codama_nodes_derive::node;
6use serde::{Deserialize, Serialize};
7
8#[node]
9#[derive(Default)]
10pub struct InstructionNode {
11    // Data.
12    pub name: CamelCaseString,
13    #[serde(default)]
14    #[serde(skip_serializing_if = "Docs::is_empty")]
15    pub docs: Docs,
16    #[serde(default)]
17    #[serde(skip_serializing_if = "InstructionOptionalAccountStrategy::is_default")]
18    pub optional_account_strategy: InstructionOptionalAccountStrategy,
19
20    // Children.
21    pub accounts: Vec<InstructionAccountNode>,
22    pub arguments: Vec<InstructionArgumentNode>,
23    #[serde(default)]
24    #[serde(skip_serializing_if = "Vec::is_empty")]
25    pub extra_arguments: Vec<InstructionArgumentNode>,
26    #[serde(default)]
27    #[serde(skip_serializing_if = "Vec::is_empty")]
28    pub remaining_accounts: Vec<InstructionRemainingAccountsNode>,
29    #[serde(default)]
30    #[serde(skip_serializing_if = "Vec::is_empty")]
31    pub byte_deltas: Vec<InstructionByteDeltaNode>,
32    #[serde(default)]
33    #[serde(skip_serializing_if = "Vec::is_empty")]
34    pub discriminators: Vec<DiscriminatorNode>,
35    #[serde(default)]
36    #[serde(skip_serializing_if = "Vec::is_empty")]
37    pub sub_instructions: Vec<InstructionNode>,
38}
39
40#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub enum InstructionOptionalAccountStrategy {
43    Omitted,
44    #[default]
45    ProgramId,
46}
47
48impl InstructionOptionalAccountStrategy {
49    pub fn is_default(&self) -> bool {
50        matches!(self, Self::ProgramId)
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn empty_instruction() {
60        let node = InstructionNode {
61            name: "myInstruction".into(),
62            ..InstructionNode::default()
63        };
64        assert_eq!(node.name, CamelCaseString::new("myInstruction"));
65        assert_eq!(node.docs, Docs::default());
66        assert_eq!(
67            node.optional_account_strategy,
68            InstructionOptionalAccountStrategy::ProgramId
69        );
70        assert_eq!(node.accounts, vec![]);
71        assert_eq!(node.arguments, vec![]);
72        assert_eq!(node.extra_arguments, vec![]);
73        assert_eq!(node.remaining_accounts, vec![]);
74        assert_eq!(node.byte_deltas, vec![]);
75        assert_eq!(node.discriminators, vec![]);
76        assert_eq!(node.sub_instructions, vec![]);
77    }
78
79    #[test]
80    fn instruction_with_sub_instructions() {
81        let node = InstructionNode {
82            name: "myInstruction".into(),
83            sub_instructions: vec![
84                InstructionNode {
85                    name: "mySubInstructionA".into(),
86                    ..InstructionNode::default()
87                },
88                InstructionNode {
89                    name: "mySubInstructionB".into(),
90                    ..InstructionNode::default()
91                },
92            ],
93            ..InstructionNode::default()
94        };
95        assert_eq!(
96            node.sub_instructions,
97            vec![
98                InstructionNode {
99                    name: "mySubInstructionA".into(),
100                    ..InstructionNode::default()
101                },
102                InstructionNode {
103                    name: "mySubInstructionB".into(),
104                    ..InstructionNode::default()
105                },
106            ]
107        );
108    }
109
110    #[test]
111    fn to_json() {
112        let node = InstructionNode {
113            name: "myInstruction".into(),
114            ..InstructionNode::default()
115        };
116        let json = serde_json::to_string(&node).unwrap();
117        assert_eq!(
118            json,
119            r#"{"kind":"instructionNode","name":"myInstruction","accounts":[],"arguments":[]}"#
120        );
121    }
122
123    #[test]
124    fn from_json() {
125        let json =
126            r#"{"kind":"instructionNode","name":"myInstruction","accounts":[],"arguments":[]}"#;
127        let node: InstructionNode = serde_json::from_str(json).unwrap();
128        assert_eq!(
129            node,
130            InstructionNode {
131                name: "myInstruction".into(),
132                ..InstructionNode::default()
133            }
134        );
135    }
136}