use crate::{
jsonrpc::{
self,
batch::{self, Batch},
JsonError,
},
method::Method,
types::Empty,
};
use reqwest::{StatusCode, Url};
use serde::{de::DeserializeOwned, Serialize};
use std::{env, sync::Arc};
use thiserror::Error;
pub struct Client {
client: reqwest::Client,
url: Url,
}
impl Client {
pub fn new(url: Url) -> Self {
Self::with_client(reqwest::Client::new(), url)
}
pub fn with_client(client: reqwest::Client, url: Url) -> Self {
Self { client, url }
}
pub fn from_env() -> Self {
Self::new(
env::var("ETHRPC")
.expect("missing ETHRPC environment variable")
.parse()
.unwrap(),
)
}
pub(super) async fn roundtrip<T, R>(&self, request: T) -> Result<R, Error>
where
T: Serialize,
R: DeserializeOwned,
{
let response = self
.client
.post(self.url.clone())
.json(&request)
.send()
.await?;
let status = response.status();
if !status.is_success() {
return Err(Error::Status(status, response.text().await?));
}
let body = response.json().await?;
Ok(body)
}
pub async fn call<M>(&self, method: M, params: M::Params) -> Result<M::Result, Error>
where
M: Method + Serialize,
{
jsonrpc::call_async(method, params, |request| self.roundtrip(request)).await
}
pub async fn call_np<M>(&self, method: M) -> Result<M::Result, Error>
where
M: Method<Params = Empty> + Serialize,
{
jsonrpc::call_async(method, Empty, |request| self.roundtrip(request)).await
}
pub async fn batch<B>(&self, batch: B) -> Result<B::Values, Error>
where
B: Batch,
{
batch::call_async(batch, |requests| self.roundtrip(requests)).await
}
pub async fn try_batch<B>(&self, batch: B) -> Result<B::Results, Error>
where
B: Batch,
{
batch::try_call_async(batch, |requests| self.roundtrip(requests)).await
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("JSON error: {0}")]
Json(#[from] Arc<JsonError>),
#[error("HTTP error: {0}")]
Http(#[from] Arc<reqwest::Error>),
#[error("HTTP {0} error: {1}")]
Status(StatusCode, String),
#[error(transparent)]
Rpc(#[from] jsonrpc::Error),
#[error(transparent)]
Batch(#[from] batch::Error),
}
impl Error {
pub(super) fn duplicate(&self) -> Self {
match self {
Self::Json(err) => Self::Json(err.clone()),
Self::Http(err) => Self::Http(err.clone()),
Self::Status(code, body) => Self::Status(*code, body.clone()),
Self::Rpc(err) => Self::Rpc(err.clone()),
Self::Batch(batch::Error) => Self::Batch(batch::Error),
}
}
}
impl From<JsonError> for Error {
fn from(err: JsonError) -> Self {
Self::from(Arc::new(err))
}
}
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self {
Self::from(Arc::new(err))
}
}