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)]
85pub struct JsonRpcResponse {
87 pub result: JsonRpcAnswer,
89 pub id: Id,
91}
92
93#[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")]
106pub 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}