1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
use std::collections::{HashMap, HashSet};
use crate::{
errors::{ChainsawError, ChainsawResult},
IdlProvider,
};
#[cfg(feature = "json")]
mod json_uses {
pub use crate::json::{JsonAccountsDeserializer, JsonSerializationOpts};
pub use std::fmt::Write;
}
#[cfg(feature = "json")]
use json_uses::*;
#[cfg(feature = "bson")]
mod bson_uses {
pub use crate::bson::{BsonAccountsDeserializer, BsonSerializationOpts};
pub use bson::raw::RawBson;
}
#[cfg(feature = "bson")]
use bson_uses::*;
/// Setup to deserialize accounts for a given program. The accounts are expected to have been
/// serialized using the [borsh] format.
///
/// Uses deserializers defined inside [deserializer] modules under the hood in order to resolve the
/// appropriate [borsh] deserializers for each field.
pub struct ChainsawDeserializer<'opts> {
#[cfg(feature = "json")]
/// The deserializers for accounts of for each program
json_account_deserializers:
HashMap<String, JsonAccountsDeserializer<'opts>>,
/// The [JsonSerializationOpts] specifying how specific data types should be deserialized.
#[cfg(feature = "json")]
json_serialization_opts: &'opts JsonSerializationOpts,
#[cfg(feature = "bson")]
/// The deserializers for accounts of for each program
bson_account_deserializers:
HashMap<String, BsonAccountsDeserializer<'opts>>,
///
/// The [BsonSerializationOpts] specifying how specific data types should be deserialized.
#[cfg(feature = "bson")]
bson_serialization_opts: &'opts BsonSerializationOpts,
}
impl<'opts> ChainsawDeserializer<'opts> {
/// Creates an instance of a [ChainsawDeserializer].
/// Make sure to use [ChainsawDeserializer::add_idl_json] for each program _before_ attempting
/// to deserialize accounts for it.
///
/// - [serialization_opts] specifying how specific data types should be deserialized.
pub fn new(
#[cfg(feature = "json")]
json_serialization_opts: &'opts JsonSerializationOpts,
#[cfg(feature = "bson")]
bson_serialization_opts: &'opts BsonSerializationOpts,
) -> Self {
Self {
#[cfg(feature = "json")]
json_account_deserializers: HashMap::new(),
#[cfg(feature = "json")]
json_serialization_opts,
#[cfg(feature = "bson")]
bson_account_deserializers: HashMap::new(),
#[cfg(feature = "bson")]
bson_serialization_opts,
}
}
/// Parses an [IDL] specification from the provided [idl_json] for the [program_id] and adds a
/// to the json/bson accounts deserializer derived from it.
pub fn add_idl_json(
&mut self,
program_id: String,
idl_json: &str,
provider: IdlProvider,
) -> ChainsawResult<()> {
#[cfg(feature = "json")]
{
#[cfg(feature = "bson")]
let (provider, program_id) = (provider.clone(), program_id.clone());
let json_deserializer = JsonAccountsDeserializer::try_from_idl(
idl_json,
provider,
self.json_serialization_opts,
)?;
self.json_account_deserializers
.insert(program_id, json_deserializer);
}
#[cfg(feature = "bson")]
{
let bson_deserializer = BsonAccountsDeserializer::try_from_idl(
idl_json,
provider,
self.bson_serialization_opts,
)?;
self.bson_account_deserializers
.insert(program_id, bson_deserializer);
}
Ok(())
}
#[cfg(any(feature = "json", feature = "bson"))]
pub fn account_name(
&self,
program_id: &str,
discriminator: &crate::DiscriminatorBytes,
) -> Option<&str> {
#[cfg(feature = "json")]
{
self.json_account_deserializers
.get(program_id)
.unwrap()
.account_name(discriminator)
}
#[cfg(all(feature = "bson", not(feature = "json")))]
{
self.bson_account_deserializers
.get(program_id)
.unwrap()
.account_name(discriminator)
}
}
/// Returns `true` if the IDL of the given [program_id] has been added to the deserializer.
#[cfg(any(feature = "json", feature = "bson"))]
pub fn has_idl(&self, program_id: &str) -> bool {
#[cfg(feature = "json")]
{
self.json_account_deserializers.contains_key(program_id)
}
#[cfg(all(feature = "bson", not(feature = "json")))]
{
self.bson_account_deserializers.contains_key(program_id)
}
}
/// Returns all program ids for which IDLs have been added to the deserializer.
#[cfg(any(feature = "json", feature = "bson"))]
pub fn added_idls(&self) -> HashSet<String> {
#[cfg(feature = "json")]
{
self.json_account_deserializers.keys().cloned().collect()
}
#[cfg(all(feature = "bson", not(feature = "json")))]
{
self.bson_account_deserializers.keys().cloned().collect()
}
}
/// Deserializes an account to a JSON string.
///
/// In order to specify a custom [Write] writer, i.e. a socket connection to write to, use
/// [deserialize_account] instead.
///
/// - [program_id] is the program id of progra that owns the account.
/// make sure to add it's IDL before via [ChainsawDeserializer::add_idl_json].
/// - [account_data] is the raw account data as a byte array
#[cfg(feature = "json")]
pub fn deserialize_account_to_json_string(
&self,
program_id: &str,
account_data: &mut &[u8],
) -> ChainsawResult<String> {
let mut f = String::new();
self.deserialize_account_to_json(program_id, account_data, &mut f)?;
Ok(f)
}
/// Deserializes an account and writes the resulting JSON to the provided [Write] write [f].
///
/// - [program_id] is the program id of progra that owns the account.
/// make sure to add it's IDL before via [ChainsawDeserializer::add_idl_json].
/// - [account_data] is the raw account data as a byte array
/// - [f] is the [Write] writer to write the resulting JSON to, i.e. `std::io::stdout()` or
/// `String::new()`
#[cfg(feature = "json")]
pub fn deserialize_account_to_json<W: Write>(
&self,
program_id: &str,
account_data: &mut &[u8],
f: &mut W,
) -> ChainsawResult<()> {
let deserializer = self
.json_account_deserializers
.get(program_id)
.ok_or_else(|| {
ChainsawError::CannotFindAccountDeserializerForProgramId(
program_id.to_string(),
)
})?;
deserializer.deserialize_account_data(account_data, f)?;
Ok(())
}
/// Deserializes an account and writes it to raw bson which it returns.
///
/// - [program_id] is the program id of progra that owns the account.
/// make sure to add it's IDL before via [ChainsawDeserializer::add_idl_json].
/// - [account_data] is the raw account data as a byte array
#[cfg(feature = "bson")]
pub fn deserialize_account_to_bson(
&self,
program_id: &str,
account_data: &mut &[u8],
) -> ChainsawResult<RawBson> {
let deserializer = self
.bson_account_deserializers
.get(program_id)
.ok_or_else(|| {
ChainsawError::CannotFindAccountDeserializerForProgramId(
program_id.to_string(),
)
})?;
deserializer.deserialize_account_data(account_data)
}
}