ethrpc/http/
client.rs

1//! Ethereum JSON RPC HTTP client.
2
3use crate::{
4    jsonrpc::{
5        self,
6        batch::{self, Batch},
7        JsonError,
8    },
9    method::Method,
10    types::Empty,
11};
12use reqwest::{StatusCode, Url};
13use serde::{de::DeserializeOwned, Serialize};
14use std::{env, sync::Arc};
15use thiserror::Error;
16
17/// An Ethereum JSON RPC HTTP client.
18pub struct Client {
19    client: reqwest::Client,
20    url: Url,
21}
22
23impl Client {
24    /// Creates a new JSON RPC HTTP client for the specified URL with the
25    /// default HTTP client.
26    pub fn new(url: Url) -> Self {
27        Self::with_client(reqwest::Client::new(), url)
28    }
29
30    /// Creates a new JSON RPC HTTP client for the specified client instance and
31    /// URL.
32    pub fn with_client(client: reqwest::Client, url: Url) -> Self {
33        Self { client, url }
34    }
35
36    /// Creates a new JSON RPC HTTP client from the environment. This method
37    /// uses the `ETHRPC` environment variable. This is useful for testing.
38    ///
39    /// # Panics
40    ///
41    /// This method panics if the environment variable is not pressent, or if it
42    /// is not a valid HTTP url.
43    pub fn from_env() -> Self {
44        Self::new(
45            env::var("ETHRPC")
46                .expect("missing ETHRPC environment variable")
47                .parse()
48                .unwrap(),
49        )
50    }
51
52    pub(super) async fn roundtrip<T, R>(&self, request: T) -> Result<R, Error>
53    where
54        T: Serialize,
55        R: DeserializeOwned,
56    {
57        let response = self
58            .client
59            .post(self.url.clone())
60            .json(&request)
61            .send()
62            .await?;
63
64        let status = response.status();
65        if !status.is_success() {
66            return Err(Error::Status(status, response.text().await?));
67        }
68
69        let body = response.json().await?;
70        Ok(body)
71    }
72
73    /// Executes a JSON RPC call.
74    pub async fn call<M>(&self, method: M, params: M::Params) -> Result<M::Result, Error>
75    where
76        M: Method + Serialize,
77    {
78        jsonrpc::call_async(method, params, |request| self.roundtrip(request)).await
79    }
80
81    /// Executes a JSON RPC call with no parameters.
82    pub async fn call_np<M>(&self, method: M) -> Result<M::Result, Error>
83    where
84        M: Method<Params = Empty> + Serialize,
85    {
86        jsonrpc::call_async(method, Empty, |request| self.roundtrip(request)).await
87    }
88
89    /// Executes a JSON RPC batch request.
90    pub async fn batch<B>(&self, batch: B) -> Result<B::Values, Error>
91    where
92        B: Batch,
93    {
94        batch::call_async(batch, |requests| self.roundtrip(requests)).await
95    }
96
97    /// Executes a JSON RPC batch request, returning individual JSON RPC results
98    /// for each batched requests. This allows fine-grained error handling
99    /// for individual methods.
100    pub async fn try_batch<B>(&self, batch: B) -> Result<B::Results, Error>
101    where
102        B: Batch,
103    {
104        batch::try_call_async(batch, |requests| self.roundtrip(requests)).await
105    }
106}
107
108/// An error code.
109#[derive(Debug, Error)]
110pub enum Error {
111    #[error("JSON error: {0}")]
112    Json(#[from] Arc<JsonError>),
113    #[error("HTTP error: {0}")]
114    Http(#[from] Arc<reqwest::Error>),
115    #[error("HTTP {0} error: {1}")]
116    Status(StatusCode, String),
117    #[error(transparent)]
118    Rpc(#[from] jsonrpc::Error),
119    #[error(transparent)]
120    Batch(#[from] batch::Error),
121}
122
123impl Error {
124    /// Duplicate an error.
125    ///
126    /// This has the exact same semantics as `Clone`, but we don't want to
127    /// expose that since the implementation is messy to say the least.
128    pub(super) fn duplicate(&self) -> Self {
129        match self {
130            Self::Json(err) => Self::Json(err.clone()),
131            Self::Http(err) => Self::Http(err.clone()),
132            Self::Status(code, body) => Self::Status(*code, body.clone()),
133            Self::Rpc(err) => Self::Rpc(err.clone()),
134            Self::Batch(batch::Error) => Self::Batch(batch::Error),
135        }
136    }
137}
138
139impl From<JsonError> for Error {
140    fn from(err: JsonError) -> Self {
141        Self::from(Arc::new(err))
142    }
143}
144
145impl From<reqwest::Error> for Error {
146    fn from(err: reqwest::Error) -> Self {
147        Self::from(Arc::new(err))
148    }
149}