xrpl/asynch/clients/json_rpc/
mod.rs

1use 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
13/// Renames the requests field `command` to `method` for JSON-RPC.
14fn 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                        // This todo!() should also be handled
119                        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                            // Fix this todo!()
256                            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;