tower_http_client/client/
client_request.rs1use std::{any::Any, future::Future, marker::PhantomData};
4
5use http::{Extensions, HeaderMap, HeaderName, HeaderValue, Method, Uri, Version};
6use tower_service::Service;
7
8use super::{IntoUri, ServiceExt as _};
9
10type EmptyBody = ();
11
12const EMPTY_BODY: EmptyBody = ();
13
14#[derive(Debug)]
22pub struct ClientRequestBuilder<'a, S, Err, RespBody> {
23 service: &'a mut S,
24 builder: http::request::Builder,
25 _phantom: PhantomData<(Err, RespBody)>,
26}
27
28impl<'a, S, Err, RespBody> ClientRequestBuilder<'a, S, Err, RespBody> {
29 #[must_use]
33 pub fn method<T>(mut self, method: T) -> Self
34 where
35 Method: TryFrom<T>,
36 <Method as TryFrom<T>>::Error: Into<http::Error>,
37 {
38 self.builder = self.builder.method(method);
39 self
40 }
41
42 #[must_use]
46 pub fn uri<U: IntoUri>(mut self, uri: U) -> Self
47 where
48 Uri: TryFrom<U::TryInto>,
49 <Uri as TryFrom<U::TryInto>>::Error: Into<http::Error>,
50 {
51 self.builder = self.builder.uri(uri.into_uri());
52 self
53 }
54
55 #[must_use]
59 pub fn version(mut self, version: Version) -> Self {
60 self.builder = self.builder.version(version);
61 self
62 }
63
64 #[must_use]
70 pub fn header<K, V>(mut self, key: K, value: V) -> Self
71 where
72 HeaderName: TryFrom<K>,
73 HeaderValue: TryFrom<V>,
74 <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
75 <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
76 {
77 self.builder = self.builder.header(key, value);
78 self
79 }
80
81 pub fn headers_mut(&mut self) -> Option<&mut HeaderMap<HeaderValue>> {
85 self.builder.headers_mut()
86 }
87
88 #[must_use]
90 pub fn extension<T>(mut self, extension: T) -> Self
91 where
92 T: Clone + Any + Send + Sync + 'static,
93 {
94 self.builder = self.builder.extension(extension);
95 self
96 }
97
98 #[must_use]
102 pub fn extensions_mut(&mut self) -> Option<&mut Extensions> {
103 self.builder.extensions_mut()
104 }
105
106 pub fn body<NewReqBody>(
113 self,
114 body: impl Into<NewReqBody>,
115 ) -> Result<ClientRequest<'a, S, Err, NewReqBody, RespBody>, http::Error> {
116 Ok(ClientRequest {
117 service: self.service,
118 request: self.builder.body(body.into())?,
119 _phantom: PhantomData,
120 })
121 }
122
123 #[doc = include_str!("../../examples/send_json.rs")]
136 #[cfg(feature = "json")]
138 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
139 pub fn json<T: serde::Serialize + ?Sized>(
140 self,
141 value: &T,
142 ) -> Result<
143 ClientRequest<'a, S, Err, bytes::Bytes, RespBody>,
144 super::request_ext::SetBodyError<serde_json::Error>,
145 > {
146 use super::RequestBuilderExt as _;
147
148 Ok(ClientRequest {
149 service: self.service,
150 request: self.builder.json(value)?,
151 _phantom: PhantomData,
152 })
153 }
154
155 #[doc = include_str!("../../examples/send_form.rs")]
168 #[cfg(feature = "form")]
170 #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
171 pub fn form<T: serde::Serialize + ?Sized>(
172 self,
173 form: &T,
174 ) -> Result<
175 ClientRequest<'a, S, Err, String, RespBody>,
176 super::request_ext::SetBodyError<serde_urlencoded::ser::Error>,
177 > {
178 use super::RequestBuilderExt as _;
179
180 Ok(ClientRequest {
181 service: self.service,
182 request: self.builder.form(form)?,
183 _phantom: PhantomData,
184 })
185 }
186
187 #[must_use]
193 #[cfg(feature = "typed-header")]
194 #[cfg_attr(docsrs, doc(cfg(feature = "typed_header")))]
195 pub fn typed_header<T>(mut self, header: T) -> Self
196 where
197 T: headers::Header,
198 {
199 use super::RequestBuilderExt as _;
200
201 self.builder = self.builder.typed_header(header);
202 self
203 }
204
205 #[allow(clippy::missing_panics_doc)]
211 pub fn build(self) -> ClientRequest<'a, S, Err, EmptyBody, RespBody> {
212 ClientRequest {
213 service: self.service,
214 request: self
215 .builder
216 .body(EMPTY_BODY)
217 .expect("failed to build request without a body"),
218 _phantom: PhantomData,
219 }
220 }
221}
222
223#[derive(Debug)]
227pub struct ClientRequest<'a, S, Err, ReqBody, RespBody> {
228 service: &'a mut S,
229 request: http::Request<ReqBody>,
230 _phantom: PhantomData<(Err, RespBody)>,
231}
232
233impl<'a, S, Err, RespBody> ClientRequest<'a, S, Err, (), RespBody> {
234 pub fn builder(service: &'a mut S) -> ClientRequestBuilder<'a, S, Err, RespBody> {
236 ClientRequestBuilder {
237 service,
238 builder: http::Request::builder(),
239 _phantom: PhantomData,
240 }
241 }
242}
243
244#[doc(hidden)]
247pub trait Captures<U> {}
248
249impl<T: ?Sized, U> Captures<U> for T {}
250
251impl<'a, S, Err, RespBody> ClientRequestBuilder<'a, S, Err, RespBody> {
252 pub fn send<ReqBody>(
258 self,
259 ) -> impl Future<Output = Result<http::Response<RespBody>, Err>> + Captures<&'a ()>
260 where
261 S: Service<http::Request<ReqBody>, Response = http::Response<RespBody>, Error = Err>,
262 S::Future: Send + 'static,
263 S::Error: 'static,
264 ReqBody: Default,
265 {
266 let request = self
267 .builder
268 .body(ReqBody::default())
269 .expect("failed to build request without a body");
270 self.service.execute(request)
271 }
272}
273
274impl<'a, S, Err, R, RespBody> ClientRequest<'a, S, Err, R, RespBody> {
275 pub fn send<ReqBody>(
277 self,
278 ) -> impl Future<Output = Result<http::Response<RespBody>, Err>> + Captures<&'a ()>
279 where
280 S: Service<http::Request<ReqBody>, Response = http::Response<RespBody>, Error = Err>,
281 S::Future: Send + 'static,
282 S::Error: 'static,
283 ReqBody: From<R>,
284 {
285 self.service.execute(self.request)
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use http::Method;
292 use reqwest::Client;
293 use tower::ServiceBuilder;
294 use tower_reqwest::HttpClientLayer;
295
296 use crate::ServiceExt as _;
297
298 #[test]
300 fn test_service_ext_request_builder_methods() {
301 let mut fake_client = ServiceBuilder::new()
302 .layer(HttpClientLayer)
303 .service(Client::new());
304
305 assert_eq!(
306 fake_client.get("http://localhost").build().request.method(),
307 Method::GET
308 );
309 assert_eq!(
310 fake_client
311 .post("http://localhost")
312 .build()
313 .request
314 .method(),
315 Method::POST
316 );
317 assert_eq!(
318 fake_client.put("http://localhost").build().request.method(),
319 Method::PUT
320 );
321 assert_eq!(
322 fake_client
323 .patch("http://localhost")
324 .build()
325 .request
326 .method(),
327 Method::PATCH
328 );
329 assert_eq!(
330 fake_client
331 .delete("http://localhost")
332 .build()
333 .request
334 .method(),
335 Method::DELETE
336 );
337 assert_eq!(
338 fake_client
339 .head("http://localhost")
340 .build()
341 .request
342 .method(),
343 Method::HEAD
344 );
345 }
346}