1use 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}