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}