crux_http/response/
response_async.rs

1use http_types::{
2    self,
3    headers::{self, HeaderName, HeaderValues, ToHeaderValues},
4    Body, Mime, StatusCode, Version,
5};
6
7use futures_util::io::AsyncRead;
8use serde::de::DeserializeOwned;
9
10use std::fmt;
11use std::io;
12use std::ops::Index;
13use std::pin::Pin;
14use std::task::{Context, Poll};
15
16use super::decode::decode_body;
17
18pin_project_lite::pin_project! {
19    /// An HTTP response that exposes async methods. This is to support async
20    /// use and middleware.
21    pub struct ResponseAsync {
22        #[pin]
23        res: http_types::Response,
24    }
25}
26
27impl ResponseAsync {
28    /// Create a new instance.
29    pub(crate) fn new(res: http_types::Response) -> Self {
30        Self { res }
31    }
32
33    /// Get the HTTP status code.
34    ///
35    /// # Examples
36    ///
37    /// ```no_run
38    /// # use crux_http::client::Client;
39    /// # async fn middleware(client: Client) -> crux_http::Result<()> {
40    /// let res = client.get("https://httpbin.org/get").await?;
41    /// assert_eq!(res.status(), 200);
42    /// # Ok(()) }
43    /// ```
44    pub fn status(&self) -> StatusCode {
45        self.res.status()
46    }
47
48    /// Get the HTTP protocol version.
49    ///
50    /// # Examples
51    ///
52    /// ```no_run
53    /// # use crux_http::client::Client;
54    /// # async fn middleware(client: Client) -> crux_http::Result<()> {
55    /// use crux_http::http::Version;
56    ///
57    /// let res = client.get("https://httpbin.org/get").await?;
58    /// assert_eq!(res.version(), Some(Version::Http1_1));
59    /// # Ok(()) }
60    /// ```
61    pub fn version(&self) -> Option<Version> {
62        self.res.version()
63    }
64
65    /// Get a header.
66    ///
67    /// # Examples
68    ///
69    /// ```no_run
70    /// # use crux_http::client::Client;
71    /// # async fn middleware(client: Client) -> crux_http::Result<()> {
72    /// let res = client.get("https://httpbin.org/get").await?;
73    /// assert!(res.header("Content-Length").is_some());
74    /// # Ok(()) }
75    /// ```
76    pub fn header(&self, name: impl Into<HeaderName>) -> Option<&HeaderValues> {
77        self.res.header(name)
78    }
79
80    /// Get an HTTP header mutably.
81    pub fn header_mut(&mut self, name: impl Into<HeaderName>) -> Option<&mut HeaderValues> {
82        self.res.header_mut(name)
83    }
84
85    /// Remove a header.
86    pub fn remove_header(&mut self, name: impl Into<HeaderName>) -> Option<HeaderValues> {
87        self.res.remove_header(name)
88    }
89
90    /// Insert an HTTP header.
91    pub fn insert_header(&mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) {
92        self.res.insert_header(key, value);
93    }
94
95    /// Append an HTTP header.
96    pub fn append_header(&mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) {
97        self.res.append_header(key, value);
98    }
99
100    /// An iterator visiting all header pairs in arbitrary order.
101    #[must_use]
102    pub fn iter(&self) -> headers::Iter<'_> {
103        self.res.iter()
104    }
105
106    /// An iterator visiting all header pairs in arbitrary order, with mutable references to the
107    /// values.
108    #[must_use]
109    pub fn iter_mut(&mut self) -> headers::IterMut<'_> {
110        self.res.iter_mut()
111    }
112
113    /// An iterator visiting all header names in arbitrary order.
114    #[must_use]
115    pub fn header_names(&self) -> headers::Names<'_> {
116        self.res.header_names()
117    }
118
119    /// An iterator visiting all header values in arbitrary order.
120    #[must_use]
121    pub fn header_values(&self) -> headers::Values<'_> {
122        self.res.header_values()
123    }
124
125    /// Get a response scoped extension value.
126    #[must_use]
127    pub fn ext<T: Send + Sync + 'static>(&self) -> Option<&T> {
128        self.res.ext().get()
129    }
130
131    /// Set a response scoped extension value.
132    pub fn insert_ext<T: Send + Sync + 'static>(&mut self, val: T) {
133        self.res.ext_mut().insert(val);
134    }
135
136    /// Get the response content type as a `Mime`.
137    ///
138    /// Gets the `Content-Type` header and parses it to a `Mime` type.
139    ///
140    /// [Read more on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
141    ///
142    /// # Panics
143    ///
144    /// This method will panic if an invalid MIME type was set as a header.
145    ///
146    /// # Examples
147    ///
148    /// ```no_run
149    /// # use crux_http::client::Client;
150    /// # async fn middleware(client: Client) -> crux_http::Result<()> {
151    /// use crux_http::http::mime;
152    /// let res = client.get("https://httpbin.org/json").await?;
153    /// assert_eq!(res.content_type(), Some(mime::JSON));
154    /// # Ok(()) }
155    /// ```
156    pub fn content_type(&self) -> Option<Mime> {
157        self.res.content_type()
158    }
159
160    /// Get the length of the body stream, if it has been set.
161    ///
162    /// This value is set when passing a fixed-size object into as the body.
163    /// E.g. a string, or a buffer. Consumers of this API should check this
164    /// value to decide whether to use `Chunked` encoding, or set the
165    /// response length.
166    #[allow(clippy::len_without_is_empty)]
167    pub fn len(&self) -> Option<usize> {
168        self.res.len()
169    }
170
171    /// Returns `true` if the set length of the body stream is zero, `false`
172    /// otherwise.
173    pub fn is_empty(&self) -> Option<bool> {
174        self.res.is_empty()
175    }
176
177    /// Set the body reader.
178    pub fn set_body(&mut self, body: impl Into<Body>) {
179        self.res.set_body(body);
180    }
181
182    /// Take the response body as a `Body`.
183    ///
184    /// This method can be called after the body has already been taken or read,
185    /// but will return an empty `Body`.
186    ///
187    /// Useful for adjusting the whole body, such as in middleware.
188    pub fn take_body(&mut self) -> Body {
189        self.res.take_body()
190    }
191
192    /// Swaps the value of the body with another body, without deinitializing
193    /// either one.
194    pub fn swap_body(&mut self, body: &mut Body) {
195        self.res.swap_body(body)
196    }
197
198    /// Reads the entire request body into a byte buffer.
199    ///
200    /// This method can be called after the body has already been read, but will
201    /// produce an empty buffer.
202    ///
203    /// # Errors
204    ///
205    /// Any I/O error encountered while reading the body is immediately returned
206    /// as an `Err`.
207    ///
208    /// # Examples
209    ///
210    /// ```no_run
211    /// # use crux_http::client::Client;
212    /// # async fn middleware(client: Client) -> crux_http::Result<()> {
213    /// let mut res = client.get("https://httpbin.org/get").await?;
214    /// let bytes: Vec<u8> = res.body_bytes().await?;
215    /// # Ok(()) }
216    /// ```
217    pub async fn body_bytes(&mut self) -> crate::Result<Vec<u8>> {
218        Ok(self.res.body_bytes().await?)
219    }
220
221    /// Reads the entire response body into a string.
222    ///
223    /// This method can be called after the body has already been read, but will
224    /// produce an empty buffer.
225    ///
226    /// # Encodings
227    ///
228    /// If the "encoding" feature is enabled, this method tries to decode the body
229    /// with the encoding that is specified in the Content-Type header. If the header
230    /// does not specify an encoding, UTF-8 is assumed. If the "encoding" feature is
231    /// disabled, Surf only supports reading UTF-8 response bodies. The "encoding"
232    /// feature is enabled by default.
233    ///
234    /// # Errors
235    ///
236    /// Any I/O error encountered while reading the body is immediately returned
237    /// as an `Err`.
238    ///
239    /// If the body cannot be interpreted because the encoding is unsupported or
240    /// incorrect, an `Err` is returned.
241    ///
242    /// # Examples
243    ///
244    /// ```no_run
245    /// # use crux_http::client::Client;
246    /// # async fn middleware(client: Client) -> crux_http::Result<()> {
247    /// let mut res = client.get("https://httpbin.org/get").await?;
248    /// let string: String = res.body_string().await?;
249    /// # Ok(()) }
250    /// ```
251    pub async fn body_string(&mut self) -> crate::Result<String> {
252        let bytes = self.body_bytes().await?;
253        let mime = self.content_type();
254        let claimed_encoding = mime
255            .as_ref()
256            .and_then(|mime| mime.param("charset"))
257            .map(|name| name.to_string());
258        Ok(decode_body(bytes, claimed_encoding.as_deref())?)
259    }
260
261    /// Reads and deserialized the entire request body from json.
262    ///
263    /// # Errors
264    ///
265    /// Any I/O error encountered while reading the body is immediately returned
266    /// as an `Err`.
267    ///
268    /// If the body cannot be interpreted as valid json for the target type `T`,
269    /// an `Err` is returned.
270    ///
271    /// # Examples
272    ///
273    /// ```no_run
274    /// # use serde::{Deserialize, Serialize};
275    /// # use crux_http::client::Client;
276    /// # async fn middleware(client: Client) -> crux_http::Result<()> {
277    /// #[derive(Deserialize, Serialize)]
278    /// struct Ip {
279    ///     ip: String
280    /// }
281    ///
282    /// let mut res = client.get("https://api.ipify.org?format=json").await?;
283    /// let Ip { ip } = res.body_json().await?;
284    /// # Ok(()) }
285    /// ```
286    pub async fn body_json<T: DeserializeOwned>(&mut self) -> crate::Result<T> {
287        let body_bytes = self.body_bytes().await?;
288        serde_json::from_slice(&body_bytes).map_err(crate::HttpError::from)
289    }
290
291    /// Reads and deserialized the entire request body from form encoding.
292    ///
293    /// # Errors
294    ///
295    /// Any I/O error encountered while reading the body is immediately returned
296    /// as an `Err`.
297    ///
298    /// If the body cannot be interpreted as valid json for the target type `T`,
299    /// an `Err` is returned.
300    ///
301    /// # Examples
302    ///
303    /// ```no_run
304    /// # use serde::{Deserialize, Serialize};
305    /// # use crux_http::client::Client;
306    /// # async fn middleware(client: Client) -> crux_http::Result<()> {
307    /// #[derive(Deserialize, Serialize)]
308    /// struct Body {
309    ///     apples: u32
310    /// }
311    ///
312    /// let mut res = client.get("https://api.example.com/v1/response").await?;
313    /// let Body { apples } = res.body_form().await?;
314    /// # Ok(()) }
315    /// ```
316    pub async fn body_form<T: serde::de::DeserializeOwned>(&mut self) -> crate::Result<T> {
317        Ok(self.res.body_form().await?)
318    }
319}
320
321impl From<http_types::Response> for ResponseAsync {
322    fn from(response: http_types::Response) -> Self {
323        Self::new(response)
324    }
325}
326
327#[allow(clippy::from_over_into)]
328impl Into<http_types::Response> for ResponseAsync {
329    fn into(self) -> http_types::Response {
330        self.res
331    }
332}
333
334impl AsRef<http_types::Headers> for ResponseAsync {
335    fn as_ref(&self) -> &http_types::Headers {
336        self.res.as_ref()
337    }
338}
339
340impl AsMut<http_types::Headers> for ResponseAsync {
341    fn as_mut(&mut self) -> &mut http_types::Headers {
342        self.res.as_mut()
343    }
344}
345
346impl AsRef<http_types::Response> for ResponseAsync {
347    fn as_ref(&self) -> &http_types::Response {
348        &self.res
349    }
350}
351
352impl AsMut<http_types::Response> for ResponseAsync {
353    fn as_mut(&mut self) -> &mut http_types::Response {
354        &mut self.res
355    }
356}
357
358impl AsyncRead for ResponseAsync {
359    fn poll_read(
360        mut self: Pin<&mut Self>,
361        cx: &mut Context<'_>,
362        buf: &mut [u8],
363    ) -> Poll<Result<usize, io::Error>> {
364        Pin::new(&mut self.res).poll_read(cx, buf)
365    }
366}
367
368impl fmt::Debug for ResponseAsync {
369    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370        f.debug_struct("Response")
371            .field("response", &self.res)
372            .finish()
373    }
374}
375
376impl Index<HeaderName> for ResponseAsync {
377    type Output = HeaderValues;
378
379    /// Returns a reference to the value corresponding to the supplied name.
380    ///
381    /// # Panics
382    ///
383    /// Panics if the name is not present in `Response`.
384    #[inline]
385    fn index(&self, name: HeaderName) -> &HeaderValues {
386        &self.res[name]
387    }
388}
389
390impl Index<&str> for ResponseAsync {
391    type Output = HeaderValues;
392
393    /// Returns a reference to the value corresponding to the supplied name.
394    ///
395    /// # Panics
396    ///
397    /// Panics if the name is not present in `Response`.
398    #[inline]
399    fn index(&self, name: &str) -> &HeaderValues {
400        &self.res[name]
401    }
402}