li_surf/request_builder.rs
1use crate::http::{
2 headers::{HeaderName, ToHeaderValues},
3 Body, Method, Mime, Url,
4};
5use crate::middleware::Middleware;
6use crate::{Client, Error, Request, Response, Result};
7
8use futures_util::future::BoxFuture;
9use serde::Serialize;
10
11use std::fmt;
12use std::future::Future;
13use std::pin::Pin;
14use std::task::{Context, Poll};
15
16/// Request Builder
17///
18/// Provides an ergonomic way to chain the creation of a request.
19/// This is generally accessed as the return value from `surf::{method}()`,
20/// however [`Request::builder`](crate::Request::builder) is also provided.
21///
22/// # Examples
23///
24/// ```rust
25/// use surf::http::{Method, mime::HTML, Url};
26/// # #[async_std::main]
27/// # async fn main() -> surf::Result<()> {
28/// let mut request = surf::post("https://httpbin.org/post")
29/// .body("<html>hi</html>")
30/// .header("custom-header", "value")
31/// .content_type(HTML)
32/// .build();
33///
34/// assert_eq!(request.take_body().into_string().await.unwrap(), "<html>hi</html>");
35/// assert_eq!(request.method(), Method::Post);
36/// assert_eq!(request.url(), &Url::parse("https://httpbin.org/post")?);
37/// assert_eq!(request["custom-header"], "value");
38/// assert_eq!(request["content-type"], "text/html;charset=utf-8");
39/// # Ok(())
40/// # }
41/// ```
42///
43/// ```rust
44/// use surf::http::{Method, Url};
45/// # #[async_std::main]
46/// # async fn main() -> surf::Result<()> {
47/// let url = Url::parse("https://httpbin.org/post")?;
48/// let request = surf::Request::builder(Method::Post, url).build();
49/// # Ok(())
50/// # }
51/// ```
52
53pub struct RequestBuilder {
54 /// Holds the state of the request.
55 req: Option<Request>,
56 /// Hold an optional Client.
57 client: Option<Client>,
58 /// Holds the state of the `impl Future`.
59 fut: Option<BoxFuture<'static, Result<Response>>>,
60}
61
62impl RequestBuilder {
63 /// Create a new instance.
64 ///
65 /// This method is particularly useful when input URLs might be passed by third parties, and
66 /// you don't want to panic if they're malformed. If URLs are statically encoded, it might be
67 /// easier to use one of the shorthand methods instead.
68 ///
69 /// # Examples
70 ///
71 /// ```no_run
72 /// # #[async_std::main]
73 /// # async fn main() -> surf::Result<()> {
74 /// use surf::http::{Method, Url};
75 ///
76 /// let url = Url::parse("https://httpbin.org/get")?;
77 /// let req = surf::RequestBuilder::new(Method::Get, url).build();
78 /// # Ok(()) }
79 /// ```
80 pub fn new(method: Method, url: Url) -> Self {
81 Self {
82 req: Some(Request::new(method, url)),
83 client: None,
84 fut: None,
85 }
86 }
87
88 pub(crate) fn with_client(mut self, client: Client) -> Self {
89 let req = self.req.as_mut().unwrap();
90
91 for (header_name, header_values) in client.config().headers.iter() {
92 req.append_header(header_name, header_values);
93 }
94
95 self.client = Some(client);
96 self
97 }
98
99 /// Sets a header on the request.
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// let req = surf::get("https://httpbin.org/get").header("header-name", "header-value").build();
105 /// assert_eq!(req["header-name"], "header-value");
106 /// ```
107 pub fn header(mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) -> Self {
108 self.req.as_mut().unwrap().insert_header(key, value);
109 self
110 }
111
112 /// Sets the Content-Type header on the request.
113 ///
114 /// # Examples
115 ///
116 /// ```
117 /// # use surf::http::mime;
118 /// let req = surf::post("https://httpbin.org/post").content_type(mime::HTML).build();
119 /// assert_eq!(req["content-type"], "text/html;charset=utf-8");
120 /// ```
121 pub fn content_type(mut self, content_type: impl Into<Mime>) -> Self {
122 self.req
123 .as_mut()
124 .unwrap()
125 .set_content_type(content_type.into());
126 self
127 }
128
129 /// Sets the body of the request from any type with implements `Into<Body>`, for example, any type with is `AsyncRead`.
130 /// # Mime
131 ///
132 /// The encoding is set to `application/octet-stream`.
133 ///
134 /// # Examples
135 ///
136 /// ```
137 /// # #[async_std::main]
138 /// # async fn main() -> surf::Result<()> {
139 /// use serde_json::json;
140 /// let mut req = surf::post("https://httpbin.org/post").body(json!({ "any": "Into<Body>"})).build();
141 /// assert_eq!(req.take_body().into_string().await.unwrap(), "{\"any\":\"Into<Body>\"}");
142 /// # Ok(())
143 /// # }
144 /// ```
145 pub fn body(mut self, body: impl Into<Body>) -> Self {
146 self.req.as_mut().unwrap().set_body(body);
147 self
148 }
149
150 /// Pass JSON as the request body.
151 ///
152 /// # Mime
153 ///
154 /// The encoding is set to `application/json`.
155 ///
156 /// # Errors
157 ///
158 /// This method will return an error if the provided data could not be serialized to JSON.
159 ///
160 /// # Examples
161 ///
162 /// ```no_run
163 /// # use serde::{Deserialize, Serialize};
164 /// # #[async_std::main]
165 /// # async fn main() -> surf::Result<()> {
166 /// #[derive(Deserialize, Serialize)]
167 /// struct Ip {
168 /// ip: String
169 /// }
170 ///
171 /// let uri = "https://httpbin.org/post";
172 /// let data = &Ip { ip: "129.0.0.1".into() };
173 /// let res = surf::post(uri).body_json(data)?.await?;
174 /// assert_eq!(res.status(), 200);
175 /// # Ok(()) }
176 /// ```
177 pub fn body_json(self, json: &impl Serialize) -> crate::Result<Self> {
178 Ok(self.body(Body::from_json(json)?))
179 }
180
181 /// Pass a string as the request body.
182 ///
183 /// # Mime
184 ///
185 /// The encoding is set to `text/plain; charset=utf-8`.
186 ///
187 /// # Examples
188 ///
189 /// ```no_run
190 /// # #[async_std::main]
191 /// # async fn main() -> surf::Result<()> {
192 /// let uri = "https://httpbin.org/post";
193 /// let data = "hello world".to_string();
194 /// let res = surf::post(uri).body_string(data).await?;
195 /// assert_eq!(res.status(), 200);
196 /// # Ok(()) }
197 /// ```
198 pub fn body_string(self, string: String) -> Self {
199 self.body(Body::from_string(string))
200 }
201
202 /// Pass bytes as the request body.
203 ///
204 /// # Mime
205 ///
206 /// The encoding is set to `application/octet-stream`.
207 ///
208 /// # Examples
209 ///
210 /// ```no_run
211 /// # #[async_std::main]
212 /// # async fn main() -> surf::Result<()> {
213 /// let uri = "https://httpbin.org/post";
214 /// let data = b"hello world".to_owned();
215 /// let res = surf::post(uri).body_bytes(data).await?;
216 /// assert_eq!(res.status(), 200);
217 /// # Ok(()) }
218 /// ```
219 pub fn body_bytes(self, bytes: impl AsRef<[u8]>) -> Self {
220 self.body(Body::from(bytes.as_ref()))
221 }
222
223 /// Pass a file as the request body.
224 ///
225 /// # Mime
226 ///
227 /// The encoding is set based on the file extension using [`mime_guess`] if the operation was
228 /// successful. If `path` has no extension, or its extension has no known MIME type mapping,
229 /// then `None` is returned.
230 ///
231 /// [`mime_guess`]: https://docs.rs/mime_guess
232 ///
233 /// # Errors
234 ///
235 /// This method will return an error if the file couldn't be read.
236 ///
237 /// # Examples
238 ///
239 /// ```no_run
240 /// # #[async_std::main]
241 /// # async fn main() -> surf::Result<()> {
242 /// let uri = "https://httpbin.org/post";
243 /// let res = surf::post(uri).body_file("./archive.tgz").await?.await?;
244 /// assert_eq!(res.status(), 200);
245 /// # Ok(()) }
246 /// ```
247 #[cfg(not(target_arch = "wasm32"))]
248 pub async fn body_file(self, path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
249 Ok(self.body(Body::from_file(path).await?))
250 }
251
252 /// Set the URL querystring.
253 ///
254 /// # Examples
255 ///
256 /// ```no_run
257 /// # use serde::{Deserialize, Serialize};
258 /// # #[async_std::main]
259 /// # async fn main() -> surf::Result<()> {
260 /// #[derive(Serialize, Deserialize)]
261 /// struct Index {
262 /// page: u32
263 /// }
264 ///
265 /// let query = Index { page: 2 };
266 /// let mut req = surf::get("https://httpbin.org/get").query(&query)?.build();
267 /// assert_eq!(req.url().query(), Some("page=2"));
268 /// assert_eq!(req.url().as_str(), "https://httpbin.org/get?page=2");
269 /// # Ok(()) }
270 /// ```
271 pub fn query(mut self, query: &impl Serialize) -> std::result::Result<Self, Error> {
272 self.req.as_mut().unwrap().set_query(query)?;
273
274 Ok(self)
275 }
276
277 /// Submit the request and get the response body as bytes.
278 ///
279 /// # Examples
280 ///
281 /// ```no_run
282 /// # #[async_std::main]
283 /// # async fn main() -> surf::Result<()> {
284 /// let bytes = surf::get("https://httpbin.org/get").recv_bytes().await?;
285 /// assert!(bytes.len() > 0);
286 /// # Ok(()) }
287 /// ```
288 pub async fn recv_bytes(self) -> Result<Vec<u8>> {
289 let mut res = self.send().await?;
290 res.body_bytes().await
291 }
292
293 /// Submit the request and get the response body as a string.
294 ///
295 /// # Examples
296 ///
297 /// ```no_run
298 /// # #[async_std::main]
299 /// # async fn main() -> surf::Result<()> {
300 /// let string = surf::get("https://httpbin.org/get").recv_string().await?;
301 /// assert!(string.len() > 0);
302 /// # Ok(()) }
303 /// ```
304 pub async fn recv_string(self) -> Result<String> {
305 let mut res = self.send().await?;
306 res.body_string().await
307 }
308
309 /// Submit the request and decode the response body from json into a struct.
310 ///
311 /// # Examples
312 ///
313 /// ```no_run
314 /// # use serde::{Deserialize, Serialize};
315 /// # #[async_std::main]
316 /// # async fn main() -> surf::Result<()> {
317 /// #[derive(Deserialize, Serialize)]
318 /// struct Ip {
319 /// ip: String
320 /// }
321 ///
322 /// let uri = "https://api.ipify.org?format=json";
323 /// let Ip { ip } = surf::get(uri).recv_json().await?;
324 /// assert!(ip.len() > 10);
325 /// # Ok(()) }
326 /// ```
327 pub async fn recv_json<T: serde::de::DeserializeOwned>(self) -> Result<T> {
328 let mut res = self.send().await?;
329 res.body_json::<T>().await
330 }
331
332 /// Submit the request and decode the response body from form encoding into a struct.
333 ///
334 /// # Errors
335 ///
336 /// Any I/O error encountered while reading the body is immediately returned
337 /// as an `Err`.
338 ///
339 /// If the body cannot be interpreted as valid json for the target type `T`,
340 /// an `Err` is returned.
341 ///
342 /// # Examples
343 ///
344 /// ```no_run
345 /// # use serde::{Deserialize, Serialize};
346 /// # #[async_std::main]
347 /// # async fn main() -> surf::Result<()> {
348 /// #[derive(Deserialize, Serialize)]
349 /// struct Body {
350 /// apples: u32
351 /// }
352 ///
353 /// let url = "https://api.example.com/v1/response";
354 /// let Body { apples } = surf::get(url).recv_form().await?;
355 /// # Ok(()) }
356 /// ```
357 pub async fn recv_form<T: serde::de::DeserializeOwned>(self) -> Result<T> {
358 let mut res = self.send().await?;
359 res.body_form::<T>().await
360 }
361
362 /// Push middleware onto a per-request middleware stack.
363 ///
364 /// **Important**: Setting per-request middleware incurs extra allocations.
365 /// Creating a `Client` with middleware is recommended.
366 ///
367 /// Client middleware is run before per-request middleware.
368 ///
369 /// See the [middleware] submodule for more information on middleware.
370 ///
371 /// [middleware]: ../middleware/index.html
372 ///
373 /// # Examples
374 ///
375 /// ```no_run
376 /// # #[async_std::main]
377 /// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
378 /// let res = surf::get("https://httpbin.org/get")
379 /// .middleware(surf::middleware::Redirect::default())
380 /// .await?;
381 /// # Ok(()) }
382 /// ```
383 pub fn middleware(mut self, middleware: impl Middleware) -> Self {
384 self.req.as_mut().unwrap().middleware(middleware);
385 self
386 }
387
388 /// Return the constructed `Request`.
389 pub fn build(self) -> Request {
390 self.req.unwrap()
391 }
392
393 /// Create a `Client` and send the constructed `Request` from it.
394 pub async fn send(mut self) -> Result<Response> {
395 self.client
396 .take()
397 .unwrap_or_else(Client::new_shared_or_panic)
398 .send(self.build())
399 .await
400 }
401}
402
403impl fmt::Debug for RequestBuilder {
404 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 fmt::Debug::fmt(&self.req, f)
406 }
407}
408
409impl Future for RequestBuilder {
410 type Output = Result<Response>;
411
412 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
413 if self.fut.is_none() {
414 let req = self.req.take().unwrap();
415
416 let client = self
417 .client
418 .take()
419 .unwrap_or_else(Client::new_shared_or_panic);
420
421 self.fut = Some(Box::pin(async move { client.send(req).await }))
422 }
423
424 // We can safely unwrap here because this is the only time we take ownership of the request.
425 self.fut.as_mut().unwrap().as_mut().poll(cx)
426 }
427}
428
429impl From<RequestBuilder> for Request {
430 /// Converts a `surf::RequestBuilder` to a `surf::Request`.
431 fn from(builder: RequestBuilder) -> Request {
432 builder.build()
433 }
434}