tiny_jsonrpc/
client.rs

1use std::borrow::Cow;
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::sync::Arc;
4use std::time::Duration;
5
6use reqwest::{Client as ReqwestClient, Url};
7use serde::{de::DeserializeOwned, Deserialize, Serialize};
8
9use crate::error::{Error, JsonRpcError};
10
11const JSON_RPC_VERSION: &str = "2.0";
12const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
13const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_secs(10);
14
15#[derive(Clone, Debug)]
16pub struct Client {
17    inner: ReqwestClient,
18    url: Url,
19    id: Arc<AtomicU64>,
20}
21
22impl Client {
23    pub fn new(url: Url, timeout: Option<Duration>, connection_timeout: Option<Duration>) -> Self {
24        let client = ReqwestClient::builder()
25            .connect_timeout(connection_timeout.unwrap_or(DEFAULT_CONNECTION_TIMEOUT))
26            .timeout(timeout.unwrap_or(DEFAULT_TIMEOUT))
27            .build()
28            .expect("Failed to build HTTP client");
29
30        Self {
31            inner: client,
32            url,
33            id: Arc::new(AtomicU64::new(0)),
34        }
35    }
36
37    pub async fn request<M, P, R>(&self, method: M, params: P) -> Result<R, Error>
38    where
39        M: AsRef<str> + Send,
40        P: Serialize,
41        R: DeserializeOwned,
42    {
43        let id = self.id.fetch_add(1, Ordering::SeqCst);
44
45        let payload = serde_json::json!({
46            "jsonrpc": JSON_RPC_VERSION,
47            "method": method.as_ref(),
48            "params": params,
49            "id": id
50        });
51
52        let response = self
53            .inner
54            .post(self.url.clone())
55            .json(&payload)
56            .send()
57            .await?
58            .json()
59            .await?;
60
61        Self::parse_response(response)
62    }
63
64    pub fn parse_response<R: DeserializeOwned>(response: serde_json::Value) -> Result<R, Error> {
65        let parsed: JsonRpcResponse = serde_json::from_value(response)?;
66
67        match parsed.result {
68            JsonRpcAnswer::Result(d) => serde_json::from_value(d).map_err(Error::Serialization),
69            JsonRpcAnswer::Error(e) => Err(Error::JsonRpc(e)),
70        }
71    }
72}
73
74impl Default for Client {
75    fn default() -> Self {
76        Self::new(
77            Url::parse("http://localhost:8545").expect("Invalid default URL"),
78            None,
79            None,
80        )
81    }
82}
83
84#[derive(Debug, Clone, PartialEq)]
85/// A JSON-RPC response.
86pub struct JsonRpcResponse {
87    /// Request content.
88    pub result: JsonRpcAnswer,
89    /// The request ID.
90    pub id: Id,
91}
92
93/// An identifier established by the Client that MUST contain a String, Number,
94/// or NULL value if included. If it is not included it is assumed to be a notification.
95/// The value SHOULD normally not be Null and Numbers SHOULD NOT contain fractional parts
96#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]
97#[serde(untagged)]
98pub enum Id {
99    Num(i64),
100    Str(String),
101    None(()),
102}
103
104#[derive(Serialize, Clone, Debug, Deserialize, PartialEq)]
105#[serde(rename_all = "lowercase")]
106/// `JsonRpc` [response object](https://www.jsonrpc.org/specification#response_object)
107pub enum JsonRpcAnswer {
108    Result(serde_json::Value),
109    Error(JsonRpcError),
110}
111
112impl<'de> Deserialize<'de> for JsonRpcResponse {
113    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
114    where
115        D: serde::Deserializer<'de>,
116    {
117        use serde::de::Error;
118
119        #[derive(Deserialize)]
120        struct Helper<'a> {
121            #[serde(borrow)]
122            jsonrpc: Cow<'a, str>,
123            #[serde(flatten)]
124            result: JsonRpcAnswer,
125            id: Id,
126        }
127
128        let helper = Helper::deserialize(deserializer)?;
129        if helper.jsonrpc == JSON_RPC_VERSION {
130            Ok(Self {
131                result: helper.result,
132                id: helper.id,
133            })
134        } else {
135            Err(D::Error::custom("Unknown jsonrpc version"))
136        }
137    }
138}