use super::{BaseHttpClient, Form, Headers, Query};
use std::convert::TryInto;
use std::time::Duration;
use async_trait::async_trait;
use reqwest::{Method, RequestBuilder};
use serde_json::Value;
#[derive(thiserror::Error, Debug)]
pub enum ReqwestError {
#[error("request: {0}")]
Client(#[from] reqwest::Error),
#[error("status code {}", reqwest::Response::status(.0))]
StatusCode(reqwest::Response),
}
#[derive(Debug, Clone)]
pub struct ReqwestClient {
client: reqwest::Client,
}
impl Default for ReqwestClient {
fn default() -> Self {
let client = reqwest::ClientBuilder::new()
.timeout(Duration::from_secs(10))
.build()
.unwrap();
Self { client }
}
}
impl ReqwestClient {
async fn request<D>(
&self,
method: Method,
url: &str,
headers: Option<&Headers>,
add_data: D,
) -> Result<String, ReqwestError>
where
D: Fn(RequestBuilder) -> RequestBuilder,
{
let mut request = self.client.request(method.clone(), url);
if let Some(headers) = headers {
let headers = headers.try_into().unwrap();
request = request.headers(headers);
}
request = add_data(request);
log::info!("Making request {:?}", request);
let response = request.send().await?;
if response.status().is_success() {
response.text().await.map_err(Into::into)
} else {
Err(ReqwestError::StatusCode(response))
}
}
}
#[async_trait]
impl BaseHttpClient for ReqwestClient {
type Error = ReqwestError;
#[inline]
async fn get(
&self,
url: &str,
headers: Option<&Headers>,
query: &Query,
) -> Result<String, Self::Error> {
self.request(Method::GET, url, headers, |req| req.query(query))
.await
}
#[inline]
async fn post(
&self,
url: &str,
headers: Option<&Headers>,
query: Option<&Query>,
payload: &Value,
) -> Result<String, Self::Error> {
self.request(Method::POST, url, headers, |req| {
req.query(&query).json(payload)
})
.await
}
#[inline]
async fn post_form(
&self,
url: &str,
headers: Option<&Headers>,
payload: &Form<'_>,
) -> Result<String, Self::Error> {
self.request(Method::POST, url, headers, |req| req.form(payload))
.await
}
#[inline]
async fn put(
&self,
url: &str,
headers: Option<&Headers>,
query: Option<&Query>,
payload: &Value,
) -> Result<String, Self::Error> {
self.request(Method::PUT, url, headers, |req| {
req.query(&query).json(payload)
})
.await
}
#[inline]
async fn delete(
&self,
url: &str,
headers: Option<&Headers>,
payload: &Value,
) -> Result<String, Self::Error> {
self.request(Method::DELETE, url, headers, |req| req.json(payload))
.await
}
}