use std::collections::HashMap;
use serde::de::DeserializeOwned;
use serde::Serialize;
use crate::error::{ApiError, Error, ErrorPayload};
#[derive(Clone, Debug)]
pub(crate) struct HttpClient {
api_key: String,
base_url: String,
client: reqwest::Client,
default_headers: HashMap<String, String>,
}
impl HttpClient {
pub(crate) fn new(
api_key: String,
base_url: String,
client: reqwest::Client,
default_headers: HashMap<String, String>,
) -> Self {
Self {
api_key,
base_url,
client,
default_headers,
}
}
pub(crate) async fn get<T>(
&self,
path: &str,
query: Option<Vec<(&str, String)>>,
) -> Result<T, Error>
where
T: DeserializeOwned,
{
let request = self.client.get(self.url(path));
let request = self.apply_headers(request);
let request = if let Some(query) = query {
request.query(&query)
} else {
request
};
self.send(request).await
}
pub(crate) async fn post<T, B>(&self, path: &str, body: &B) -> Result<T, Error>
where
T: DeserializeOwned,
B: Serialize + ?Sized,
{
let request = self
.apply_headers(self.client.post(self.url(path)))
.json(body);
self.send(request).await
}
pub(crate) async fn patch<T, B>(&self, path: &str, body: &B) -> Result<T, Error>
where
T: DeserializeOwned,
B: Serialize + ?Sized,
{
let request = self
.apply_headers(self.client.patch(self.url(path)))
.json(body);
self.send(request).await
}
pub(crate) async fn delete<T>(&self, path: &str) -> Result<T, Error>
where
T: DeserializeOwned,
{
let request = self.apply_headers(self.client.delete(self.url(path)));
self.send(request).await
}
async fn send<T>(&self, request: reqwest::RequestBuilder) -> Result<T, Error>
where
T: DeserializeOwned,
{
let response = request.send().await?;
let status = response.status();
let text = response.text().await?;
if !status.is_success() {
if let Ok(mut payload) = serde_json::from_str::<ErrorPayload>(&text) {
payload.error.status = status.as_u16();
return Err(Error::Api(Box::new(payload.error)));
}
if let Ok(mut api_error) = serde_json::from_str::<ApiError>(&text) {
api_error.status = status.as_u16();
return Err(Error::Api(Box::new(api_error)));
}
return Err(Error::Http {
status: status.as_u16(),
body: text,
});
}
serde_json::from_str(&text).map_err(Error::Decode)
}
fn apply_headers(&self, request: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
let mut request = request
.header(reqwest::header::ACCEPT, "application/json")
.header(
reqwest::header::AUTHORIZATION,
format!("Bearer {}", self.api_key),
)
.header(
"X-ParcelWing-SDK",
format!("rust/{}", env!("CARGO_PKG_VERSION")),
);
for (key, value) in &self.default_headers {
request = request.header(key, value);
}
request
}
fn url(&self, path: &str) -> String {
format!("{}{}", self.base_url, path)
}
}