use std::{any::Any, future::Future, marker::PhantomData};
use bytes::Bytes;
use http::{Extensions, HeaderMap, HeaderName, HeaderValue, Method, Uri, Version};
use tower_service::Service;
use super::{IntoUri, ServiceExt as _};
pub struct ClientRequestBuilder<'a, S, Err, RespBody> {
service: &'a mut S,
builder: http::request::Builder,
_phantom: PhantomData<(Err, RespBody)>,
}
impl<'a, S, Err, RespBody> ClientRequestBuilder<'a, S, Err, RespBody> {
#[must_use]
pub fn method<T>(mut self, method: T) -> Self
where
Method: TryFrom<T>,
<Method as TryFrom<T>>::Error: Into<http::Error>,
{
self.builder = self.builder.method(method);
self
}
#[must_use]
pub fn uri<U: IntoUri>(mut self, uri: U) -> Self
where
Uri: TryFrom<U::TryInto>,
<Uri as TryFrom<U::TryInto>>::Error: Into<http::Error>,
{
self.builder = self.builder.uri(uri.into_uri());
self
}
#[must_use]
pub fn version(mut self, version: Version) -> Self {
self.builder = self.builder.version(version);
self
}
#[must_use]
pub fn header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: TryFrom<K>,
HeaderValue: TryFrom<V>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
self.builder = self.builder.header(key, value);
self
}
pub fn headers_mut(&mut self) -> Option<&mut HeaderMap<HeaderValue>> {
self.builder.headers_mut()
}
#[must_use]
pub fn extension<T>(mut self, extension: T) -> Self
where
T: Clone + Any + Send + Sync + 'static,
{
self.builder = self.builder.extension(extension);
self
}
#[must_use]
pub fn extensions_mut(&mut self) -> Option<&mut Extensions> {
self.builder.extensions_mut()
}
#[must_use]
#[cfg(feature = "typed-header")]
#[cfg_attr(docsrs, doc(cfg(feature = "typed-header")))]
pub fn typed_header<T>(mut self, header: T) -> Self
where
T: headers::Header,
{
use super::RequestBuilderExt as _;
self.builder = self.builder.typed_header(header);
self
}
pub fn body<NewReqBody>(
self,
body: impl Into<NewReqBody>,
) -> Result<ClientRequest<'a, S, Err, NewReqBody, RespBody>, http::Error> {
Ok(ClientRequest {
service: self.service,
request: self.builder.body(body.into())?,
_phantom: PhantomData,
})
}
#[allow(clippy::missing_panics_doc)]
pub fn without_body(self) -> ClientRequest<'a, S, Err, Bytes, RespBody> {
ClientRequest {
service: self.service,
request: self
.builder
.body(Bytes::default())
.expect("failed to build request without a body"),
_phantom: PhantomData,
}
}
#[doc = include_str!("../../examples/send_json.rs")]
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub fn json<T: serde::Serialize + ?Sized>(
self,
value: &T,
) -> Result<
ClientRequest<'a, S, Err, bytes::Bytes, RespBody>,
super::request_ext::SetBodyError<serde_json::Error>,
> {
use super::RequestBuilderExt as _;
Ok(ClientRequest {
service: self.service,
request: self.builder.json(value)?,
_phantom: PhantomData,
})
}
#[doc = include_str!("../../examples/send_form.rs")]
#[cfg(feature = "form")]
#[cfg_attr(docsrs, doc(cfg(feature = "form")))]
pub fn form<T: serde::Serialize + ?Sized>(
self,
form: &T,
) -> Result<
ClientRequest<'a, S, Err, Bytes, RespBody>,
super::request_ext::SetBodyError<serde_urlencoded::ser::Error>,
> {
use super::RequestBuilderExt as _;
Ok(ClientRequest {
service: self.service,
request: self.builder.form(form)?,
_phantom: PhantomData,
})
}
#[cfg(feature = "query")]
#[cfg_attr(docsrs, doc(cfg(feature = "query")))]
pub fn query<T: serde::Serialize + ?Sized>(
mut self,
value: &T,
) -> Result<Self, serde_urlencoded::ser::Error> {
use super::RequestBuilderExt as _;
self.builder = self.builder.query(value)?;
Ok(self)
}
}
impl<S, Err, RespBody> std::fmt::Debug for ClientRequestBuilder<'_, S, Err, RespBody> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClientRequestBuilder")
.field("builder", &self.builder)
.finish_non_exhaustive()
}
}
impl<S, Err, RespBody> From<ClientRequestBuilder<'_, S, Err, RespBody>> for http::request::Builder {
fn from(builder: ClientRequestBuilder<'_, S, Err, RespBody>) -> Self {
builder.builder
}
}
pub struct ClientRequest<'a, S, Err, ReqBody, RespBody> {
service: &'a mut S,
request: http::Request<ReqBody>,
_phantom: PhantomData<(Err, RespBody)>,
}
impl<'a, S, Err, RespBody> ClientRequest<'a, S, Err, (), RespBody> {
pub fn builder(service: &'a mut S) -> ClientRequestBuilder<'a, S, Err, RespBody> {
ClientRequestBuilder {
service,
builder: http::Request::builder(),
_phantom: PhantomData,
}
}
}
impl<'a, S, Err, RespBody> ClientRequestBuilder<'a, S, Err, RespBody> {
pub fn send<ReqBody>(
self,
) -> impl Future<Output = Result<http::Response<RespBody>, Err>> + use<'a, S, Err, RespBody, ReqBody>
where
S: Service<http::Request<ReqBody>, Response = http::Response<RespBody>, Error = Err>,
S::Future: Send + 'static,
S::Error: 'static,
ReqBody: From<Bytes>,
{
self.without_body().send::<ReqBody>()
}
}
impl<'a, S, Err, ReqBody, RespBody> ClientRequest<'a, S, Err, ReqBody, RespBody> {
pub fn send<R>(
self,
) -> impl Future<Output = Result<http::Response<RespBody>, Err>>
+ use<'a, S, Err, ReqBody, RespBody, R>
where
S: Service<http::Request<R>, Response = http::Response<RespBody>, Error = Err>,
S::Future: Send + 'static,
S::Error: 'static,
R: From<ReqBody>,
{
self.service.execute(self.request)
}
}
impl<S, Err, ReqBody, RespBody> std::fmt::Debug for ClientRequest<'_, S, Err, ReqBody, RespBody>
where
ReqBody: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClientRequest")
.field("request", &self.request)
.finish_non_exhaustive()
}
}
impl<S, Err, ReqBody, RespBody> From<ClientRequest<'_, S, Err, ReqBody, RespBody>>
for http::Request<ReqBody>
{
fn from(request: ClientRequest<'_, S, Err, ReqBody, RespBody>) -> Self {
request.request
}
}
#[cfg(test)]
mod tests {
use http::Method;
use reqwest::Client;
use tower::ServiceBuilder;
use tower_reqwest::HttpClientLayer;
use crate::ServiceExt as _;
#[test]
fn test_service_ext_request_builder_methods() {
let mut fake_client = ServiceBuilder::new()
.layer(HttpClientLayer)
.service(Client::new());
assert_eq!(
fake_client
.get("http://localhost")
.without_body()
.request
.method(),
Method::GET
);
assert_eq!(
fake_client
.post("http://localhost")
.without_body()
.request
.method(),
Method::POST
);
assert_eq!(
fake_client
.put("http://localhost")
.without_body()
.request
.method(),
Method::PUT
);
assert_eq!(
fake_client
.patch("http://localhost")
.without_body()
.request
.method(),
Method::PATCH
);
assert_eq!(
fake_client
.delete("http://localhost")
.without_body()
.request
.method(),
Method::DELETE
);
assert_eq!(
fake_client
.head("http://localhost")
.without_body()
.request
.method(),
Method::HEAD
);
}
}