1use candid::Nat;
2use derive_builder::Builder;
3use ic_cdk::api::management_canister::http_request::{HttpResponse, TransformArgs};
4use serde_json::Value;
5
6#[derive(Debug, Builder, Default)]
7pub struct SingleResultTransformProcessor {
8 pub transaction_index: bool,
9}
10
11#[derive(Debug, Builder, Default)]
12pub struct ArrayResultTransformProcessor {
13 pub transaction_index: bool,
14 pub log_index: bool,
15}
16
17pub trait TransformProcessor {
18 fn transform(&self, raw: TransformArgs) -> HttpResponse {
19 let mut res = HttpResponse {
20 status: raw.response.status.clone(),
21 ..Default::default()
22 };
23 if res.status == Nat::from(200u8) {
24 res.body = self.process_body(&raw.response.body);
25 } else {
26 ic_cdk::api::print(format!("Received an error from blockchain: err = {:?}", raw));
27 }
28 res
29 }
30 fn process_body(&self, body: &[u8]) -> Vec<u8>;
31}
32
33impl TransformProcessor for ArrayResultTransformProcessor {
34 fn process_body(&self, body: &[u8]) -> Vec<u8> {
35 let mut body: Value = serde_json::from_slice(body).unwrap();
36 let elements = body.get_mut("result").unwrap().as_array_mut().unwrap();
37 for element in elements.iter_mut() {
38 if self.transaction_index {
39 element
40 .as_object_mut()
41 .unwrap()
42 .insert("transactionIndex".to_string(), Value::from("0x0"));
43 }
44 if self.log_index {
45 element
46 .as_object_mut()
47 .unwrap()
48 .insert("logIndex".to_string(), Value::from("0x0"));
49 }
50 }
51 serde_json::to_vec(&body).unwrap()
52 }
53}
54
55impl TransformProcessor for SingleResultTransformProcessor {
56 fn process_body(&self, body: &[u8]) -> Vec<u8> {
57 let mut body: Value = serde_json::from_slice(body).unwrap();
58 if self.transaction_index {
59 body.get_mut("result")
60 .unwrap()
61 .as_object_mut()
62 .unwrap()
63 .insert("transactionIndex".to_string(), Value::from("0x0"));
64 }
65 serde_json::to_vec(&body).unwrap()
66 }
67}
68
69#[cfg(test)]
70pub mod tests {
71 use std::{str::FromStr, vec};
72
73 use candid::Nat;
74 use ic_cdk::api::management_canister::http_request::{HttpResponse, TransformArgs};
75
76 use crate::transforms::transform::{
77 ArrayResultTransformProcessor, SingleResultTransformProcessor, TransformProcessor,
78 };
79
80 #[test]
81 fn test_single_transform() {
82 struct Cases {
83 input: String,
84 want: String,
85 }
86
87 let cases = vec![Cases {
88 input: r#"{
89 "id":1,
90 "jsonrpc":"2.0",
91 "result": {
92 "transactionHash": "0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238",
93 "transactionIndex": "0x10",
94 "blockNumber": "0xb",
95 "blockHash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b",
96 "cumulativeGasUsed": "0x33bc",
97 "gasUsed": "0x4dc",
98 "contractAddress": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
99 "logs": [],
100 "logsBloom": "0x00...0",
101 "status": "0x1"
102 }
103 }"#
104 .to_string(),
105 want: r#"{
106 "id":1,
107 "jsonrpc":"2.0",
108 "result": {
109 "transactionHash": "0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238",
110 "transactionIndex": "0x0",
111 "blockNumber": "0xb",
112 "blockHash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b",
113 "cumulativeGasUsed": "0x33bc",
114 "gasUsed": "0x4dc",
115 "contractAddress": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
116 "logs": [],
117 "logsBloom": "0x00...0",
118 "status": "0x1"
119 }
120 }"#
121 .to_string(),
122 }];
123 for case in cases {
124 let response = HttpResponse {
125 status: Nat::from_str("200".to_string().as_str()).unwrap(),
126 headers: Vec::default(),
127 body: case.input.into_bytes(),
128 };
129 let args = TransformArgs {
130 response: response,
131 context: vec![],
132 };
133 let got = SingleResultTransformProcessor {
134 transaction_index: true,
135 }
136 .transform(args);
137 let want_json = serde_json::from_str::<serde_json::Value>(&case.want).unwrap();
138 let got_json = serde_json::from_slice::<serde_json::Value>(&got.body).unwrap();
139 assert_eq!(got_json, want_json);
140 }
141 }
142
143 #[test]
144 fn test_array_transform() {
145 struct Cases {
146 input: String,
147 want: String,
148 }
149
150 let cases = vec![Cases {
151 input: r#"{
152 "id":1,
153 "jsonrpc":"2.0",
154 "result": [{
155 "logIndex": "0x11",
156 "blockNumber":"0x1b4",
157 "blockHash": "0x8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcfdf829c5a142f1fccd7d",
158 "transactionHash": "0xdf829c5a142f1fccd7d8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcf",
159 "transactionIndex": "0x12",
160 "address": "0x16c5785ac562ff41e2dcfdf829c5a142f1fccd7d",
161 "data":"0x0000000000000000000000000000000000000000000000000000000000000000",
162 "topics": ["0x59ebeb90bc63057b6515673c3ecf9438e5058bca0f92585014eced636878c9a5"]
163 },
164 {
165 "logIndex": "0x10",
166 "blockNumber":"0x1b4",
167 "blockHash": "0x8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcfdf829c5a142f1fccd7d",
168 "transactionHash": "0xdf829c5a142f1fccd7d8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcf",
169 "transactionIndex": "0x13",
170 "address": "0x16c5785ac562ff41e2dcfdf829c5a142f1fccd7d",
171 "data":"0x0000000000000000000000000000000000000000000000000000000000000000",
172 "topics": ["0x59ebeb90bc63057b6515673c3ecf9438e5058bca0f92585014eced636878c9a5"]
173 }
174 ]
175 }"#
176 .to_string(),
177 want: r#"{
178 "id":1,
179 "jsonrpc":"2.0",
180 "result": [{
181 "logIndex": "0x0",
182 "blockNumber":"0x1b4",
183 "blockHash": "0x8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcfdf829c5a142f1fccd7d",
184 "transactionHash": "0xdf829c5a142f1fccd7d8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcf",
185 "transactionIndex": "0x0",
186 "address": "0x16c5785ac562ff41e2dcfdf829c5a142f1fccd7d",
187 "data":"0x0000000000000000000000000000000000000000000000000000000000000000",
188 "topics": ["0x59ebeb90bc63057b6515673c3ecf9438e5058bca0f92585014eced636878c9a5"]
189 },{
190 "logIndex": "0x0",
191 "blockNumber":"0x1b4",
192 "blockHash": "0x8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcfdf829c5a142f1fccd7d",
193 "transactionHash": "0xdf829c5a142f1fccd7d8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcf",
194 "transactionIndex": "0x0",
195 "address": "0x16c5785ac562ff41e2dcfdf829c5a142f1fccd7d",
196 "data":"0x0000000000000000000000000000000000000000000000000000000000000000",
197 "topics": ["0x59ebeb90bc63057b6515673c3ecf9438e5058bca0f92585014eced636878c9a5"]
198 }]
199 }"#
200 .to_string(),
201 }];
202 for case in cases {
203 let response = HttpResponse {
204 status: Nat::from_str("200".to_string().as_str()).unwrap(),
205 headers: Vec::default(),
206 body: case.input.into_bytes(),
207 };
208 let args = TransformArgs {
209 response: response,
210 context: vec![],
211 };
212 let got = ArrayResultTransformProcessor {
213 transaction_index: true,
214 log_index: true,
215 }
216 .transform(args);
217 let want_json = serde_json::from_str::<serde_json::Value>(&case.want).unwrap();
218 let got_json = serde_json::from_slice::<serde_json::Value>(&got.body).unwrap();
219 assert_eq!(got_json, want_json);
220 }
221 }
222}