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}