1use 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
17pub struct Client {
19 client: reqwest::Client,
20 url: Url,
21}
22
23impl Client {
24 pub fn new(url: Url) -> Self {
27 Self::with_client(reqwest::Client::new(), url)
28 }
29
30 pub fn with_client(client: reqwest::Client, url: Url) -> Self {
33 Self { client, url }
34 }
35
36 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 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 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 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 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#[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 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}