use crate::errors::UbiClientError;
use percent_encoding::{NON_ALPHANUMERIC, percent_encode};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct HTTPClient {
client: reqwest::Client,
base_url: reqwest::Url,
version: String,
}
#[derive(Deserialize, Serialize, Debug, Default)]
pub struct APIError {
pub code: Option<u16>,
pub message: Option<String>,
#[serde(rename = "type")]
pub _type: Option<String>,
pub details: Option<String>,
}
#[derive(Deserialize, Serialize, Debug, Default)]
pub struct ResponseAPIError {
pub error: APIError,
}
pub(crate) fn encode_param(param: &str) -> String {
percent_encode(param.as_bytes(), NON_ALPHANUMERIC).to_string()
}
#[macro_export]
macro_rules! make_json_request {
($sel:ident, $method:path, $url:expr, $body:expr, $query:expr, $response_ty:ty) => {{
use $crate::{client::ResponseAPIError, errors::UbiClientError};
use tracing::{debug, error};
debug!(
method = stringify!($method),
url = $url,
body = ?$body,
"Sending JSON request"
);
let response = $sel
.http_client
.inner($method, $url)?
.json(&$body)
.query(&$query)
.send()
.await?;
let status = response.status();
if status.as_u16() == 204 {
return Ok(Default::default());
}
if !status.is_success() {
error!(status = status.as_u16(), url = $url, "Non-success response");
let value: serde_json::Value = response.json().await?;
debug!(?value, "Received error response");
let parsed_error: ResponseAPIError = serde_json::from_value(value)?;
return Err(UbiClientError::APIResponseError {
etype: parsed_error.error._type.unwrap_or_default(),
message: parsed_error.error.message.unwrap_or_default(),
details: parsed_error.error.details,
});
}
#[cfg(feature = "debug")]
{
let json: serde_json::Value = response.json().await?;
debug!(?json, "Parsed JSON response");
Ok(serde_json::from_value::<$response_ty>(json)?)
}
#[cfg(not(feature = "debug"))]
{
Ok(response.json::<$response_ty>().await?)
}
}};
}
#[macro_export]
macro_rules! make_request {
($sel:ident, $method:path, $url:expr) => {{
use reqwest::Response;
use tracing::{debug, error};
use $crate::client::ResponseAPIError;
debug!(method = stringify!($method), url = $url);
let response: Response = $sel.http_client.inner($method, $url)?.send().await?;
let status_code = response.status().as_u16();
debug!("Received HTTP status code: {}", status_code);
debug!("Sending request to URL: {}", $url);
if status_code == 204 {
return Ok(Default::default());
}
if !response.status().is_success() {
error!(status = status_code, url = $url, "Non-success response");
let value: serde_json::Value = response.json().await?;
debug!(?value, "Received error response");
let parsed_error: ResponseAPIError = serde_json::from_value(value)?;
return Err(UbiClientError::APIResponseError {
etype: parsed_error.error._type.unwrap_or_default(),
message: parsed_error.error.message.unwrap_or_default(),
details: parsed_error.error.details,
});
}
let ret: Result<reqwest::Response, UbiClientError> = Ok(response);
ret
}};
}
impl HTTPClient {
pub fn new<S, T>(base_url: S, client: reqwest::Client, version: T) -> HTTPClient
where
S: Into<String>,
T: Into<String>,
{
let parsed_url =
reqwest::Url::parse(&base_url.into()).expect("Failed to parse the base_url");
let ver = format!("{}/", version.into().replace('/', ""));
tracing::debug!("API Version is {}", &ver);
HTTPClient {
base_url: parsed_url,
client,
version: ver,
}
}
pub(crate) fn inner(
&self,
method: reqwest::Method,
query_url: &str,
) -> Result<reqwest::RequestBuilder, UbiClientError> {
let qurl = query_url.trim_start_matches('/');
let url = self.base_url.join(&self.version)?.join(qurl)?;
tracing::debug!("URL is {:?}", &url);
let request_with_url_and_header: Result<reqwest::RequestBuilder, UbiClientError> =
match method {
reqwest::Method::GET => Ok(self.client.get(url)),
reqwest::Method::PUT => Ok(self.client.put(url)),
reqwest::Method::POST => Ok(self.client.post(url)),
reqwest::Method::DELETE => Ok(self.client.delete(url)),
_ => return Err(UbiClientError::UnsupportedMethod),
};
request_with_url_and_header
}
}