ic_web3_rs/transforms/
transform.rs

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}