1use std::sync::atomic::{AtomicU64, Ordering};
2use std::time::Duration;
3
4use reqwest::Client;
5use serde::{Deserialize, 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(Deserialize)]
18struct Response {
19 #[serde(default)]
20 result: Option<serde_json::Value>,
21 #[serde(default)]
22 error: Option<RpcError>,
23 #[allow(dead_code)]
24 id: u64,
25}
26
27#[derive(Deserialize)]
28struct RpcError {
29 code: i64,
30 message: String,
31}
32
33pub struct RpcTransport {
35 client: Client,
36 url: String,
37 next_id: AtomicU64,
38}
39
40impl RpcTransport {
41 pub fn new(url: String, timeout: Duration) -> Self {
42 let client = Client::builder()
43 .timeout(timeout)
44 .build()
45 .unwrap_or_else(|_| Client::new());
46 Self {
47 client,
48 url,
49 next_id: AtomicU64::new(1),
50 }
51 }
52
53 pub async fn call<T: serde::de::DeserializeOwned>(
54 &self,
55 method: &str,
56 params: serde_json::Value,
57 ) -> Result<T, Error> {
58 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
59 let request = Request {
60 jsonrpc: "2.0",
61 method,
62 params,
63 id,
64 };
65
66 let resp = self.client.post(&self.url).json(&request).send().await?;
67 let body: Response = resp.json().await?;
68
69 if let Some(err) = body.error {
70 return Err(Error::Rpc {
71 code: err.code,
72 message: err.message,
73 });
74 }
75
76 let result = body.result.ok_or_else(|| {
77 Error::InvalidData("JSON-RPC response missing both result and error".into())
78 })?;
79
80 serde_json::from_value(result).map_err(Into::into)
81 }
82
83 pub async fn call_void(&self, method: &str, params: serde_json::Value) -> Result<(), Error> {
88 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
89 let request = Request {
90 jsonrpc: "2.0",
91 method,
92 params,
93 id,
94 };
95
96 let resp = self.client.post(&self.url).json(&request).send().await?;
97 let body: Response = resp.json().await?;
98
99 if let Some(err) = body.error {
100 return Err(Error::Rpc {
101 code: err.code,
102 message: err.message,
103 });
104 }
105
106 Ok(())
107 }
108}
109
110#[cfg(test)]
111#[allow(clippy::unwrap_used)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn request_envelope_serializes() {
117 let req = Request {
118 jsonrpc: "2.0",
119 method: "test_method",
120 params: serde_json::json!([1, "two"]),
121 id: 42,
122 };
123 let json = serde_json::to_value(&req).unwrap();
124 assert_eq!(json["jsonrpc"], "2.0");
125 assert_eq!(json["method"], "test_method");
126 assert_eq!(json["id"], 42);
127 assert_eq!(json["params"][0], 1);
128 assert_eq!(json["params"][1], "two");
129 }
130
131 #[test]
132 fn response_with_result_parses() {
133 let json = r#"{"jsonrpc":"2.0","result":42,"id":1}"#;
134 let resp: Response = serde_json::from_str(json).unwrap();
135 assert_eq!(resp.result.unwrap(), serde_json::json!(42));
136 assert!(resp.error.is_none());
137 }
138
139 #[test]
140 fn response_with_error_parses() {
141 let json =
142 r#"{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":1}"#;
143 let resp: Response = serde_json::from_str(json).unwrap();
144 assert!(resp.result.is_none());
145 let err = resp.error.unwrap();
146 assert_eq!(err.code, -32600);
147 assert_eq!(err.message, "Invalid Request");
148 }
149
150 #[test]
151 fn response_with_null_result_parses() {
152 let json = r#"{"jsonrpc":"2.0","result":null,"id":1}"#;
153 let resp: Response = serde_json::from_str(json).unwrap();
154 assert!(resp.result.is_none());
156 assert!(resp.error.is_none());
157 }
158}