Skip to main content

crux_http/response/
response_async.rs

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