use std::fmt::Debug;
use async_trait::async_trait;
use futures::stream::BoxStream;
use serde::{de::Deserialize, ser::Serialize};
use serde_json::Value;
use thiserror::Error;
use url::Url;
use nimiq_jsonrpc_core::{Request, Response, SubscriptionId};
use crate::{Client, Credentials};
#[derive(Debug, Error)]
pub enum Error {
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("{0}")]
JsonRpc(#[from] nimiq_jsonrpc_core::Error),
#[error("Response ID doesn't match request ID: expected {expected}, but got {got:?}")]
IdMismatch {
expected: usize,
got: Value,
},
}
pub struct HttpClient {
next_id: usize,
client: reqwest::Client,
url: Url,
basic_auth: Option<Credentials>,
}
impl HttpClient {
pub fn new(client: reqwest::Client, url: Url, basic_auth: Option<Credentials>) -> Self {
Self {
next_id: 1,
client,
url,
basic_auth,
}
}
pub fn with_url(url: Url) -> Self {
Self::new(reqwest::Client::new(), url, None)
}
}
#[async_trait]
impl Client for HttpClient {
type Error = Error;
async fn send_request<P, R>(&mut self, method: &str, params: &P) -> Result<R, Error>
where
P: Serialize + Debug + Send + Sync,
R: for<'de> Deserialize<'de> + Debug + Send + Sync,
{
let request_id = self.next_id;
self.next_id += 1;
let request = Request::build(method.to_owned(), Some(params), Some(&request_id))
.expect("Failed to serialize JSON-RPC request.");
log::debug!("Sending request: {:?}", request);
let mut request_builder = self.client.post(self.url.clone());
if let Some(basic_auth) = &self.basic_auth {
request_builder =
request_builder.basic_auth(&basic_auth.username, Some(&basic_auth.password.0));
}
let response: Response = request_builder
.json(&request)
.send()
.await?
.error_for_status()?
.json()
.await?;
log::debug!("Received response: {:?}", response);
if response.id != Value::Number(request_id.into()) {
Err(Error::IdMismatch {
expected: request_id,
got: response.id,
})
} else {
Ok(response.into_result()?)
}
}
async fn connect_stream<T>(&mut self, _id: SubscriptionId) -> BoxStream<'static, T> {
panic!("Streams are not supported by the HTTP client.");
}
async fn disconnect_stream(&mut self, _id: SubscriptionId) -> Result<(), Self::Error> {
panic!("Streams are not supported by the HTTP client.");
}
async fn close(&mut self) {}
}