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}