2020-05-24 15:40:16 +00:00
|
|
|
//! Minimal HTTP JSON-RPC 2.0 Client
|
2020-05-22 18:37:21 +00:00
|
|
|
//! The request/response code is taken from [here](https://github.com/althea-net/guac_rs/blob/master/web3/src/jsonrpc)
|
2020-05-24 15:40:16 +00:00
|
|
|
use crate::providers::JsonRpcClient;
|
|
|
|
|
|
|
|
use async_trait::async_trait;
|
2020-05-22 18:37:21 +00:00
|
|
|
use reqwest::{Client, Error as ReqwestError};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use serde_json::Value;
|
2020-05-24 15:40:16 +00:00
|
|
|
use std::{
|
|
|
|
convert::TryFrom,
|
|
|
|
fmt,
|
|
|
|
sync::atomic::{AtomicU64, Ordering},
|
|
|
|
};
|
2020-05-22 18:37:21 +00:00
|
|
|
use thiserror::Error;
|
2020-05-24 15:40:16 +00:00
|
|
|
use url::{ParseError, Url};
|
2020-05-22 18:37:21 +00:00
|
|
|
|
2020-05-24 15:40:16 +00:00
|
|
|
/// An HTTP Client
|
2020-05-23 00:01:20 +00:00
|
|
|
#[derive(Debug)]
|
2020-05-24 15:40:16 +00:00
|
|
|
pub struct Provider {
|
2020-05-22 18:37:21 +00:00
|
|
|
id: AtomicU64,
|
|
|
|
client: Client,
|
|
|
|
url: Url,
|
|
|
|
}
|
|
|
|
|
2020-05-24 15:40:16 +00:00
|
|
|
#[derive(Error, Debug)]
|
|
|
|
pub enum ClientError {
|
|
|
|
#[error(transparent)]
|
|
|
|
ReqwestError(#[from] ReqwestError),
|
|
|
|
#[error(transparent)]
|
|
|
|
JsonRpcError(#[from] JsonRpcError),
|
2020-05-23 00:01:20 +00:00
|
|
|
}
|
|
|
|
|
2020-05-24 15:40:16 +00:00
|
|
|
#[async_trait]
|
|
|
|
impl JsonRpcClient for Provider {
|
|
|
|
type Error = ClientError;
|
2020-05-22 18:37:21 +00:00
|
|
|
|
|
|
|
/// Sends a POST request with the provided method and the params serialized as JSON
|
2020-05-24 15:40:16 +00:00
|
|
|
/// over HTTP
|
|
|
|
async fn request<T: Serialize + Send + Sync, R: for<'a> Deserialize<'a>>(
|
2020-05-22 18:37:21 +00:00
|
|
|
&self,
|
|
|
|
method: &str,
|
|
|
|
params: Option<T>,
|
|
|
|
) -> Result<R, ClientError> {
|
|
|
|
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::<Response<R>>().await?;
|
|
|
|
|
|
|
|
Ok(res.data.into_result()?)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-24 15:40:16 +00:00
|
|
|
impl Provider {
|
|
|
|
/// Initializes a new HTTP Client
|
|
|
|
pub fn new(url: impl Into<Url>) -> Self {
|
|
|
|
Self {
|
|
|
|
id: AtomicU64::new(0),
|
|
|
|
client: Client::new(),
|
|
|
|
url: url.into(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-24 15:55:46 +00:00
|
|
|
impl TryFrom<&str> for super::Provider<Provider> {
|
2020-05-24 15:40:16 +00:00
|
|
|
type Error = ParseError;
|
|
|
|
|
|
|
|
fn try_from(src: &str) -> Result<Self, Self::Error> {
|
2020-05-24 15:55:46 +00:00
|
|
|
Ok(super::Provider(Provider::new(Url::parse(src)?)))
|
2020-05-24 15:40:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Clone for Provider {
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
Self {
|
|
|
|
id: AtomicU64::new(0),
|
|
|
|
client: self.client.clone(),
|
|
|
|
url: self.url.clone(),
|
|
|
|
}
|
|
|
|
}
|
2020-05-22 18:37:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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<Value>,
|
|
|
|
}
|
|
|
|
|
|
|
|
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<T>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> Request<'a, T> {
|
|
|
|
/// Creates a new JSON RPC request
|
|
|
|
fn new(id: u64, method: &'a str, params: Option<T>) -> Self {
|
|
|
|
Self {
|
|
|
|
id,
|
|
|
|
jsonrpc: "2.0",
|
|
|
|
method,
|
|
|
|
params,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
struct Response<T> {
|
|
|
|
id: u64,
|
|
|
|
jsonrpc: String,
|
|
|
|
#[serde(flatten)]
|
|
|
|
data: ResponseData<T>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
#[serde(untagged)]
|
|
|
|
enum ResponseData<R> {
|
|
|
|
Error { error: JsonRpcError },
|
|
|
|
Success { result: R },
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<R> ResponseData<R> {
|
|
|
|
/// Consume response and return value
|
|
|
|
fn into_result(self) -> Result<R, JsonRpcError> {
|
|
|
|
match self {
|
|
|
|
ResponseData::Success { result } => Ok(result),
|
|
|
|
ResponseData::Error { error } => Err(error),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn response() {
|
|
|
|
let response: Response<u64> =
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|