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