bitski-provider 0.3.1

Bitski Web3 provider
Documentation
use crate::USER_AGENT;
use bitski_chain_models::networks::Network;
use jsonrpc_core::Call;
use serde::de::DeserializeOwned;
use serde_json::Value;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use web3::error::TransportError;
use web3::futures::future::BoxFuture;
use web3::{helpers, Error, RequestId, Transport};

use once_cell::sync::Lazy;

#[derive(Debug, Clone)]
pub struct RestWeb3Provider {
    client: reqwest::Client,
    pub network: Network,
    client_id: String,
    id: Arc<AtomicUsize>,
}

static CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
    reqwest::ClientBuilder::new()
        .user_agent(USER_AGENT.clone())
        .build()
        .expect("could not build REST client")
});

impl RestWeb3Provider {
    pub fn new(network: Network, client_id: &dyn ToString) -> Self {
        RestWeb3Provider {
            client: CLIENT.clone(),
            network,
            client_id: client_id.to_string(),
            id: Arc::new(AtomicUsize::new(0)),
        }
    }

    async fn send<T: DeserializeOwned>(
        client: &reqwest::Client,
        request: Call,
        network: Network,
        client_id: String,
    ) -> web3::error::Result<T> {
        let method_call = match request {
            Call::MethodCall(ref method_call) => method_call,
            _ => return Err(Error::Internal),
        };
        #[cfg(feature = "tracing")]
        let request_id = serde_json::to_string(&method_call.id).unwrap_or_default();

        let url = format!(
            "{}/{}?params={}",
            network.rpc_url,
            method_call.method,
            serde_json::to_string(&method_call.params)?,
        );

        #[cfg(feature = "tracing")]
        tracing::debug!(
            "[id:{}] sending request: {:?} to {}",
            request_id,
            serde_json::to_string(&request)?,
            url
        );
        let response = client
            .get(url)
            .header("X-API-Key", client_id)
            .send()
            .await
            .map_err(|err| {
                Error::Transport(TransportError::Message(format!(
                    "failed to send request: {}",
                    err
                )))
            })?;
        let status = response.status();
        let response = response.bytes().await.map_err(|err| {
            Error::Transport(TransportError::Message(format!(
                "failed to read response bytes: {}",
                err
            )))
        })?;
        #[cfg(feature = "tracing")]
        tracing::debug!(
            "[id:{}] received response: {:?}",
            request_id,
            String::from_utf8_lossy(&response)
        );
        if !status.is_success() {
            return Err(Error::Transport(TransportError::Code(status.as_u16())));
        }
        helpers::arbitrary_precision_deserialize_workaround(&response).map_err(|err| {
            Error::Transport(TransportError::Message(format!(
                "failed to deserialize response: {}",
                err
            )))
        })
    }
}

impl Transport for RestWeb3Provider {
    type Out = BoxFuture<'static, web3::error::Result<jsonrpc_core::Value>>;

    fn prepare(&self, method: &str, params: Vec<Value>) -> (RequestId, Call) {
        let id = self.id.fetch_add(1, Ordering::AcqRel);
        let request = helpers::build_request(id, method, params);
        (id, request)
    }

    fn send(&self, _id: RequestId, call: Call) -> Self::Out {
        let client = self.client.clone();
        let network = self.network.clone();
        let client_id = self.client_id.clone();
        Box::pin(async move {
            let output: jsonrpc_core::Value = Self::send(&client, call, network, client_id).await?;
            Ok(output)
        })
    }
}