1use std::{any::Any, future::Future, marker::PhantomData};
4
5use bytes::Bytes;
6use http::{Extensions, HeaderMap, HeaderName, HeaderValue, Method, Uri, Version};
7use tower_service::Service;
8
9use super::{IntoUri, ServiceExt as _};
10
11pub struct ClientRequestBuilder<'a, S, Err, RespBody> {
19 service: &'a mut S,
20 builder: http::request::Builder,
21 _phantom: PhantomData<(Err, RespBody)>,
22}
23
24impl<'a, S, Err, RespBody> ClientRequestBuilder<'a, S, Err, RespBody> {
25 #[must_use]
29 pub fn method<T>(mut self, method: T) -> Self
30 where
31 Method: TryFrom<T>,
32 <Method as TryFrom<T>>::Error: Into<http::Error>,
33 {
34 self.builder = self.builder.method(method);
35 self
36 }
37
38 #[must_use]
42 pub fn uri<U: IntoUri>(mut self, uri: U) -> Self
43 where
44 Uri: TryFrom<U::TryInto>,
45 <Uri as TryFrom<U::TryInto>>::Error: Into<http::Error>,
46 {
47 self.builder = self.builder.uri(uri.into_uri());
48 self
49 }
50
51 #[must_use]
55 pub fn version(mut self, version: Version) -> Self {
56 self.builder = self.builder.version(version);
57 self
58 }
59
60 #[must_use]
66 pub fn header<K, V>(mut self, key: K, value: V) -> Self
67 where
68 HeaderName: TryFrom<K>,
69 HeaderValue: TryFrom<V>,
70 <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
71 <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
72 {
73 self.builder = self.builder.header(key, value);
74 self
75 }
76
77 pub fn headers_mut(&mut self) -> Option<&mut HeaderMap<HeaderValue>> {
81 self.builder.headers_mut()
82 }
83
84 #[must_use]
86 pub fn extension<T>(mut self, extension: T) -> Self
87 where
88 T: Clone + Any + Send + Sync + 'static,
89 {
90 self.builder = self.builder.extension(extension);
91 self
92 }
93
94 #[must_use]
98 pub fn extensions_mut(&mut self) -> Option<&mut Extensions> {
99 self.builder.extensions_mut()
100 }
101
102 #[must_use]
108 #[cfg(feature = "typed-header")]
109 #[cfg_attr(docsrs, doc(cfg(feature = "typed-header")))]
110 pub fn typed_header<T>(mut self, header: T) -> Self
111 where
112 T: headers::Header,
113 {
114 use super::RequestBuilderExt as _;
115
116 self.builder = self.builder.typed_header(header);
117 self
118 }
119
120 pub fn body<NewReqBody>(
127 self,
128 body: impl Into<NewReqBody>,
129 ) -> Result<ClientRequest<'a, S, Err, NewReqBody, RespBody>, http::Error> {
130 Ok(ClientRequest {
131 service: self.service,
132 request: self.builder.body(body.into())?,
133 _phantom: PhantomData,
134 })
135 }
136
137 #[allow(clippy::missing_panics_doc)]
143 pub fn without_body(self) -> ClientRequest<'a, S, Err, Bytes, RespBody> {
144 ClientRequest {
145 service: self.service,
146 request: self
147 .builder
148 .body(Bytes::default())
149 .expect("failed to build request without a body"),
150 _phantom: PhantomData,
151 }
152 }
153
154 #[doc = include_str!("../../examples/send_json.rs")]
167 #[cfg(feature = "json")]
169 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
170 pub fn json<T: serde::Serialize + ?Sized>(
171 self,
172 value: &T,
173 ) -> Result<
174 ClientRequest<'a, S, Err, bytes::Bytes, RespBody>,
175 super::request_ext::SetBodyError<serde_json::Error>,
176 > {
177 use super::RequestBuilderExt as _;
178
179 Ok(ClientRequest {
180 service: self.service,
181 request: self.builder.json(value)?,
182 _phantom: PhantomData,
183 })
184 }
185
186 #[doc = include_str!("../../examples/send_form.rs")]
199 #[cfg(feature = "form")]
201 #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
202 pub fn form<T: serde::Serialize + ?Sized>(
203 self,
204 form: &T,
205 ) -> Result<
206 ClientRequest<'a, S, Err, Bytes, RespBody>,
207 super::request_ext::SetBodyError<serde_urlencoded::ser::Error>,
208 > {
209 use super::RequestBuilderExt as _;
210
211 Ok(ClientRequest {
212 service: self.service,
213 request: self.builder.form(form)?,
214 _phantom: PhantomData,
215 })
216 }
217
218 #[cfg(feature = "query")]
238 #[cfg_attr(docsrs, doc(cfg(feature = "query")))]
239 pub fn query<T: serde::Serialize + ?Sized>(
240 mut self,
241 value: &T,
242 ) -> Result<Self, serde_urlencoded::ser::Error> {
243 use super::RequestBuilderExt as _;
244
245 self.builder = self.builder.query(value)?;
246 Ok(self)
247 }
248}
249
250impl<S, Err, RespBody> std::fmt::Debug for ClientRequestBuilder<'_, S, Err, RespBody> {
251 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 f.debug_struct("ClientRequestBuilder")
253 .field("builder", &self.builder)
254 .finish_non_exhaustive()
255 }
256}
257
258impl<S, Err, RespBody> From<ClientRequestBuilder<'_, S, Err, RespBody>> for http::request::Builder {
259 fn from(builder: ClientRequestBuilder<'_, S, Err, RespBody>) -> Self {
260 builder.builder
261 }
262}
263
264pub struct ClientRequest<'a, S, Err, ReqBody, RespBody> {
268 service: &'a mut S,
269 request: http::Request<ReqBody>,
270 _phantom: PhantomData<(Err, RespBody)>,
271}
272
273impl<'a, S, Err, RespBody> ClientRequest<'a, S, Err, (), RespBody> {
274 pub fn builder(service: &'a mut S) -> ClientRequestBuilder<'a, S, Err, RespBody> {
276 ClientRequestBuilder {
277 service,
278 builder: http::Request::builder(),
279 _phantom: PhantomData,
280 }
281 }
282}
283
284impl<'a, S, Err, RespBody> ClientRequestBuilder<'a, S, Err, RespBody> {
285 pub fn send<ReqBody>(
294 self,
295 ) -> impl Future<Output = Result<http::Response<RespBody>, Err>> + use<'a, S, Err, RespBody, ReqBody>
296 where
297 S: Service<http::Request<ReqBody>, Response = http::Response<RespBody>, Error = Err>,
298 S::Future: Send + 'static,
299 S::Error: 'static,
300 ReqBody: From<Bytes>,
301 {
302 self.without_body().send::<ReqBody>()
303 }
304}
305
306impl<'a, S, Err, ReqBody, RespBody> ClientRequest<'a, S, Err, ReqBody, RespBody> {
307 pub fn send<R>(
309 self,
310 ) -> impl Future<Output = Result<http::Response<RespBody>, Err>>
311 + use<'a, S, Err, ReqBody, RespBody, R>
312 where
313 S: Service<http::Request<R>, Response = http::Response<RespBody>, Error = Err>,
314 S::Future: Send + 'static,
315 S::Error: 'static,
316 R: From<ReqBody>,
317 {
318 self.service.execute(self.request)
319 }
320}
321
322impl<S, Err, ReqBody, RespBody> std::fmt::Debug for ClientRequest<'_, S, Err, ReqBody, RespBody>
323where
324 ReqBody: std::fmt::Debug,
325{
326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 f.debug_struct("ClientRequest")
328 .field("request", &self.request)
329 .finish_non_exhaustive()
330 }
331}
332
333impl<S, Err, ReqBody, RespBody> From<ClientRequest<'_, S, Err, ReqBody, RespBody>>
334 for http::Request<ReqBody>
335{
336 fn from(request: ClientRequest<'_, S, Err, ReqBody, RespBody>) -> Self {
337 request.request
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use http::Method;
344 use reqwest::Client;
345 use tower::ServiceBuilder;
346 use tower_reqwest::HttpClientLayer;
347
348 use crate::ServiceExt as _;
349
350 #[test]
352 fn test_service_ext_request_builder_methods() {
353 let mut fake_client = ServiceBuilder::new()
354 .layer(HttpClientLayer)
355 .service(Client::new());
356
357 assert_eq!(
358 fake_client
359 .get("http://localhost")
360 .without_body()
361 .request
362 .method(),
363 Method::GET
364 );
365 assert_eq!(
366 fake_client
367 .post("http://localhost")
368 .without_body()
369 .request
370 .method(),
371 Method::POST
372 );
373 assert_eq!(
374 fake_client
375 .put("http://localhost")
376 .without_body()
377 .request
378 .method(),
379 Method::PUT
380 );
381 assert_eq!(
382 fake_client
383 .patch("http://localhost")
384 .without_body()
385 .request
386 .method(),
387 Method::PATCH
388 );
389 assert_eq!(
390 fake_client
391 .delete("http://localhost")
392 .without_body()
393 .request
394 .method(),
395 Method::DELETE
396 );
397 assert_eq!(
398 fake_client
399 .head("http://localhost")
400 .without_body()
401 .request
402 .method(),
403 Method::HEAD
404 );
405 }
406}