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
111 let res = serde_json::from_str(raw.get())
112 .map_err(|err| ClientError::SerdeJson { err, text: raw.to_string() })?;
113
114 Ok(res)
115 }
116}
117
118impl Default for HttpProvider {
119 fn default() -> Self {
121 Self::new(Url::parse(NeoConstants::SEED_1).unwrap()).unwrap()
122 }
123}
124
125impl HttpProvider {
126 pub fn new<T: TryInto<Url>>(url: T) -> Result<Self, T::Error> {
146 let url = url.try_into()?;
147 Ok(Self::new_with_client(url, Client::new()))
148 }
149
150 pub fn url(&self) -> &Url {
152 &self.url
153 }
154
155 pub fn url_mut(&mut self) -> &mut Url {
157 &mut self.url
158 }
159
160 pub fn new_with_auth(
173 url: impl Into<Url>,
174 auth: Authorization,
175 ) -> Result<Self, HttpClientError> {
176 let mut auth_value = HeaderValue::from_str(&auth.to_string())?;
177 auth_value.set_sensitive(true);
178
179 let mut headers = reqwest::header::HeaderMap::new();
180 headers.insert(reqwest::header::AUTHORIZATION, auth_value);
181
182 let client = Client::builder().default_headers(headers).build()?;
183
184 Ok(Self::new_with_client(url, client))
185 }
186
187 pub fn new_with_client(url: impl Into<Url>, client: reqwest::Client) -> Self {
200 Self { id: AtomicU64::new(1), client, url: url.into() }
201 }
202}
203
204impl Clone for HttpProvider {
205 fn clone(&self) -> Self {
206 Self { id: AtomicU64::new(1), client: self.client.clone(), url: self.url.clone() }
207 }
208}
209
210#[derive(Error, Debug)]
211pub enum HttpClientError {
213 #[error(transparent)]
215 InvalidHeader(#[from] header::InvalidHeaderValue),
216
217 #[error(transparent)]
219 ClientBuild(#[from] reqwest::Error),
220}