accumulate_client/
json_rpc_client.rs1#![allow(missing_docs)]
4
5use reqwest::Client;
6use serde::de::DeserializeOwned;
7use serde_json::Value;
8use std::time::Duration;
9use thiserror::Error;
10use url::Url;
11
12#[derive(Error, Debug)]
13pub enum JsonRpcError {
14 #[error("HTTP request failed: {0}")]
15 Http(#[from] reqwest::Error),
16
17 #[error("JSON serialization/deserialization failed: {0}")]
18 Json(#[from] serde_json::Error),
19
20 #[error("JSON-RPC error: code={code}, message={message}")]
21 Rpc { code: i32, message: String },
22
23 #[error("Invalid URL: {0}")]
24 InvalidUrl(#[from] url::ParseError),
25
26 #[error("General error: {0}")]
27 General(#[from] anyhow::Error),
28}
29
30#[derive(Debug, Clone)]
32pub struct JsonRpcClient {
33 pub base_url: Url,
34 pub http: Client,
35}
36
37impl JsonRpcClient {
38 pub fn new(base_url: Url) -> Result<Self, JsonRpcError> {
40 let http = Client::builder().timeout(Duration::from_secs(30)).build()?;
41
42 Ok(Self { base_url, http })
43 }
44
45 pub fn with_client(base_url: Url, http: Client) -> Result<Self, JsonRpcError> {
47 Ok(Self { base_url, http })
48 }
49
50 pub async fn call<T: DeserializeOwned>(
52 &self,
53 method: &str,
54 params: Value,
55 ) -> Result<T, JsonRpcError> {
56 let request_body = serde_json::json!({
57 "jsonrpc": "2.0",
58 "id": 1,
59 "method": method,
60 "params": params
61 });
62
63 let response = self
64 .http
65 .post(self.base_url.clone())
66 .json(&request_body)
67 .send()
68 .await?;
69
70 let response_text = response.text().await?;
71 let response_json: Value = serde_json::from_str(&response_text)?;
72
73 if let Some(error) = response_json.get("error") {
74 let code = error.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) as i32;
75 let message = error
76 .get("message")
77 .and_then(|m| m.as_str())
78 .unwrap_or("Unknown error")
79 .to_string();
80 return Err(JsonRpcError::Rpc { code, message });
81 }
82
83 let result = response_json.get("result").cloned().unwrap_or(Value::Null);
84 Ok(serde_json::from_value(result)?)
85 }
86
87 pub async fn call_v2<T: DeserializeOwned>(
89 &self,
90 method_path: &str,
91 payload: Option<Value>,
92 ) -> Result<T, JsonRpcError> {
93 let url = self.base_url.join(method_path)?;
94
95 let request = match payload {
96 Some(body) => self.http.post(url).json(&body),
97 None => self.http.get(url),
98 };
99
100 let response = request.send().await?;
101 let response_text = response.text().await?;
102
103 Ok(serde_json::from_str(&response_text)?)
104 }
105
106 pub async fn call_v3<T: DeserializeOwned>(
108 &self,
109 method: &str,
110 params: Value,
111 ) -> Result<T, JsonRpcError> {
112 self.call(method, params).await
113 }
114}
115
116pub fn canonical_json(value: &Value) -> String {
118 crate::canonjson::canonicalize(value)
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use serde_json::json;
125
126 #[test]
127 fn test_canonical_json() {
128 let value = json!({
129 "b": 2,
130 "a": 1,
131 "c": {
132 "z": 3,
133 "y": 4
134 }
135 });
136
137 let canonical = canonical_json(&value);
138 assert!(canonical.contains(r#""a":1"#));
139 assert!(canonical.contains(r#""b":2"#));
140
141 let a_pos = canonical.find(r#""a":"#).unwrap();
143 let b_pos = canonical.find(r#""b":"#).unwrap();
144 assert!(a_pos < b_pos);
145 }
146}