pub use ::http::{Request, Response};
use http::header::CONTENT_TYPE;
use crate::Error;
pub static CONTENT_TYPE_FORM_URLENCODED: &str = "application/x-www-form-urlencoded";
pub static CONTENT_TYPE_JSON: &str = "application/json";
#[derive(Clone)]
#[derive(Default)]
pub enum Body {
Bytes(Vec<u8>),
#[default]
Empty,
}
impl std::fmt::Debug for Body {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bytes(bytes) => f.debug_tuple("Bytes").field(&bytes.len()).finish(),
Self::Empty => write!(f, "no bytes"),
}
}
}
impl From<Vec<u8>> for Body {
fn from(value: Vec<u8>) -> Self {
Self::Bytes(value)
}
}
impl From<String> for Body {
fn from(value: String) -> Self {
value.into_bytes().into()
}
}
impl Body {
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::Empty => &[],
Self::Bytes(b) => b,
}
}
pub fn text(&self) -> Result<String, Error> {
String::from_utf8(self.as_bytes().to_vec()).map_err(Error::FromUTF8)
}
}
pub(crate) fn from_json_value<V: serde::de::DeserializeOwned>(
response: Response<Body>,
) -> Result<V, Error> {
let ct_header = String::from_utf8(
response
.headers()
.get(CONTENT_TYPE)
.map(|hv| hv.as_bytes())
.unwrap_or_default()
.to_vec(),
)?;
if ct_header.starts_with(CONTENT_TYPE_JSON) {
Ok(serde_json::from_slice(response.into_body().as_bytes())?)
} else {
Err(Error::ResponseNotJson(ct_header))
}
}
#[async_trait::async_trait]
pub trait Client: Send + Sync {
async fn send_request(&self, request: Request<Body>) -> Result<Response<Body>, Error>;
}
#[cfg(feature = "reqwest")]
pub mod reqwest {
use std::time::Duration;
pub(super) fn into_local_request(
request: ::http::Request<super::Body>,
) -> Result<reqwest::Request, crate::Error> {
let method = reqwest::Method::from_bytes(request.method().as_str().as_bytes())
.map_err(crate::Error::ReqwestMethod)?;
let url = request.uri().to_string().parse()?;
let mut req = reqwest::Request::new(method, url);
for (name, value) in request.headers() {
req.headers_mut().insert(name, value.clone());
}
let _ = req
.body_mut()
.insert(request.into_body().as_bytes().to_vec().into());
Ok(req)
}
pub(super) async fn into_local_response(
response: reqwest::Response,
) -> Result<::http::Response<super::Body>, crate::Error> {
let mut resp = ::http::Response::builder().status(response.status().as_u16());
for (name, value) in response.headers() {
resp = resp.header(name, value);
}
let body = response
.bytes()
.await
.map_err(crate::Error::from)?
.to_vec()
.into();
resp.body(body).map_err(crate::Error::Http)
}
pub struct Client(::reqwest::Client);
impl std::default::Default for Client {
fn default() -> Self {
Self(
::reqwest::Client::builder()
.timeout(Duration::from_secs(5))
.build()
.expect("failed to build a http client"),
)
}
}
impl From<::reqwest::Client> for Client {
fn from(client: ::reqwest::Client) -> Self {
Self(client)
}
}
#[async_trait::async_trait]
impl super::Client for Client {
#[tracing::instrument(skip(self))]
async fn send_request(
&self,
request: ::http::Request<super::Body>,
) -> Result<::http::Response<super::Body>, crate::Error> {
let local_request = into_local_request(request)?;
let resp = self.0.execute(local_request).await;
match resp {
Ok(resp) => into_local_response(resp).await,
Err(err) => Err(Box::new(err).into()),
}
}
}
pub use reqwest as reqwest_client;
}
#[cfg(feature = "blocking")]
pub mod blocking {
use std::time::Duration;
type ReqwestMethod = ::reqwest::Method;
type ReqwestRequest = ::reqwest::blocking::Request;
type ReqwestResponse = ::reqwest::blocking::Response;
type ReqwestClient = ::reqwest::blocking::Client;
fn into_local_request(
request: ::http::Request<crate::http::Body>,
) -> Result<ReqwestRequest, crate::Error> {
let method = ReqwestMethod::from_bytes(request.method().as_str().as_bytes())
.map_err(crate::Error::ReqwestMethod)?;
let url = request.uri().to_string().parse()?;
let mut req = ReqwestRequest::new(method, url);
for (name, value) in request.headers() {
req.headers_mut().insert(name, value.clone());
}
let _ = req
.body_mut()
.insert(request.into_body().as_bytes().to_vec().into());
Ok(req)
}
fn into_local_response(
response: ReqwestResponse,
) -> Result<::http::Response<crate::http::Body>, crate::Error> {
let mut resp = ::http::Response::builder().status(response.status().as_u16());
for (name, value) in response.headers() {
resp = resp.header(name, value);
}
let body = response
.bytes()
.map_err(crate::Error::from)?
.to_vec()
.into();
resp.body(body).map_err(crate::Error::Http)
}
pub struct Client(ReqwestClient);
impl std::default::Default for Client {
fn default() -> Self {
Self(
ReqwestClient::builder()
.timeout(Duration::from_secs(5))
.build()
.expect("failed to build a blocking http client"),
)
}
}
impl From<ReqwestClient> for Client {
fn from(client: ReqwestClient) -> Self {
Self(client)
}
}
#[async_trait::async_trait]
impl crate::http::Client for Client {
#[tracing::instrument(skip(self))]
async fn send_request(
&self,
request: ::http::Request<crate::http::Body>,
) -> Result<::http::Response<crate::http::Body>, crate::Error> {
let local_request = into_local_request(request)?;
let resp = self.0.execute(local_request);
match resp {
Ok(resp) => into_local_response(resp),
Err(err) => Err(Box::new(err).into()),
}
}
}
}
#[cfg(feature = "reqwest_middleware")]
pub mod reqwest_middleware {
use futures::TryFutureExt;
#[cfg(feature = "reqwest")]
use super::reqwest::into_local_request;
#[cfg(feature = "reqwest")]
use super::reqwest::into_local_response;
#[cfg(feature = "reqwest")]
pub struct MiddlewareClient(::reqwest_middleware::ClientWithMiddleware);
#[cfg(feature = "reqwest")]
impl From<::reqwest_middleware::ClientWithMiddleware> for MiddlewareClient {
fn from(client: ::reqwest_middleware::ClientWithMiddleware) -> Self {
Self(client)
}
}
#[cfg(feature = "reqwest")]
#[async_trait::async_trait]
impl super::Client for MiddlewareClient {
async fn send_request(
&self,
request: ::http::Request<super::Body>,
) -> Result<::http::Response<super::Body>, crate::Error> {
let local_request = into_local_request(request)?;
self.0
.execute(local_request)
.map_err(crate::Error::ReqwestMiddleware)
.and_then(into_local_response)
.await
}
}
}
#[cfg(feature = "reqwest_middleware")]
pub use reqwest_middleware::MiddlewareClient;