1use 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
14pub struct ClientRequestBuilder<'a, S, Err, RespBody> {
22 service: &'a mut S,
23 builder: http::request::Builder,
24 _phantom: PhantomData<(Err, RespBody)>,
25}
26
27impl<'a, S, Err, RespBody> ClientRequestBuilder<'a, S, Err, RespBody> {
28 #[must_use]
32 pub fn method<T>(mut self, method: T) -> Self
33 where
34 Method: TryFrom<T>,
35 <Method as TryFrom<T>>::Error: Into<http::Error>,
36 {
37 self.builder = self.builder.method(method);
38 self
39 }
40
41 #[must_use]
45 pub fn uri<U: IntoUri>(mut self, uri: U) -> Self
46 where
47 Uri: TryFrom<U::TryInto>,
48 <Uri as TryFrom<U::TryInto>>::Error: Into<http::Error>,
49 {
50 self.builder = self.builder.uri(uri.into_uri());
51 self
52 }
53
54 #[must_use]
58 pub fn version(mut self, version: Version) -> Self {
59 self.builder = self.builder.version(version);
60 self
61 }
62
63 #[must_use]
69 pub fn header<K, V>(mut self, key: K, value: V) -> Self
70 where
71 HeaderName: TryFrom<K>,
72 HeaderValue: TryFrom<V>,
73 <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
74 <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
75 {
76 self.builder = self.builder.header(key, value);
77 self
78 }
79
80 pub fn headers_mut(&mut self) -> Option<&mut HeaderMap<HeaderValue>> {
84 self.builder.headers_mut()
85 }
86
87 #[must_use]
89 pub fn extension<T>(mut self, extension: T) -> Self
90 where
91 T: Clone + Any + Send + Sync + 'static,
92 {
93 self.builder = self.builder.extension(extension);
94 self
95 }
96
97 #[must_use]
101 pub fn extensions_mut(&mut self) -> Option<&mut Extensions> {
102 self.builder.extensions_mut()
103 }
104
105 pub fn body<NewReqBody>(
112 self,
113 body: impl Into<NewReqBody>,
114 ) -> Result<ClientRequest<'a, S, Err, NewReqBody, RespBody>, http::Error> {
115 Ok(ClientRequest {
116 service: self.service,
117 request: self.builder.body(body.into())?,
118 _phantom: PhantomData,
119 })
120 }
121
122 #[doc = include_str!("../../examples/send_json.rs")]
135 #[cfg(feature = "json")]
137 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
138 pub fn json<T: serde::Serialize + ?Sized>(
139 self,
140 value: &T,
141 ) -> Result<
142 ClientRequest<'a, S, Err, bytes::Bytes, RespBody>,
143 super::request_ext::SetBodyError<serde_json::Error>,
144 > {
145 use super::RequestBuilderExt as _;
146
147 Ok(ClientRequest {
148 service: self.service,
149 request: self.builder.json(value)?,
150 _phantom: PhantomData,
151 })
152 }
153
154 #[doc = include_str!("../../examples/send_form.rs")]
167 #[cfg(feature = "form")]
169 #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
170 pub fn form<T: serde::Serialize + ?Sized>(
171 self,
172 form: &T,
173 ) -> Result<
174 ClientRequest<'a, S, Err, String, RespBody>,
175 super::request_ext::SetBodyError<serde_urlencoded::ser::Error>,
176 > {
177 use super::RequestBuilderExt as _;
178
179 Ok(ClientRequest {
180 service: self.service,
181 request: self.builder.form(form)?,
182 _phantom: PhantomData,
183 })
184 }
185
186 #[must_use]
192 #[cfg(feature = "typed-header")]
193 #[cfg_attr(docsrs, doc(cfg(feature = "typed_header")))]
194 pub fn typed_header<T>(mut self, header: T) -> Self
195 where
196 T: headers::Header,
197 {
198 use super::RequestBuilderExt as _;
199
200 self.builder = self.builder.typed_header(header);
201 self
202 }
203
204 #[allow(clippy::missing_panics_doc)]
210 pub fn build(self) -> ClientRequest<'a, S, Err, EmptyBody, RespBody> {
211 ClientRequest {
212 service: self.service,
213 request: self
214 .builder
215 .body(EMPTY_BODY)
216 .expect("failed to build request without a body"),
217 _phantom: PhantomData,
218 }
219 }
220}
221
222impl<S, Err, RespBody> std::fmt::Debug for ClientRequestBuilder<'_, S, Err, RespBody> {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 f.debug_struct("ClientRequestBuilder")
225 .field("builder", &self.builder)
226 .finish_non_exhaustive()
227 }
228}
229
230impl<S, Err, RespBody> From<ClientRequestBuilder<'_, S, Err, RespBody>> for http::request::Builder {
231 fn from(builder: ClientRequestBuilder<'_, S, Err, RespBody>) -> Self {
232 builder.builder
233 }
234}
235
236pub struct ClientRequest<'a, S, Err, ReqBody, RespBody> {
240 service: &'a mut S,
241 request: http::Request<ReqBody>,
242 _phantom: PhantomData<(Err, RespBody)>,
243}
244
245impl<'a, S, Err, RespBody> ClientRequest<'a, S, Err, (), RespBody> {
246 pub fn builder(service: &'a mut S) -> ClientRequestBuilder<'a, S, Err, RespBody> {
248 ClientRequestBuilder {
249 service,
250 builder: http::Request::builder(),
251 _phantom: PhantomData,
252 }
253 }
254}
255
256#[doc(hidden)]
259pub trait Captures<U> {}
260
261impl<T: ?Sized, U> Captures<U> for T {}
262
263impl<'a, S, Err, RespBody> ClientRequestBuilder<'a, S, Err, RespBody> {
264 pub fn send<ReqBody>(
270 self,
271 ) -> impl Future<Output = Result<http::Response<RespBody>, Err>> + Captures<&'a ()>
272 where
273 S: Service<http::Request<ReqBody>, Response = http::Response<RespBody>, Error = Err>,
274 S::Future: Send + 'static,
275 S::Error: 'static,
276 ReqBody: Default,
277 {
278 let request = self
279 .builder
280 .body(ReqBody::default())
281 .expect("failed to build request without a body");
282 self.service.execute(request)
283 }
284}
285
286impl<'a, S, Err, ReqBody, RespBody> ClientRequest<'a, S, Err, ReqBody, RespBody> {
287 pub fn send<R>(
289 self,
290 ) -> impl Future<Output = Result<http::Response<RespBody>, Err>> + Captures<&'a ()>
291 where
292 S: Service<http::Request<R>, Response = http::Response<RespBody>, Error = Err>,
293 S::Future: Send + 'static,
294 S::Error: 'static,
295 R: From<ReqBody>,
296 {
297 self.service.execute(self.request)
298 }
299}
300
301impl<S, Err, ReqBody, RespBody> std::fmt::Debug for ClientRequest<'_, S, Err, ReqBody, RespBody>
302where
303 ReqBody: std::fmt::Debug,
304{
305 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306 f.debug_struct("ClientRequest")
307 .field("request", &self.request)
308 .finish_non_exhaustive()
309 }
310}
311
312impl<S, Err, ReqBody, RespBody> From<ClientRequest<'_, S, Err, ReqBody, RespBody>>
313 for http::Request<ReqBody>
314{
315 fn from(request: ClientRequest<'_, S, Err, ReqBody, RespBody>) -> Self {
316 request.request
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use http::Method;
323 use reqwest::Client;
324 use tower::ServiceBuilder;
325 use tower_reqwest::HttpClientLayer;
326
327 use crate::ServiceExt as _;
328
329 #[test]
331 fn test_service_ext_request_builder_methods() {
332 let mut fake_client = ServiceBuilder::new()
333 .layer(HttpClientLayer)
334 .service(Client::new());
335
336 assert_eq!(
337 fake_client.get("http://localhost").build().request.method(),
338 Method::GET
339 );
340 assert_eq!(
341 fake_client
342 .post("http://localhost")
343 .build()
344 .request
345 .method(),
346 Method::POST
347 );
348 assert_eq!(
349 fake_client.put("http://localhost").build().request.method(),
350 Method::PUT
351 );
352 assert_eq!(
353 fake_client
354 .patch("http://localhost")
355 .build()
356 .request
357 .method(),
358 Method::PATCH
359 );
360 assert_eq!(
361 fake_client
362 .delete("http://localhost")
363 .build()
364 .request
365 .method(),
366 Method::DELETE
367 );
368 assert_eq!(
369 fake_client
370 .head("http://localhost")
371 .build()
372 .request
373 .method(),
374 Method::HEAD
375 );
376 }
377}