//! Minimal JSON-RPC 2.0 Client //! The request/response code is taken from [here](https://github.com/althea-net/guac_rs/blob/master/web3/src/jsonrpc) use reqwest::{Client, Error as ReqwestError}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::fmt; use std::sync::atomic::{AtomicU64, Ordering}; use thiserror::Error; use url::Url; /// JSON-RPC 2.0 Client #[derive(Debug)] pub struct HttpClient { id: AtomicU64, client: Client, url: Url, } impl Clone for HttpClient { fn clone(&self) -> Self { Self { id: AtomicU64::new(0), client: self.client.clone(), url: self.url.clone(), } } } impl HttpClient { /// Initializes a new HTTP Client pub fn new(url: impl Into) -> Self { Self { id: AtomicU64::new(0), client: Client::new(), url: url.into(), } } /// Sends a POST request with the provided method and the params serialized as JSON pub async fn request Deserialize<'a>>( &self, method: &str, params: Option, ) -> Result { let next_id = self.id.load(Ordering::SeqCst) + 1; self.id.store(next_id, Ordering::SeqCst); let payload = Request::new(next_id, method, params); let res = self .client .post(self.url.as_ref()) .json(&payload) .send() .await?; let res = res.json::>().await?; Ok(res.data.into_result()?) } } #[derive(Error, Debug)] pub enum ClientError { #[error(transparent)] ReqwestError(#[from] ReqwestError), #[error(transparent)] JsonRpcError(#[from] JsonRpcError), } #[derive(Serialize, Deserialize, Debug, Clone, Error)] /// A JSON-RPC 2.0 error pub struct JsonRpcError { /// The error code pub code: i64, /// The error message pub message: String, /// Additional data pub data: Option, } impl fmt::Display for JsonRpcError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "(code: {}, message: {}, data: {:?})", self.code, self.message, self.data ) } } #[derive(Serialize, Deserialize, Debug)] /// A JSON-RPC request struct Request<'a, T> { id: u64, jsonrpc: &'a str, method: &'a str, params: Option, } impl<'a, T> Request<'a, T> { /// Creates a new JSON RPC request fn new(id: u64, method: &'a str, params: Option) -> Self { Self { id, jsonrpc: "2.0", method, params, } } } #[derive(Serialize, Deserialize, Debug, Clone)] struct Response { id: u64, jsonrpc: String, #[serde(flatten)] data: ResponseData, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] enum ResponseData { Error { error: JsonRpcError }, Success { result: R }, } impl ResponseData { /// Consume response and return value fn into_result(self) -> Result { match self { ResponseData::Success { result } => Ok(result), ResponseData::Error { error } => Err(error), } } } #[cfg(test)] mod tests { use super::*; #[test] fn response() { let response: Response = serde_json::from_str(r#"{"jsonrpc": "2.0", "result": 19, "id": 1}"#).unwrap(); assert_eq!(response.id, 1); assert_eq!(response.data.into_result().unwrap(), 19); } }