zebra_node_services/
rpc_client.rs

1//! A client for calling Zebra's JSON-RPC methods.
2//!
3//! Used in the rpc sync scanning functionality and in various tests and tools.
4
5use std::net::SocketAddr;
6
7use reqwest::Client;
8
9use crate::BoxError;
10
11/// An HTTP client for making JSON-RPC requests.
12#[derive(Clone, Debug)]
13pub struct RpcRequestClient {
14    client: Client,
15    rpc_address: SocketAddr,
16}
17
18impl RpcRequestClient {
19    /// Creates new RPCRequestSender
20    pub fn new(rpc_address: SocketAddr) -> Self {
21        Self {
22            client: Client::new(),
23            rpc_address,
24        }
25    }
26
27    /// Builds rpc request
28    pub async fn call(
29        &self,
30        method: impl AsRef<str>,
31        params: impl AsRef<str>,
32    ) -> reqwest::Result<reqwest::Response> {
33        let method = method.as_ref();
34        let params = params.as_ref();
35
36        self.client
37            .post(format!("http://{}", &self.rpc_address))
38            .body(format!(
39                r#"{{"jsonrpc": "2.0", "method": "{method}", "params": {params}, "id":123 }}"#
40            ))
41            .header("Content-Type", "application/json")
42            .send()
43            .await
44    }
45
46    /// Builds rpc request with a variable `content-type`.
47    pub async fn call_with_content_type(
48        &self,
49        method: impl AsRef<str>,
50        params: impl AsRef<str>,
51        content_type: String,
52    ) -> reqwest::Result<reqwest::Response> {
53        let method = method.as_ref();
54        let params = params.as_ref();
55
56        self.client
57            .post(format!("http://{}", &self.rpc_address))
58            .body(format!(
59                r#"{{"jsonrpc": "2.0", "method": "{method}", "params": {params}, "id":123 }}"#
60            ))
61            .header("Content-Type", content_type)
62            .send()
63            .await
64    }
65
66    /// Builds rpc request with no content type.
67    pub async fn call_with_no_content_type(
68        &self,
69        method: impl AsRef<str>,
70        params: impl AsRef<str>,
71    ) -> reqwest::Result<reqwest::Response> {
72        let method = method.as_ref();
73        let params = params.as_ref();
74
75        self.client
76            .post(format!("http://{}", &self.rpc_address))
77            .body(format!(
78                r#"{{"jsonrpc": "2.0", "method": "{method}", "params": {params}, "id":123 }}"#
79            ))
80            .send()
81            .await
82    }
83
84    /// Builds rpc request and gets text from response
85    pub async fn text_from_call(
86        &self,
87        method: impl AsRef<str>,
88        params: impl AsRef<str>,
89    ) -> reqwest::Result<String> {
90        self.call(method, params).await?.text().await
91    }
92
93    /// Builds an RPC request, awaits its response, and attempts to deserialize
94    /// it to the expected result type.
95    ///
96    /// Returns Ok with json result from response if successful.
97    /// Returns an error if the call or result deserialization fail.
98    pub async fn json_result_from_call<T: serde::de::DeserializeOwned>(
99        &self,
100        method: impl AsRef<str>,
101        params: impl AsRef<str>,
102    ) -> std::result::Result<T, BoxError> {
103        Self::json_result_from_response_text(&self.text_from_call(method, params).await?)
104    }
105
106    /// Accepts response text from an RPC call
107    /// Returns `Ok` with a deserialized `result` value in the expected type, or an error report.
108    fn json_result_from_response_text<T: serde::de::DeserializeOwned>(
109        response_text: &str,
110    ) -> std::result::Result<T, BoxError> {
111        let output: jsonrpsee_types::Response<serde_json::Value> =
112            serde_json::from_str(response_text)?;
113        match output.payload {
114            jsonrpsee_types::ResponsePayload::Success(success) => {
115                Ok(serde_json::from_value(success.into_owned())?)
116            }
117            jsonrpsee_types::ResponsePayload::Error(failure) => Err(failure.to_string().into()),
118        }
119    }
120}