use std::time::Duration;
use serde::de::DeserializeOwned;
use serde::Serialize;
use crate::error::{Error, Result};
const API_KEY_HEADER: &str = "X-API-KEY";
#[derive(Clone)]
pub(crate) struct Transport {
http: reqwest::Client,
base_url: String,
api_key: String,
}
impl Transport {
pub(crate) fn new(base_url: String, api_key: String) -> Self {
let http = reqwest::Client::builder()
.timeout(Duration::from_secs(60))
.build()
.expect("failed to build HTTP client");
Self {
http,
base_url,
api_key,
}
}
pub(crate) async fn post_json<B, R>(&self, path: &str, body: &B) -> Result<R>
where
B: Serialize + ?Sized,
R: DeserializeOwned,
{
let mut req = self.http.post(self.url(path)).json(body);
req = self.with_api_key(req);
Self::decode(req.send().await?).await
}
pub(crate) async fn get_json<R>(&self, path: &str) -> Result<R>
where
R: DeserializeOwned,
{
let mut req = self.http.get(self.url(path));
req = self.with_api_key(req);
Self::decode(req.send().await?).await
}
fn url(&self, path: &str) -> String {
format!("{}{}", self.base_url, path)
}
fn with_api_key(&self, req: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
if self.api_key.is_empty() {
req
} else {
req.header(API_KEY_HEADER, &self.api_key)
}
}
async fn decode<R>(resp: reqwest::Response) -> Result<R>
where
R: DeserializeOwned,
{
let status = resp.status();
if !status.is_success() {
let body = resp.text().await.unwrap_or_default();
return Err(Error::Api {
status: status.as_u16(),
body,
});
}
Ok(resp.json::<R>().await?)
}
}