Skip to main content

tower_http_client/client/
client_request.rs

1//! Useful utilities for constructing HTTP requests.
2
3use 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
11/// An [`http::Request`] builder.
12///
13/// Generally, this builder copies the behavior of the [`http::request::Builder`],
14/// but unlike it, this builder contains a reference to the client and is able to send a
15/// constructed request. Also, this builder borrows most useful methods from the [`reqwest`] one.
16///
17/// [`reqwest`]: https://docs.rs/reqwest/latest/reqwest/struct.RequestBuilder.html
18pub 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    /// Sets the HTTP method for this request.
26    ///
27    /// By default this is `GET`.
28    #[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    /// Sets the URI for this request
39    ///
40    /// By default this is `/`.
41    #[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    /// Set the HTTP version for this request.
52    ///
53    /// By default this is HTTP/1.1.
54    #[must_use]
55    pub fn version(mut self, version: Version) -> Self {
56        self.builder = self.builder.version(version);
57        self
58    }
59
60    /// Appends a header to this request.
61    ///
62    /// This function will append the provided key/value as a header to the
63    /// internal [`HeaderMap`] being constructed.  Essentially this is
64    /// equivalent to calling [`HeaderMap::append`].
65    #[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    /// Returns a mutable reference to headers of this request builder.
78    ///
79    /// If builder contains error returns `None`.
80    pub fn headers_mut(&mut self) -> Option<&mut HeaderMap<HeaderValue>> {
81        self.builder.headers_mut()
82    }
83
84    /// Adds an extension to this builder.
85    #[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    /// Returns a mutable reference to the extensions of this request builder.
95    ///
96    /// If builder contains error returns `None`.
97    #[must_use]
98    pub fn extensions_mut(&mut self) -> Option<&mut Extensions> {
99        self.builder.extensions_mut()
100    }
101
102    /// Appends a typed header to this request.
103    ///
104    /// This function will append the provided header as a header to the
105    /// internal [`HeaderMap`] being constructed.  Essentially this is
106    /// equivalent to calling [`headers::HeaderMapExt::typed_insert`].
107    #[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    /// "Consumes" this builder, using the provided `body` to return a
121    /// constructed [`ClientRequest`].
122    ///
123    /// # Errors
124    ///
125    /// Same as the [`http::request::Builder::body`]
126    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    /// Consumes this builder and returns a constructed request without a body.
138    ///
139    /// # Errors
140    ///
141    /// If erroneous data was passed during the query building process.
142    #[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    /// Sets a JSON body for this request.
155    ///
156    /// Additionally this method adds a `CONTENT_TYPE` header for JSON body.
157    /// If you decide to override the request body, keep this in mind.
158    ///
159    /// # Errors
160    ///
161    /// If the given value's implementation of [`serde::Serialize`] decides to fail.
162    ///
163    /// # Examples
164    ///
165    /// ```
166    #[doc = include_str!("../../examples/send_json.rs")]
167    /// ```
168    #[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    /// Sets a form body for this request.
187    ///
188    /// Additionally this method adds a `CONTENT_TYPE` header for form body.
189    /// If you decide to override the request body, keep this in mind.
190    ///
191    /// # Errors
192    ///
193    /// If the given value's implementation of [`serde::Serialize`] decides to fail.
194    ///
195    /// # Examples
196    ///
197    /// ```
198    #[doc = include_str!("../../examples/send_form.rs")]
199    /// ```
200    #[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    /// Sets the query string of the URL.
219    ///
220    /// Serializes the given value into a query string using [`serde_urlencoded`]
221    /// and replaces the existing query string of the URL entirely. Any previously
222    /// set query parameters are discarded.
223    ///
224    /// # Notes
225    ///
226    /// - Duplicate keys are preserved as-is:
227    ///   `.query(&[("foo", "a"), ("foo", "b")])` produces `"foo=a&foo=b"`.
228    ///
229    /// - This method does not support a single key-value tuple directly.
230    ///   Use a slice like `.query(&[("key", "val")])` instead.
231    ///   Structs and maps that serialize into key-value pairs are also supported.
232    ///
233    /// # Errors
234    ///
235    /// Returns a [`serde_urlencoded::ser::Error`] if the provided value cannot be serialized
236    /// into a query string.
237    #[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
264/// An [`http::Request`] wrapper with a reference to a client.
265///
266/// This struct is used to send constructed HTTP request by using a client.
267pub 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    /// Creates a client request builder.
275    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    /// Sends the request to the target URI without a body.
286    ///
287    /// This is a shorthand for `self.without_body().send()`. The service's
288    /// request body type must implement `From<Bytes>`.
289    ///
290    /// # Panics
291    ///
292    /// - if erroneous data was passed during the query building process.
293    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    /// Sends the request to the target URI.
308    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    // Check that client request builder uses proper methods.
351    #[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}