1use std::sync::atomic::{AtomicU64, Ordering};
2use std::time::Duration;
3
4use reqwest::Client;
5use serde::Serialize;
6
7use crate::Error;
8
9#[derive(Serialize)]
10struct Request<'a> {
11 jsonrpc: &'static str,
12 method: &'a str,
13 params: serde_json::Value,
14 id: u64,
15}
16
17#[derive(serde::Deserialize)]
18struct RpcError {
19 code: i64,
20 message: String,
21}
22
23pub struct RpcTransport {
25 client: Client,
26 url: String,
27 timeout: Duration,
28 next_id: AtomicU64,
29}
30
31impl RpcTransport {
32 pub fn url(&self) -> &str {
34 &self.url
35 }
36
37 pub fn timeout(&self) -> Duration {
39 self.timeout
40 }
41
42 pub fn new(url: String, timeout: Duration) -> Self {
43 let client = Client::builder()
44 .timeout(timeout)
45 .build()
46 .unwrap_or_else(|_| Client::new());
47 Self {
48 client,
49 url,
50 timeout,
51 next_id: AtomicU64::new(1),
52 }
53 }
54
55 pub async fn call<T: serde::de::DeserializeOwned>(
56 &self,
57 method: &str,
58 params: serde_json::Value,
59 ) -> Result<T, Error> {
60 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
61 let request = Request {
62 jsonrpc: "2.0",
63 method,
64 params,
65 id,
66 };
67
68 let resp = self.client.post(&self.url).json(&request).send().await?;
69 let body: serde_json::Value = resp.json().await?;
70 let error: Option<RpcError> = body
71 .get("error")
72 .filter(|value| !value.is_null())
73 .cloned()
74 .map(serde_json::from_value)
75 .transpose()?;
76
77 if let Some(err) = error {
78 return Err(Error::Rpc {
79 code: err.code,
80 message: err.message,
81 });
82 }
83
84 let result = body.get("result").cloned().ok_or_else(|| {
85 Error::InvalidData(format!(
86 "JSON-RPC response for method '{method}' missing both result and error"
87 ))
88 })?;
89
90 serde_json::from_value(result).map_err(Into::into)
91 }
92
93 pub async fn call_optional<T: serde::de::DeserializeOwned>(
98 &self,
99 method: &str,
100 params: serde_json::Value,
101 ) -> Result<Option<T>, Error> {
102 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
103 let request = Request {
104 jsonrpc: "2.0",
105 method,
106 params,
107 id,
108 };
109
110 let resp = self.client.post(&self.url).json(&request).send().await?;
111 let body: serde_json::Value = resp.json().await?;
112 let error: Option<RpcError> = body
113 .get("error")
114 .filter(|value| !value.is_null())
115 .cloned()
116 .map(serde_json::from_value)
117 .transpose()?;
118
119 if let Some(err) = error {
120 return Err(Error::Rpc {
121 code: err.code,
122 message: err.message,
123 });
124 }
125
126 let Some(result) = body.get("result").cloned() else {
127 return Ok(None);
128 };
129
130 if result.is_null() {
131 return Ok(None);
132 }
133
134 serde_json::from_value(result).map(Some).map_err(Into::into)
135 }
136
137 pub async fn call_void(&self, method: &str, params: serde_json::Value) -> Result<(), Error> {
142 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
143 let request = Request {
144 jsonrpc: "2.0",
145 method,
146 params,
147 id,
148 };
149
150 let resp = self.client.post(&self.url).json(&request).send().await?;
151 let body: serde_json::Value = resp.json().await?;
152 let error: Option<RpcError> = body
153 .get("error")
154 .filter(|value| !value.is_null())
155 .cloned()
156 .map(serde_json::from_value)
157 .transpose()?;
158
159 if let Some(err) = error {
160 return Err(Error::Rpc {
161 code: err.code,
162 message: err.message,
163 });
164 }
165
166 Ok(())
167 }
168}
169
170#[cfg(test)]
171#[allow(clippy::unwrap_used)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn request_envelope_serializes() {
177 let req = Request {
178 jsonrpc: "2.0",
179 method: "test_method",
180 params: serde_json::json!([1, "two"]),
181 id: 42,
182 };
183 let json = serde_json::to_value(&req).unwrap();
184 assert_eq!(json["jsonrpc"], "2.0");
185 assert_eq!(json["method"], "test_method");
186 assert_eq!(json["id"], 42);
187 assert_eq!(json["params"][0], 1);
188 assert_eq!(json["params"][1], "two");
189 }
190
191 #[test]
192 fn response_with_result_parses() {
193 let body: serde_json::Value =
194 serde_json::from_str(r#"{"jsonrpc":"2.0","result":42,"id":1}"#).unwrap();
195 assert_eq!(body["result"], serde_json::json!(42));
196 assert!(body.get("error").is_none());
197 }
198
199 #[test]
200 fn response_with_error_parses() {
201 let body: serde_json::Value = serde_json::from_str(
202 r#"{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":1}"#,
203 )
204 .unwrap();
205 assert!(body.get("result").is_none());
206 let err: RpcError = serde_json::from_value(body["error"].clone()).unwrap();
207 assert_eq!(err.code, -32600);
208 assert_eq!(err.message, "Invalid Request");
209 }
210
211 #[test]
212 fn response_with_null_result_parses() {
213 let body: serde_json::Value =
214 serde_json::from_str(r#"{"jsonrpc":"2.0","result":null,"id":1}"#).unwrap();
215 assert!(body.get("result").is_some());
216 assert!(body["result"].is_null());
217 assert!(body.get("error").is_none());
218 }
219}