Skip to main content

hyperstack_idl/
snapshot.rs

1//! Snapshot type definitions
2
3use serde::{de::Error, Deserialize, Deserializer, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct IdlSnapshot {
7    pub name: String,
8    #[serde(default, skip_serializing_if = "Option::is_none")]
9    pub program_id: Option<String>,
10    pub version: String,
11    pub accounts: Vec<IdlAccountSnapshot>,
12    pub instructions: Vec<IdlInstructionSnapshot>,
13    #[serde(default)]
14    pub types: Vec<IdlTypeDefSnapshot>,
15    #[serde(default)]
16    pub events: Vec<IdlEventSnapshot>,
17    #[serde(default)]
18    pub errors: Vec<IdlErrorSnapshot>,
19    #[serde(default = "default_discriminant_size")]
20    pub discriminant_size: usize,
21}
22
23fn default_discriminant_size() -> usize {
24    8
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct IdlAccountSnapshot {
29    pub name: String,
30    pub discriminator: Vec<u8>,
31    #[serde(default)]
32    pub docs: Vec<String>,
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub serialization: Option<IdlSerializationSnapshot>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct IdlInstructionSnapshot {
39    pub name: String,
40    pub discriminator: Vec<u8>,
41    #[serde(default)]
42    pub docs: Vec<String>,
43    pub accounts: Vec<IdlInstructionAccountSnapshot>,
44    pub args: Vec<IdlFieldSnapshot>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct IdlInstructionAccountSnapshot {
49    pub name: String,
50    #[serde(default)]
51    pub writable: bool,
52    #[serde(default)]
53    pub signer: bool,
54    #[serde(default)]
55    pub optional: bool,
56    #[serde(default)]
57    pub address: Option<String>,
58    #[serde(default)]
59    pub docs: Vec<String>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct IdlFieldSnapshot {
64    pub name: String,
65    #[serde(rename = "type")]
66    pub type_: IdlTypeSnapshot,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70#[serde(untagged)]
71pub enum IdlTypeSnapshot {
72    Simple(String),
73    Array(IdlArrayTypeSnapshot),
74    Option(IdlOptionTypeSnapshot),
75    Vec(IdlVecTypeSnapshot),
76    HashMap(IdlHashMapTypeSnapshot),
77    Defined(IdlDefinedTypeSnapshot),
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct IdlHashMapTypeSnapshot {
82    #[serde(rename = "hashMap", deserialize_with = "deserialize_hash_map")]
83    pub hash_map: (Box<IdlTypeSnapshot>, Box<IdlTypeSnapshot>),
84}
85
86fn deserialize_hash_map<'de, D>(
87    deserializer: D,
88) -> Result<(Box<IdlTypeSnapshot>, Box<IdlTypeSnapshot>), D::Error>
89where
90    D: Deserializer<'de>,
91{
92    let values: Vec<IdlTypeSnapshot> = Vec::deserialize(deserializer)?;
93    if values.len() != 2 {
94        return Err(D::Error::custom("hashMap must have exactly 2 elements"));
95    }
96    let mut iter = values.into_iter();
97    Ok((
98        Box::new(iter.next().expect("length checked")),
99        Box::new(iter.next().expect("length checked")),
100    ))
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct IdlArrayTypeSnapshot {
105    pub array: Vec<IdlArrayElementSnapshot>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
109#[serde(untagged)]
110pub enum IdlArrayElementSnapshot {
111    Type(IdlTypeSnapshot),
112    TypeName(String),
113    Size(u32),
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct IdlOptionTypeSnapshot {
118    pub option: Box<IdlTypeSnapshot>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct IdlVecTypeSnapshot {
123    pub vec: Box<IdlTypeSnapshot>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct IdlDefinedTypeSnapshot {
128    pub defined: IdlDefinedInnerSnapshot,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
132#[serde(untagged)]
133pub enum IdlDefinedInnerSnapshot {
134    Named { name: String },
135    Simple(String),
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
139#[serde(rename_all = "lowercase")]
140pub enum IdlSerializationSnapshot {
141    Borsh,
142    Bytemuck,
143    #[serde(alias = "bytemuckunsafe")]
144    BytemuckUnsafe,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct IdlTypeDefSnapshot {
149    pub name: String,
150    #[serde(default)]
151    pub docs: Vec<String>,
152    #[serde(default, skip_serializing_if = "Option::is_none")]
153    pub serialization: Option<IdlSerializationSnapshot>,
154    #[serde(rename = "type")]
155    pub type_def: IdlTypeDefKindSnapshot,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
159#[serde(untagged)]
160pub enum IdlTypeDefKindSnapshot {
161    Struct {
162        kind: String,
163        fields: Vec<IdlFieldSnapshot>,
164    },
165    TupleStruct {
166        kind: String,
167        fields: Vec<IdlTypeSnapshot>,
168    },
169    Enum {
170        kind: String,
171        variants: Vec<IdlEnumVariantSnapshot>,
172    },
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct IdlEnumVariantSnapshot {
177    pub name: String,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct IdlEventSnapshot {
182    pub name: String,
183    pub discriminator: Vec<u8>,
184    #[serde(default)]
185    pub docs: Vec<String>,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
189pub struct IdlErrorSnapshot {
190    pub code: u32,
191    pub name: String,
192    #[serde(default, skip_serializing_if = "Option::is_none")]
193    pub msg: Option<String>,
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_snapshot_serde() {
202        let snapshot = IdlSnapshot {
203            name: "test_program".to_string(),
204            program_id: Some("11111111111111111111111111111111".to_string()),
205            version: "0.1.0".to_string(),
206            accounts: vec![IdlAccountSnapshot {
207                name: "ExampleAccount".to_string(),
208                discriminator: vec![1, 2, 3, 4, 5, 6, 7, 8],
209                docs: vec!["Example account".to_string()],
210                serialization: Some(IdlSerializationSnapshot::Borsh),
211            }],
212            instructions: vec![IdlInstructionSnapshot {
213                name: "example_instruction".to_string(),
214                discriminator: vec![8, 7, 6, 5, 4, 3, 2, 1],
215                docs: vec!["Example instruction".to_string()],
216                accounts: vec![IdlInstructionAccountSnapshot {
217                    name: "payer".to_string(),
218                    writable: true,
219                    signer: true,
220                    optional: false,
221                    address: None,
222                    docs: vec![],
223                }],
224                args: vec![IdlFieldSnapshot {
225                    name: "amount".to_string(),
226                    type_: IdlTypeSnapshot::HashMap(IdlHashMapTypeSnapshot {
227                        hash_map: (
228                            Box::new(IdlTypeSnapshot::Simple("u64".to_string())),
229                            Box::new(IdlTypeSnapshot::Simple("string".to_string())),
230                        ),
231                    }),
232                }],
233            }],
234            types: vec![IdlTypeDefSnapshot {
235                name: "ExampleType".to_string(),
236                docs: vec![],
237                serialization: None,
238                type_def: IdlTypeDefKindSnapshot::Struct {
239                    kind: "struct".to_string(),
240                    fields: vec![IdlFieldSnapshot {
241                        name: "value".to_string(),
242                        type_: IdlTypeSnapshot::Simple("u64".to_string()),
243                    }],
244                },
245            }],
246            events: vec![IdlEventSnapshot {
247                name: "ExampleEvent".to_string(),
248                discriminator: vec![0, 0, 0, 0, 0, 0, 0, 1],
249                docs: vec![],
250            }],
251            errors: vec![IdlErrorSnapshot {
252                code: 6000,
253                name: "ExampleError".to_string(),
254                msg: Some("example".to_string()),
255            }],
256            discriminant_size: default_discriminant_size(),
257        };
258
259        let serialized = serde_json::to_value(&snapshot).expect("serialize snapshot");
260        let deserialized: IdlSnapshot =
261            serde_json::from_value(serialized.clone()).expect("deserialize snapshot");
262        let round_trip = serde_json::to_value(&deserialized).expect("re-serialize snapshot");
263
264        assert_eq!(serialized, round_trip);
265        assert_eq!(deserialized.name, "test_program");
266    }
267
268    #[test]
269    fn test_hashmap_compat() {
270        let json = r#"{"hashMap":["u64","string"]}"#;
271        let parsed: IdlHashMapTypeSnapshot =
272            serde_json::from_str(json).expect("deserialize hashMap");
273
274        assert!(matches!(
275            parsed.hash_map.0.as_ref(),
276            IdlTypeSnapshot::Simple(value) if value == "u64"
277        ));
278        assert!(matches!(
279            parsed.hash_map.1.as_ref(),
280            IdlTypeSnapshot::Simple(value) if value == "string"
281        ));
282    }
283}