neo3/neo_clients/rpc/transports/
http_provider.rs1use std::{
4 str::FromStr,
5 sync::atomic::{AtomicU64, Ordering},
6};
7
8use async_trait::async_trait;
9use http::HeaderValue;
10use log::debug;
11use reqwest::{header, Client, Error as ReqwestError};
12use serde::{de::DeserializeOwned, Serialize};
13use thiserror::Error;
14use url::Url;
15
16use super::common::{JsonRpcError, Request, Response};
17use crate::neo_clients::{Authorization, JsonRpcProvider, ProviderError};
18use neo3::config::NeoConstants;
19
20#[derive(Debug)]
37pub struct HttpProvider {
38 id: AtomicU64,
39 client: Client,
40 url: Url,
41}
42
43#[derive(Error, Debug)]
44pub enum ClientError {
46 #[error(transparent)]
48 ReqwestError(#[from] ReqwestError),
49 #[error(transparent)]
50 JsonRpcError(#[from] JsonRpcError),
52
53 #[error("Deserialization Error: {err}. Response: {text}")]
54 SerdeJson {
56 err: serde_json::Error,
58 text: String,
60 },
61}
62
63impl From<ClientError> for ProviderError {
64 fn from(src: ClientError) -> Self {
65 match src {
66 ClientError::ReqwestError(err) => ProviderError::HTTPError(err.into()),
67 ClientError::JsonRpcError(err) => ProviderError::JsonRpcError(err),
68 ClientError::SerdeJson { err, text } => {
69 debug!("SerdeJson Error: {:#?}, Response: {:#?}", err, text);
70 ProviderError::SerdeJson(err)
71 },
72 _ => ProviderError::IllegalState("unexpected error".to_string()),
73 }
74 }
75}
76
77#[cfg_attr(target_arch = "wasm32", async_trait(? Send))]
78#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
79impl JsonRpcProvider for HttpProvider {
80 type Error = ClientError;
81
82 async fn fetch<T: Serialize + Send + Sync, R: DeserializeOwned>(
83 &self,
84 method: &str,
85 params: T,
86 ) -> Result<R, ClientError> {
87 let next_id = self.id.fetch_add(1, Ordering::SeqCst);
88 let payload = Request::new(next_id, method, params);
89
90 let res = self.client.post(self.url.as_ref()).json(&payload).send().await?;
91 let body = res.bytes().await?;
92
93 let raw = match serde_json::from_slice(&body) {
94 Ok(Response::Success { result, .. }) => result.to_owned(),
95 Ok(Response::Error { error, .. }) => return Err(error.into()),
96 Ok(_) => {
97 let err = ClientError::SerdeJson {
98 err: serde::de::Error::custom("unexpected notification over HTTP transport"),
99 text: String::from_utf8_lossy(&body).to_string(),
100 };
101 return Err(err);
102 },
103 Err(err) =>
104 return Err(ClientError::SerdeJson {
105 err,
106 text: String::from_utf8_lossy(&body).to_string(),
107 }),
108 };
109
110 let res = serde_json::from_str(raw.get())
111 .map_err(|err| ClientError::SerdeJson { err, text: raw.to_string() })?;
112
113 Ok(res)
114 }
115}
116
117impl Default for HttpProvider {
118 fn default() -> Self {
120 Self::new(Url::parse(NeoConstants::SEED_1).unwrap()).unwrap()
121 }
122}
123
124impl HttpProvider {
125 pub fn new<T: TryInto<Url>>(url: T) -> Result<Self, T::Error> {
145 let url = url.try_into()?;
146 Ok(Self::new_with_client(url, Client::new()))
147 }
148
149 pub fn url(&self) -> &Url {
151 &self.url
152 }
153
154 pub fn url_mut(&mut self) -> &mut Url {
156 &mut self.url
157 }
158
159 pub fn new_with_auth(
172 url: impl Into<Url>,
173 auth: Authorization,
174 ) -> Result<Self, HttpClientError> {
175 let mut auth_value = HeaderValue::from_str(&auth.to_string())?;
176 auth_value.set_sensitive(true);
177
178 let mut headers = reqwest::header::HeaderMap::new();
179 headers.insert(reqwest::header::AUTHORIZATION, auth_value);
180
181 let client = Client::builder().default_headers(headers).build()?;
182
183 Ok(Self::new_with_client(url, client))
184 }
185
186 pub fn new_with_client(url: impl Into<Url>, client: reqwest::Client) -> Self {
199 Self { id: AtomicU64::new(1), client, url: url.into() }
200 }
201}
202
203impl Clone for HttpProvider {
204 fn clone(&self) -> Self {
205 Self { id: AtomicU64::new(1), client: self.client.clone(), url: self.url.clone() }
206 }
207}
208
209#[derive(Error, Debug)]
210pub enum HttpClientError {
212 #[error(transparent)]
214 InvalidHeader(#[from] header::InvalidHeaderValue),
215
216 #[error(transparent)]
218 ClientBuild(#[from] reqwest::Error),
219}