xrpl/asynch/clients/json_rpc/
mod.rs1use alloc::{string::ToString, vec};
2use serde::Serialize;
3use serde_json::{Map, Value};
4
5use crate::models::results::XRPLResponse;
6use crate::XRPLSerdeJsonError;
7
8mod exceptions;
9pub use exceptions::XRPLJsonRpcException;
10
11use super::{client::XRPLClient, exceptions::XRPLClientResult};
12
13fn request_to_json_rpc(request: &impl Serialize) -> XRPLClientResult<Value> {
15 let mut json_rpc_request = Map::new();
16 let request_value = serde_json::to_value(request)?;
17 let mut request = request_value
18 .as_object()
19 .ok_or(XRPLSerdeJsonError::UnexpectedValueType {
20 expected: "Object".to_string(),
21 found: request_value.clone(),
22 })?
23 .clone();
24 if let Some(command) = request.remove("command") {
25 json_rpc_request.insert("method".to_string(), command);
26 json_rpc_request.insert(
27 "params".to_string(),
28 serde_json::Value::Array(vec![Value::Object(request)]),
29 );
30 }
31
32 Ok(Value::Object(json_rpc_request))
33}
34
35#[cfg(all(feature = "json-rpc", feature = "std"))]
36mod _std {
37 use crate::models::requests::XRPLRequest;
38 #[cfg(feature = "helpers")]
39 use crate::{asynch::clients::XRPLFaucet, models::requests::FundFaucet};
40 #[cfg(feature = "helpers")]
41 use alloc::string::ToString;
42
43 use super::*;
44 use reqwest::Client as HttpClient;
45 use url::Url;
46
47 pub struct AsyncJsonRpcClient {
48 url: Url,
49 client: HttpClient,
50 }
51
52 impl AsyncJsonRpcClient {
53 pub fn connect(url: Url) -> Self {
54 Self {
55 url,
56 client: HttpClient::new(),
57 }
58 }
59 }
60
61 impl XRPLClient for AsyncJsonRpcClient {
62 async fn request_impl<'a: 'b, 'b>(
63 &self,
64 request: XRPLRequest<'a>,
65 ) -> XRPLClientResult<XRPLResponse<'b>> {
66 let request_json_rpc = request_to_json_rpc(&request)?;
67 let response = self
68 .client
69 .post(self.url.as_ref())
70 .json(&request_json_rpc)
71 .send()
72 .await;
73 match response {
74 Ok(response) => match response.text().await {
75 Ok(response_text) => {
76 match serde_json::from_str::<XRPLResponse<'b>>(&response_text) {
77 Ok(parsed_response) => Ok(parsed_response),
78 Err(parse_error) => {
79 Err(XRPLSerdeJsonError::SerdeJsonError(parse_error).into())
80 }
81 }
82 }
83 Err(error) => Err(error.into()),
84 },
85 Err(error) => Err(error.into()),
86 }
87 }
88
89 fn get_host(&self) -> Url {
90 self.url.clone()
91 }
92 }
93
94 #[cfg(feature = "helpers")]
95 impl XRPLFaucet for AsyncJsonRpcClient {
96 async fn request_funding(
97 &self,
98 url: Option<Url>,
99 request: FundFaucet<'_>,
100 ) -> XRPLClientResult<()> {
101 let faucet_url = self.get_faucet_url(url)?;
102 let client = HttpClient::new();
103 let request_json_rpc = match serde_json::to_value(&request) {
104 Ok(value) => value,
105 Err(error) => return Err(XRPLSerdeJsonError::SerdeJsonError(error).into()),
106 };
107
108 let response = client
109 .post(faucet_url.to_string())
110 .json(&request_json_rpc)
111 .send()
112 .await;
113 match response {
114 Ok(response) => {
115 if response.status().is_success() {
116 Ok(())
117 } else {
118 Err(XRPLJsonRpcException::RequestError(format!(
120 "Faucet request failed with status: {}",
121 response.status()
122 ))
123 .into())
124 }
125 }
126 Err(error) => Err(error.into()),
127 }
128 }
129 }
130}
131
132#[cfg(all(feature = "json-rpc", not(feature = "std")))]
133mod _no_std {
134 use crate::{asynch::clients::SingleExecutorMutex, models::requests::XRPLRequest};
135 #[cfg(feature = "helpers")]
136 use crate::{asynch::clients::XRPLFaucet, models::requests::FundFaucet};
137
138 use super::*;
139 use alloc::sync::Arc;
140 use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex};
141 use embedded_nal_async::{Dns, TcpConnect};
142 use reqwless::{
143 client::{HttpClient, TlsConfig},
144 headers::ContentType,
145 request::{Method, RequestBuilder},
146 };
147 use url::Url;
148
149 pub struct AsyncJsonRpcClient<'a, const BUF: usize, T, D, M = SingleExecutorMutex>
150 where
151 M: RawMutex,
152 T: TcpConnect + 'a,
153 D: Dns + 'a,
154 {
155 url: Url,
156 client: Arc<Mutex<M, HttpClient<'a, T, D>>>,
157 }
158
159 impl<'a, const BUF: usize, T, D, M> AsyncJsonRpcClient<'a, BUF, T, D, M>
160 where
161 M: RawMutex,
162 T: TcpConnect + 'a,
163 D: Dns + 'a,
164 {
165 pub fn connect(url: Url, tcp: &'a T, dns: &'a D) -> Self {
166 Self {
167 url,
168 client: Arc::new(Mutex::new(HttpClient::new(tcp, dns))),
169 }
170 }
171
172 pub fn connect_with_tls(url: Url, tcp: &'a T, dns: &'a D, tls: TlsConfig<'a>) -> Self {
173 Self {
174 url,
175 client: Arc::new(Mutex::new(HttpClient::new_with_tls(tcp, dns, tls))),
176 }
177 }
178 }
179
180 impl<const BUF: usize, T, D, M> XRPLClient for AsyncJsonRpcClient<'_, BUF, T, D, M>
181 where
182 M: RawMutex,
183 T: TcpConnect,
184 D: Dns,
185 {
186 async fn request_impl<'a: 'b, 'b>(
187 &self,
188 request: XRPLRequest<'a>,
189 ) -> XRPLClientResult<XRPLResponse<'b>> {
190 let request_json_rpc = request_to_json_rpc(&request)?;
191 let request_string = request_json_rpc.to_string();
192 let request_buf = request_string.as_bytes();
193 let mut rx_buffer = [0; BUF];
194 let mut client = self.client.lock().await;
195 let response = match client.request(Method::POST, self.url.as_str()).await {
196 Ok(client) => {
197 if let Err(error) = client
198 .body(request_buf)
199 .content_type(ContentType::ApplicationJson)
200 .send(&mut rx_buffer)
201 .await
202 {
203 Err(error.into())
204 } else {
205 Ok(serde_json::from_slice::<XRPLResponse<'_>>(&rx_buffer)?)
206 }
207 }
208 Err(error) => Err(error.into()),
209 };
210
211 response
212 }
213
214 fn get_host(&self) -> Url {
215 self.url.clone()
216 }
217 }
218
219 #[cfg(feature = "helpers")]
220 impl<'a, const BUF: usize, T, D, M> XRPLFaucet for AsyncJsonRpcClient<'a, BUF, T, D, M>
221 where
222 M: RawMutex,
223 T: TcpConnect + 'a,
224 D: Dns + 'a,
225 {
226 async fn request_funding(
227 &self,
228 url: Option<Url>,
229 request: FundFaucet<'_>,
230 ) -> XRPLClientResult<()> {
231 let faucet_url = self.get_faucet_url(url)?;
232 let request_json_rpc = match serde_json::to_value(&request) {
233 Ok(value) => value,
234 Err(error) => return Err(XRPLSerdeJsonError::SerdeJsonError(error).into()),
235 };
236
237 let request_string = request_json_rpc.to_string();
238 let request_buf = request_string.as_bytes();
239 let mut rx_buffer = [0; BUF];
240 let mut client = self.client.lock().await;
241 let response = match client.request(Method::POST, faucet_url.as_str()).await {
242 Ok(client) => {
243 if let Err(error) = client
244 .body(request_buf)
245 .content_type(ContentType::ApplicationJson)
246 .send(&mut rx_buffer)
247 .await
248 {
249 Err(error.into())
250 } else {
251 let response = serde_json::from_slice::<XRPLResponse<'_>>(&rx_buffer)?;
252 if response.is_success() {
253 Ok(())
254 } else {
255 Err(XRPLJsonRpcException::RequestError(
257 "Faucet request was not successful".to_string(),
258 )
259 .into())
260 }
261 }
262 }
263 Err(error) => Err(XRPLJsonRpcException::ReqwlessError(error)),
264 };
265
266 response.map_err(Into::into)
267 }
268 }
269}
270
271#[cfg(all(feature = "json-rpc", not(feature = "std")))]
272pub use _no_std::AsyncJsonRpcClient;
273#[cfg(all(feature = "json-rpc", feature = "std"))]
274pub use _std::AsyncJsonRpcClient;