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}