Skip to main content

crux_http/response/
response.rs

1use super::{decode::decode_body, new_headers};
2use http_types::{
3    self, Mime, StatusCode, Version,
4    headers::{self, HeaderName, HeaderValues, ToHeaderValues},
5};
6
7use http_types::{Headers, headers::CONTENT_TYPE};
8use serde::de::DeserializeOwned;
9
10use std::fmt;
11use std::ops::Index;
12
13/// An HTTP Response that will be passed to in a message to an apps update function
14#[derive(Clone, serde::Serialize, serde::Deserialize)]
15pub struct Response<Body> {
16    version: Option<http_types::Version>,
17    status: http_types::StatusCode,
18    #[serde(with = "header_serde")]
19    headers: Headers,
20    body: Option<Body>,
21}
22
23impl<Body> Response<Body> {
24    /// Create a new instance.
25    pub(crate) async fn new(mut res: super::ResponseAsync) -> crate::Result<Response<Vec<u8>>> {
26        let body = res.body_bytes().await?;
27        let status = res.status();
28
29        if status.is_client_error() || status.is_server_error() {
30            return Err(crate::HttpError::Http {
31                code: status,
32                message: status.to_string(),
33                body: Some(body),
34            });
35        }
36
37        let headers: &Headers = res.as_ref();
38        let headers = headers.clone();
39
40        Ok(Response {
41            status: res.status(),
42            headers,
43            version: res.version(),
44            body: Some(body),
45        })
46    }
47
48    /// Get the HTTP status code.
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// # let res = crux_http::testing::ResponseBuilder::ok().build();
54    /// assert_eq!(res.status(), 200);
55    /// ```
56    pub fn status(&self) -> StatusCode {
57        self.status
58    }
59
60    /// Get the HTTP protocol version.
61    ///
62    /// # Examples
63    ///
64    /// ```no_run
65    /// # let res = crux_http::testing::ResponseBuilder::ok().build();
66    /// use crux_http::http::Version;
67    /// assert_eq!(res.version(), Some(Version::Http1_1));
68    /// ```
69    pub fn version(&self) -> Option<Version> {
70        self.version
71    }
72
73    /// Get a header.
74    ///
75    /// # Examples
76    ///
77    /// ```no_run
78    /// # let res = crux_http::testing::ResponseBuilder::ok()
79    /// #   .header("Content-Length", "1")
80    /// #   .build();
81    /// assert!(res.header("Content-Length").is_some());
82    /// ```
83    pub fn header(&self, name: impl Into<HeaderName>) -> Option<&HeaderValues> {
84        self.headers.get(name)
85    }
86
87    /// Get an HTTP header mutably.
88    pub fn header_mut(&mut self, name: impl Into<HeaderName>) -> Option<&mut HeaderValues> {
89        self.headers.get_mut(name)
90    }
91
92    /// Remove a header.
93    pub fn remove_header(&mut self, name: impl Into<HeaderName>) -> Option<HeaderValues> {
94        self.headers.remove(name)
95    }
96
97    /// Insert an HTTP header.
98    pub fn insert_header(&mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) {
99        self.headers.insert(key, value);
100    }
101
102    /// Append an HTTP header.
103    pub fn append_header(&mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) {
104        self.headers.append(key, value);
105    }
106
107    /// An iterator visiting all header pairs in arbitrary order.
108    #[must_use]
109    pub fn iter(&self) -> headers::Iter<'_> {
110        self.headers.iter()
111    }
112
113    /// An iterator visiting all header pairs in arbitrary order, with mutable references to the
114    /// values.
115    #[must_use]
116    pub fn iter_mut(&mut self) -> headers::IterMut<'_> {
117        self.headers.iter_mut()
118    }
119
120    /// An iterator visiting all header names in arbitrary order.
121    #[must_use]
122    pub fn header_names(&self) -> headers::Names<'_> {
123        self.headers.names()
124    }
125
126    /// An iterator visiting all header values in arbitrary order.
127    #[must_use]
128    pub fn header_values(&self) -> headers::Values<'_> {
129        self.headers.values()
130    }
131
132    /// Get the response content type as a `Mime`.
133    ///
134    /// Gets the `Content-Type` header and parses it to a `Mime` type.
135    ///
136    /// [Read more on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
137    ///
138    /// # Panics
139    ///
140    /// This method will panic if an invalid MIME type was set as a header.
141    ///
142    /// # Examples
143    ///
144    /// ```
145    /// # let res = crux_http::testing::ResponseBuilder::ok()
146    /// #   .header("Content-Type", "application/json")
147    /// #   .build();
148    /// use crux_http::http::mime;
149    /// assert_eq!(res.content_type(), Some(mime::JSON));
150    /// ```
151    pub fn content_type(&self) -> Option<Mime> {
152        self.header(CONTENT_TYPE)?.last().as_str().parse().ok()
153    }
154
155    pub fn body(&self) -> Option<&Body> {
156        self.body.as_ref()
157    }
158
159    pub fn take_body(&mut self) -> Option<Body> {
160        self.body.take()
161    }
162
163    pub fn with_body<NewBody>(self, body: NewBody) -> Response<NewBody> {
164        Response {
165            body: Some(body),
166            headers: self.headers,
167            status: self.status,
168            version: self.version,
169        }
170    }
171}
172
173impl<'a, Body> IntoIterator for &'a Response<Body> {
174    type Item = (&'a headers::HeaderName, &'a headers::HeaderValues);
175    type IntoIter = headers::Iter<'a>;
176    fn into_iter(self) -> Self::IntoIter {
177        self.iter()
178    }
179}
180
181impl<'a, Body> IntoIterator for &'a mut Response<Body> {
182    type Item = (&'a headers::HeaderName, &'a mut headers::HeaderValues);
183    type IntoIter = headers::IterMut<'a>;
184    fn into_iter(self) -> Self::IntoIter {
185        self.iter_mut()
186    }
187}
188
189impl Response<Vec<u8>> {
190    pub(crate) fn new_with_status(status: http_types::StatusCode) -> Self {
191        let headers = new_headers();
192
193        Response {
194            status,
195            headers,
196            version: None,
197            body: None,
198        }
199    }
200
201    /// Reads the entire request body into a byte buffer.
202    ///
203    /// This method can be called after the body has already been read, but will
204    /// produce an empty buffer.
205    ///
206    /// # Errors
207    ///
208    /// Any I/O error encountered while reading the body is immediately returned
209    /// as an `Err`.
210    ///
211    /// # Examples
212    ///
213    /// ```
214    /// # fn main() -> crux_http::Result<()> {
215    /// # let mut res = crux_http::testing::ResponseBuilder::ok()
216    /// #   .header("Content-Type", "application/json")
217    /// #   .body(vec![0u8, 1])
218    /// #   .build();
219    /// let bytes: Vec<u8> = res.body_bytes()?;
220    /// # Ok(()) }
221    /// ```
222    pub fn body_bytes(&mut self) -> crate::Result<Vec<u8>> {
223        self.body.take().ok_or_else(|| crate::HttpError::Http {
224            code: self.status(),
225            message: "Body had no bytes".to_string(),
226            body: None,
227        })
228    }
229
230    /// Reads the entire response body into a string.
231    ///
232    /// This method can be called after the body has already been read, but will
233    /// produce an empty buffer.
234    ///
235    /// # Encodings
236    ///
237    /// If the "encoding" feature is enabled, this method tries to decode the body
238    /// with the encoding that is specified in the Content-Type header. If the header
239    /// does not specify an encoding, UTF-8 is assumed. If the "encoding" feature is
240    /// disabled, Surf only supports reading UTF-8 response bodies. The "encoding"
241    /// feature is enabled by default.
242    ///
243    /// # Errors
244    ///
245    /// Any I/O error encountered while reading the body is immediately returned
246    /// as an `Err`.
247    ///
248    /// If the body cannot be interpreted because the encoding is unsupported or
249    /// incorrect, an `Err` is returned.
250    ///
251    /// # Examples
252    ///
253    /// ```
254    /// # fn main() -> crux_http::Result<()> {
255    /// # let mut res = crux_http::testing::ResponseBuilder::ok()
256    /// #   .header("Content-Type", "application/json")
257    /// #   .body("hello".to_string().into_bytes())
258    /// #   .build();
259    /// let string: String = res.body_string()?;
260    /// assert_eq!(string, "hello");
261    /// # Ok(()) }
262    /// ```
263    pub fn body_string(&mut self) -> crate::Result<String> {
264        let bytes = self.body_bytes()?;
265
266        let mime = self.content_type();
267        let claimed_encoding = mime
268            .as_ref()
269            .and_then(|mime| mime.param("charset"))
270            .map(std::string::ToString::to_string);
271        Ok(decode_body(bytes, claimed_encoding.as_deref())?)
272    }
273
274    /// Reads and deserialized the entire request body from json.
275    ///
276    /// # Errors
277    ///
278    /// Any I/O error encountered while reading the body is immediately returned
279    /// as an `Err`.
280    ///
281    /// If the body cannot be interpreted as valid json for the target type `T`,
282    /// an `Err` is returned.
283    ///
284    /// # Examples
285    ///
286    /// ```
287    /// # use serde::{Deserialize, Serialize};
288    /// # fn main() -> crux_http::Result<()> {
289    /// # let mut res = crux_http::testing::ResponseBuilder::ok()
290    /// #   .header("Content-Type", "application/json")
291    /// #   .body("{\"ip\": \"127.0.0.1\"}".to_string().into_bytes())
292    /// #   .build();
293    /// #[derive(Deserialize, Serialize)]
294    /// struct Ip {
295    ///     ip: String
296    /// }
297    ///
298    /// let Ip { ip } = res.body_json()?;
299    /// assert_eq!(ip, "127.0.0.1");
300    /// # Ok(()) }
301    /// ```
302    pub fn body_json<T: DeserializeOwned>(&mut self) -> crate::Result<T> {
303        let body_bytes = self.body_bytes()?;
304        serde_json::from_slice(&body_bytes).map_err(crate::HttpError::from)
305    }
306}
307
308impl<Body> AsRef<http_types::Headers> for Response<Body> {
309    fn as_ref(&self) -> &http_types::Headers {
310        &self.headers
311    }
312}
313
314impl<Body> AsMut<http_types::Headers> for Response<Body> {
315    fn as_mut(&mut self) -> &mut http_types::Headers {
316        &mut self.headers
317    }
318}
319
320impl<Body> fmt::Debug for Response<Body> {
321    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322        f.debug_struct("Response")
323            .field("version", &self.version)
324            .field("status", &self.status)
325            .field("headers", &self.headers)
326            .finish_non_exhaustive()
327    }
328}
329
330impl<Body> Index<HeaderName> for Response<Body> {
331    type Output = HeaderValues;
332
333    /// Returns a reference to the value corresponding to the supplied name.
334    ///
335    /// # Panics
336    ///
337    /// Panics if the name is not present in `Response`.
338    #[inline]
339    fn index(&self, name: HeaderName) -> &HeaderValues {
340        &self.headers[name]
341    }
342}
343
344impl<Body> Index<&str> for Response<Body> {
345    type Output = HeaderValues;
346
347    /// Returns a reference to the value corresponding to the supplied name.
348    ///
349    /// # Panics
350    ///
351    /// Panics if the name is not present in `Response`.
352    #[inline]
353    fn index(&self, name: &str) -> &HeaderValues {
354        &self.headers[name]
355    }
356}
357
358impl<Body> PartialEq for Response<Body>
359where
360    Body: PartialEq,
361{
362    fn eq(&self, other: &Self) -> bool {
363        self.version == other.version
364            && self.status == other.status
365            && self.headers.iter().zip(other.headers.iter()).all(
366                |((lhs_name, lhs_values), (rhs_name, rhs_values))| {
367                    lhs_name == rhs_name
368                        && lhs_values
369                            .iter()
370                            .zip(rhs_values.iter())
371                            .all(|(lhs, rhs)| lhs == rhs)
372                },
373            )
374            && self.body == other.body
375    }
376}
377
378impl<Body> Eq for Response<Body> where Body: Eq {}
379
380#[cfg(feature = "http-compat")]
381impl<Body> TryInto<http::Response<Body>> for Response<Body> {
382    type Error = ();
383
384    fn try_into(self) -> Result<http::Response<Body>, Self::Error> {
385        let mut response = http::Response::new(self.body.ok_or(())?);
386
387        if let Some(version) = self.version {
388            let version = match version {
389                Version::Http0_9 => Some(http::Version::HTTP_09),
390                Version::Http1_0 => Some(http::Version::HTTP_10),
391                Version::Http1_1 => Some(http::Version::HTTP_11),
392                Version::Http2_0 => Some(http::Version::HTTP_2),
393                Version::Http3_0 => Some(http::Version::HTTP_3),
394                _ => None,
395            };
396
397            if let Some(version) = version {
398                *response.version_mut() = version;
399            }
400        }
401
402        let mut headers = self.headers;
403        headers_to_hyperium_headers(&mut headers, response.headers_mut());
404
405        Ok(response)
406    }
407}
408
409#[cfg(feature = "http-compat")]
410fn headers_to_hyperium_headers(headers: &mut Headers, hyperium_headers: &mut http::HeaderMap) {
411    for (name, values) in headers {
412        let name = format!("{name}").into_bytes();
413        let name = http::header::HeaderName::from_bytes(&name).unwrap();
414
415        for value in values.iter() {
416            let value = format!("{value}").into_bytes();
417            let value = http::header::HeaderValue::from_bytes(&value).unwrap();
418            hyperium_headers.append(&name, value);
419        }
420    }
421}
422
423mod header_serde {
424    use crate::{http::Headers, response::new_headers};
425    use http_types::headers::{HeaderName, HeaderValue};
426    use serde::{Deserializer, Serializer, de::Error};
427
428    pub fn serialize<S>(headers: &Headers, serializer: S) -> Result<S::Ok, S::Error>
429    where
430        S: Serializer,
431    {
432        serializer.collect_map(headers.iter().map(|(name, values)| {
433            (
434                name.as_str(),
435                values.iter().map(HeaderValue::as_str).collect::<Vec<_>>(),
436            )
437        }))
438    }
439
440    pub fn deserialize<'de, D>(deserializer: D) -> Result<Headers, D::Error>
441    where
442        D: Deserializer<'de>,
443    {
444        let strs = <Vec<(String, Vec<String>)> as serde::Deserialize>::deserialize(deserializer)?;
445
446        let mut headers = new_headers();
447
448        for (name, values) in strs {
449            let name = HeaderName::from_string(name).map_err(D::Error::custom)?;
450            for value in values {
451                headers.append(&name, value);
452            }
453        }
454
455        Ok(headers)
456    }
457}