postchain_client/transport/
client.rs1extern crate serde_json;
10extern crate url;
11
12use reqwest::{header::CONTENT_TYPE, Client};
13use url::Url;
14
15use serde_json::Value;
16use std::{error::Error, time::Duration};
17
18use crate::utils::transaction::{Transaction, TransactionStatus};
19
20#[derive(Debug)]
28pub struct RestClient<'a> {
29 pub node_url: Vec<&'a str>,
31 pub request_time_out: u64,
33 pub poll_attemps: u64,
35 pub poll_attemp_interval_time: u64
37}
38
39#[derive(Debug)]
41pub enum RestResponse {
42 String(String),
44 Json(Value),
46 Bytes(Vec<u8>),
48}
49
50#[derive(PartialEq, Eq, Clone, Copy)]
52pub enum RestRequestMethod {
53 GET,
55 POST,
57}
58
59impl<'a> Default for RestClient<'a> {
60 fn default() -> Self {
61 return RestClient {
62 node_url: vec!["http://localhost:7740"],
63 request_time_out: 30,
64 poll_attemps: 5,
65 poll_attemp_interval_time: 5
66 };
67 }
68}
69
70#[derive(Debug)]
72pub enum TypeError {
73 FromReqClient,
75 FromRestApi,
77}
78
79#[derive(Debug)]
81pub struct RestError {
82 pub status_code: Option<String>,
84 pub error_str: Option<String>,
86 pub error_json: Option<Value>,
88 pub type_error: TypeError,
90}
91
92impl Error for RestError {}
93
94impl Default for RestError {
95 fn default() -> Self {
96 return RestError {
97 status_code: None,
98 error_str: None,
99 error_json: None,
100 type_error: TypeError::FromRestApi,
101 };
102 }
103}
104
105impl std::fmt::Display for RestError {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 let mut hsc = "N/A".to_string();
108 let mut err_str = "N/A".to_string();
109
110 if let Some(val) = &self.status_code {
111 hsc = val.clone();
112 }
113
114 if let Some(val) = &self.error_str {
115 err_str = val.clone();
116 }
117
118 write!(f, "{:?} {} {}", self.type_error, hsc, err_str)
119 }
120}
121
122impl<'a> RestClient<'a> {
123 pub async fn get_nodes_from_directory(&self, brid: &str) -> Result<Vec<String>, RestError> {
140 let directory_brid = self.get_blockchain_rid(0).await?;
141
142 let path_segments = &["query", &directory_brid];
143 let mut query_params = vec![
144 ("type", "cm_get_blockchain_api_urls"),
145 ("blockchain_rid", brid),
146 ];
147 let query_body_json = None;
148 let query_body_raw = None;
149
150 let resp = self
151 .postchain_rest_api(
152 RestRequestMethod::GET,
153 Some(path_segments),
154 Some(&mut query_params),
155 query_body_json,
156 query_body_raw
157 )
158 .await;
159
160 match resp {
161 Ok(val) => match val {
162 RestResponse::Json(json_val) => {
163 let list_of_nodes = json_val
164 .as_array()
165 .unwrap()
166 .iter()
167 .filter_map(|value| value.as_str().map(String::from))
168 .collect();
169 Ok(list_of_nodes)
170 }
171 RestResponse::String(str_val) => Ok(vec![str_val]),
172 _ => Ok(vec!["nop".to_string()]),
173 },
174 Err(error) => {
175 tracing::error!("Can't get API urls from DC chain: {} because of error: {:?}", brid, error);
176 Err(error)
177 }
178 }
179 }
180
181 pub async fn get_blockchain_rid(&self, blockchain_iid: u8) -> Result<String, RestError> {
189 let resp: Result<RestResponse, RestError> = self
190 .postchain_rest_api(
191 RestRequestMethod::GET,
192 Some(&[&format!("/brid/iid_{}", blockchain_iid)]),
193 None,
194 None,
195 None
196 )
197 .await;
198
199 if let Err(error) = resp {
200 tracing::error!("Can't get blockchain RID with IID = {} because of error: {:?}", blockchain_iid, error);
201 return Err(error);
202 }
203
204 let resp_val: RestResponse = resp.unwrap();
205
206 match resp_val {
207 RestResponse::String(val) => Ok(val.to_string()),
208 _ => Ok("".to_string()),
209 }
210 }
211
212 pub fn print_error(&self, error: &RestError, ignore_all_errors: bool) -> bool {
221 println!(">> Error(s)");
222
223 if let Some(error_str) = &error.error_str {
224 println!("{}", error_str);
225 } else {
226 let val = &error.error_json.as_ref().unwrap();
227 let pprint = serde_json::to_string_pretty(val).unwrap();
228 println!("{}", pprint);
229 }
230
231 if ignore_all_errors {
232 println!("Allow ignore this error");
233 return false
234 }
235
236 true
237 }
238
239 pub fn update_node_urls(&mut self, node_urls: &'a Vec<String>) {
244 self.node_url = node_urls.iter().map(String::as_str).collect();
245 }
246
247 pub async fn get_transaction_status(&self, blockchain_rid: &str, tx_rid: &str) -> Result<TransactionStatus, RestError> {
258 self.get_transaction_status_with_poll(blockchain_rid, tx_rid, 0).await
259 }
260
261 pub async fn get_transaction_status_with_poll(&self, blockchain_rid: &str, tx_rid: &str, attempts: u64) -> Result<TransactionStatus, RestError> {
271 tracing::info!("Waiting for transaction status of blockchain RID: {} with tx: {} | attempt: {}", blockchain_rid, tx_rid, attempts);
272
273 if attempts >= self.poll_attemps {
274 tracing::warn!("Transaction status still in waiting status after {} attempts", attempts);
275 return Ok(TransactionStatus::WAITING);
276 }
277
278 let resp = self.postchain_rest_api(RestRequestMethod::GET,
279 Some(&["tx", blockchain_rid, tx_rid, "status"]),
280 None,
281 None,
282 None).await?;
283 match resp {
284 RestResponse::Json(val) => {
285 let status: serde_json::Map<String, Value> = serde_json::from_value(val).unwrap();
286 if let Some(status_value) = status.get("status") {
287 let status_value = status_value.as_str();
288 let status_code = match status_value {
289 Some("waiting") => {
290 tokio::time::sleep(Duration::from_secs(self.poll_attemp_interval_time)).await;
294 return Box::pin(self.get_transaction_status_with_poll(blockchain_rid, tx_rid, attempts + 1)).await;
295 },
296 Some("confirmed") => {
297 tracing::info!("Transaction confirmed!");
298 Ok(TransactionStatus::CONFIRMED)
299 },
300 Some("rejected") => {
301 tracing::warn!("Transaction rejected!");
302 Ok(TransactionStatus::REJECTED)
303 },
304 _ => Ok(TransactionStatus::UNKNOWN)
305 };
306 return status_code
307 }
308 Ok(TransactionStatus::UNKNOWN)
309 }
310 _ => {
311 Ok(TransactionStatus::UNKNOWN)
312 }
313 }
314 }
315
316 pub async fn send_transaction(&self, tx: &Transaction<'a>) -> Result<RestResponse, RestError> {
326 let txe = tx.gvt_hex_encoded();
327
328 let resq_body: serde_json::Map<String, Value> =
329 vec![("tx".to_string(), serde_json::json!(txe))]
330 .into_iter()
331 .collect();
332
333 let blockchain_rid = hex::encode(tx.blockchain_rid.clone()).as_str().to_owned();
334
335 tracing::info!("Sending transaction to {}", blockchain_rid);
336
337 self
338 .postchain_rest_api(
339 RestRequestMethod::POST,
340 Some(&["tx", &blockchain_rid]),
341 None,
342 Some(serde_json::json!(resq_body)),
343 None
344 )
345 .await
346 }
347
348 pub async fn query<T: AsRef<str>>(
362 &self,
363 brid: &str,
364 query_prefix: Option<&str>,
365 query_type: &'a str,
366 query_params: Option<&'a mut Vec<(&'a str, &'a str)>>,
367 query_args: Option<&'a mut Vec<(T, crate::utils::operation::Params)>>,
368 ) -> Result<RestResponse, RestError> {
369 let mut query_prefix_str = "query_gtv";
370
371 if let Some(val) = query_prefix {
372 query_prefix_str = val;
373 }
374
375 let mut query_args_converted: Option<Vec<(&str, crate::utils::operation::Params)>> = query_args.map(|args| {
376 args.iter()
377 .map(|(key, params)| (key.as_ref(), params.clone()))
378 .collect()
379 });
380
381 let encode_str = crate::encoding::gtv::encode(query_type, query_args_converted.as_mut().map(|v| v.as_mut()));
382
383 tracing::info!("Querying {} to {}", query_type, brid);
384
385 self.postchain_rest_api(
386 RestRequestMethod::POST,
387 Some(&[query_prefix_str, brid]),
388 query_params.as_deref(),
389 None,
390 Some(encode_str)
391 ).await
392 }
393
394 async fn postchain_rest_api(
406 &self,
407 method: RestRequestMethod,
408 path_segments: Option<&[&str]>,
409 query_params: Option<&'a Vec<(&'a str, &'a str)>>,
410 query_body_json: Option<Value>,
411 query_body_raw: Option<Vec<u8>>
412 ) -> Result<RestResponse, RestError> {
413 let mut node_index: usize = 0;
414 loop {
415 let result = self.postchain_rest_api_with_poll(method,
416 path_segments, query_params,
417 query_body_json.clone(), query_body_raw.clone(), node_index).await;
418
419 if let Err(ref error) = result {
420 node_index += 1;
421
422 if node_index < self.node_url.len() && error.status_code.is_none() {
423 tracing::info!("The API endpoint can't be reached; will try another one!");
424 continue;
425 }
426 }
427 return result;
428 }
429 }
430
431 async fn postchain_rest_api_with_poll(
444 &self,
445 method: RestRequestMethod,
446 path_segments: Option<&[&str]>,
447 query_params: Option<&'a Vec<(&'a str, &'a str)>>,
448 query_body_json: Option<Value>,
449 query_body_raw: Option<Vec<u8>>,
450 node_index: usize,
451 ) -> Result<RestResponse, RestError> {
452
453 let mut url = Url::parse(&self.node_url[node_index]).unwrap();
454
455 tracing::info!("Requesting on API endpoint: {}", url);
456
457 if let Some(ps) = path_segments {
458 if !ps.is_empty() {
459 let psj = ps.join("/");
460 url.set_path(&psj);
461 }
462 }
463
464 if let Some(qp) = query_params {
465 if !qp.is_empty() {
466 for (name, value) in qp {
467 url.query_pairs_mut().append_pair(name, value);
468 }
469 }
470 }
471
472 if method == RestRequestMethod::POST
473 && query_body_json.is_none()
474 && query_body_raw.is_none()
475 {
476 let error_str = "Error: POST request need a body [json or binary].".to_string();
477
478 tracing::error!(error_str);
479
480 return Err(RestError {
481 type_error: TypeError::FromRestApi,
482 error_str: Some(error_str),
483 status_code: None,
484 ..Default::default()
485 });
486 }
487
488 let rest_client = Client::new();
489
490 let req_result = match method {
491 RestRequestMethod::GET => {
492 rest_client
493 .get(url.clone())
494 .timeout(Duration::from_secs(self.request_time_out))
495 .send()
496 .await
497 }
498
499 RestRequestMethod::POST => {
500 if let Some(qb) = query_body_json {
501 rest_client
502 .post(url.clone())
503 .timeout(Duration::from_secs(self.request_time_out))
504 .json(&qb)
505 .send()
506 .await
507 } else {
508 let r_body = reqwest::Body::from(query_body_raw.unwrap());
509 rest_client
510 .post(url.clone())
511 .timeout(Duration::from_secs(self.request_time_out))
512 .body(r_body)
513 .send()
514 .await
515 }
516 }
517 };
518
519 let req_result_match = match req_result {
520 Ok(resp) => {
521 let http_status_code = resp.status().to_string();
522 let http_resp_header = resp.headers().get(CONTENT_TYPE).unwrap().to_str().unwrap();
523 let json_resp = http_resp_header.contains("application/json");
524 let octet_stream_resp = http_resp_header.contains("application/octet-stream");
525
526 if http_status_code.starts_with('4') || http_status_code.starts_with('5') {
527 let mut err = RestError {
528 status_code: Some(http_status_code),
529 type_error: TypeError::FromRestApi,
530 ..Default::default()
531 };
532
533 if json_resp {
534 let error_json = resp.json().await.unwrap();
535 err.error_json = Some(error_json);
536 } else {
537 let error_str = resp.text().await.unwrap();
538 err.error_str = Some(error_str);
539 }
540
541 tracing::error!("{:?}", err);
542
543 return Err(err);
544 }
545
546 let rest_resp: RestResponse;
547
548 if json_resp {
549 let val = resp.json().await.unwrap();
550 rest_resp = RestResponse::Json(val);
551 } else if octet_stream_resp {
552 let bytes = resp.bytes().await.unwrap();
553 rest_resp = RestResponse::Bytes(bytes.to_vec());
554 } else {
555 let val = resp.text().await.unwrap();
556 rest_resp = RestResponse::String(val);
557 }
558
559 Ok(rest_resp)
560 }
561 Err(error) => {
562 let rest_error = RestError {
563 error_str: Some(error.to_string()),
564 type_error: TypeError::FromReqClient,
565 ..Default::default()};
566
567 tracing::error!("{:?}", rest_error);
568
569 Err(rest_error)
570 },
571 };
572
573 req_result_match
574 }
575}