use crate::{
Body, BodyWriter, Client, RequestBuilder, Response, ResponseBody, APPLICATION_JSON,
APPLICATION_OCTET_STREAM,
};
use async_trait::async_trait;
use bytes::Bytes;
use conjure_error::Error;
use conjure_http::client::{
Accept, AsyncClient, AsyncRequestBody, AsyncVisitRequestBody, AsyncWriteBody, VisitResponse,
};
use conjure_http::{PathParams, QueryParams};
use conjure_serde::json;
use hyper::header::{HeaderValue, ACCEPT, CONTENT_TYPE};
use hyper::{HeaderMap, Method, StatusCode};
use serde::Serialize;
use std::future::Future;
use std::pin::Pin;
use tokio::io::AsyncReadExt;
impl Client {
#[allow(clippy::too_many_arguments)]
async fn conjure_inner(
&self,
method: Method,
path: &str,
path_params: PathParams,
query_params: QueryParams,
headers: HeaderMap,
body: Option<RawBody<'_>>,
accept: Accept,
) -> Result<Response, Error> {
let mut request = RequestBuilder::new(self, method, path);
for (key, value) in &path_params {
request = request.param(key, value);
}
for (key, values) in &query_params {
for value in values {
request = request.param(key, value);
}
}
for (key, value) in &headers {
request.headers_mut().insert(key.clone(), value.clone());
}
if let Some(body) = body {
request = request.body(body);
}
let accept = match accept {
Accept::Empty | Accept::Serializable => APPLICATION_JSON.clone(),
Accept::Binary => APPLICATION_OCTET_STREAM.clone(),
};
request.headers_mut().insert(ACCEPT, accept);
request.send().await
}
}
impl AsyncClient for Client {
type BinaryWriter = BodyWriter;
type BinaryBody = ResponseBody;
fn request<'a, T, U>(
&'a self,
method: Method,
path: &'static str,
path_params: PathParams,
query_params: QueryParams,
headers: HeaderMap<HeaderValue>,
body: T,
response_visitor: U,
) -> Pin<Box<dyn Future<Output = Result<U::Output, Error>> + Send + 'a>>
where
T: AsyncRequestBody<'a, Self::BinaryWriter> + Send + 'a,
U: VisitResponse<Self::BinaryBody> + Send + 'a,
{
Box::pin(async move {
let body = body.accept(RawBodyVisitor)?;
let accept = response_visitor.accept();
let response = self
.conjure_inner(
method,
path,
path_params,
query_params,
headers,
body,
accept,
)
.await?;
if response.status() == StatusCode::NO_CONTENT {
return response_visitor.visit_empty();
}
if let Some(header) = response.headers().get(CONTENT_TYPE) {
if header == *APPLICATION_JSON {
let mut body = vec![];
response
.into_body()
.read_to_end(&mut body)
.await
.map_err(Error::internal_safe)?;
let mut deserializer = json::ClientDeserializer::from_slice(&body);
let r = response_visitor.visit_serializable(&mut deserializer)?;
deserializer.end().map_err(Error::internal_safe)?;
return Ok(r);
} else if header == *APPLICATION_OCTET_STREAM {
let body = response.into_body();
return response_visitor.visit_binary(body);
}
}
Err(Error::internal_safe("invalid response Content-Type"))
})
}
}
enum RawBody<'a> {
Json(Bytes),
Binary(Pin<Box<dyn AsyncWriteBody<BodyWriter> + Sync + Send + 'a>>),
}
#[async_trait]
impl<'a> Body for RawBody<'a> {
fn content_length(&self) -> Option<u64> {
match self {
RawBody::Json(buf) => Some(buf.len() as u64),
RawBody::Binary(_) => None,
}
}
fn content_type(&self) -> HeaderValue {
match self {
RawBody::Json(_) => APPLICATION_JSON.clone(),
RawBody::Binary(_) => APPLICATION_OCTET_STREAM.clone(),
}
}
fn full_body(&self) -> Option<Bytes> {
match self {
RawBody::Json(body) => Some(body.clone()),
RawBody::Binary(_) => None,
}
}
async fn write(mut self: Pin<&mut Self>, w: Pin<&mut BodyWriter>) -> Result<(), Error> {
match &mut *self {
RawBody::Json(_) => unreachable!(),
RawBody::Binary(body) => body.as_mut().write_body(w).await,
}
}
async fn reset(mut self: Pin<&mut Self>) -> bool {
match &mut *self {
RawBody::Json(_) => true,
RawBody::Binary(body) => body.as_mut().reset().await,
}
}
}
struct RawBodyVisitor;
impl<'a> AsyncVisitRequestBody<'a, BodyWriter> for RawBodyVisitor {
type Output = Result<Option<RawBody<'a>>, Error>;
fn visit_empty(self) -> Self::Output {
Ok(None)
}
fn visit_serializable<T>(self, body: T) -> Self::Output
where
T: Serialize + 'a,
{
let body = json::to_vec(&body).map_err(Error::internal)?;
Ok(Some(RawBody::Json(Bytes::from(body))))
}
fn visit_binary<T>(self, body: T) -> Self::Output
where
T: AsyncWriteBody<BodyWriter> + Sync + Send + 'a,
{
Ok(Some(RawBody::Binary(Box::pin(body))))
}
}