Skip to main content

timvw_rust_runtime/
response.rs

1use std::fmt;
2
3use bytes::Bytes;
4use futures_core::Stream;
5use futures_util::TryStreamExt;
6use http_body_util::BodyDataStream;
7use serde::de::DeserializeOwned;
8
9use crate::{Body, Error, HeaderMap, MaybeSend, StatusCode};
10
11/// A response, with the methods of reqwest's `Response` that generated clients use.
12pub struct Response {
13    inner: http::Response<Body>,
14}
15
16impl Response {
17    /// The status code.
18    pub fn status(&self) -> StatusCode {
19        self.inner.status()
20    }
21
22    /// The headers.
23    pub fn headers(&self) -> &HeaderMap {
24        self.inner.headers()
25    }
26
27    /// The headers, to change.
28    pub fn headers_mut(&mut self) -> &mut HeaderMap {
29        self.inner.headers_mut()
30    }
31
32    /// Reads the body into memory.
33    pub async fn bytes(self) -> Result<Bytes, Error> {
34        self.inner
35            .into_body()
36            .collect_bytes()
37            .await
38            .map_err(Error::body)
39    }
40
41    /// Reads the body as text; bytes that are not UTF-8 become U+FFFD.
42    pub async fn text(self) -> Result<String, Error> {
43        let bytes = self.bytes().await?;
44        Ok(match String::from_utf8(bytes.to_vec()) {
45            Ok(text) => text,
46            Err(error) => String::from_utf8_lossy(error.as_bytes()).into_owned(),
47        })
48    }
49
50    /// Reads the body as JSON.
51    pub async fn json<T: DeserializeOwned>(self) -> Result<T, Error> {
52        let bytes = self.bytes().await?;
53        serde_json::from_slice(&bytes).map_err(Error::decode)
54    }
55
56    /// The body as a stream of chunks, as they arrive. `Send`, except on WebAssembly ([`MaybeSend`]).
57    pub fn bytes_stream(self) -> impl Stream<Item = Result<Bytes, Error>> + MaybeSend + 'static {
58        BodyDataStream::new(self.inner.into_body()).map_err(Error::body)
59    }
60
61    /// The body as server-sent events (`text/event-stream`), as they arrive. [`Events::json`](crate::sse::Events::json)
62    /// decodes their `data:` as JSON. The status and the content type are not checked.
63    pub fn events(self) -> crate::sse::Events {
64        crate::sse::Events::from_response(self)
65    }
66
67    /// Streams the body to the file at `path`, and returns the number of bytes written.
68    ///
69    /// The body goes to a temporary file in the same directory, which is flushed to disk (`fsync`) and then renamed
70    /// to `path`, replacing any file there: `path` never holds part of a body. When reading the body or writing the
71    /// file fails, the temporary file is removed and `path` is left as it was. The status is not checked.
72    #[cfg(all(feature = "fs", not(target_family = "wasm")))]
73    pub async fn save_to(self, path: impl AsRef<std::path::Path>) -> Result<u64, Error> {
74        crate::download::save_to(self.bytes_stream(), path.as_ref()).await
75    }
76
77    /// The body as a tokio [`AsyncRead`](tokio::io::AsyncRead), read as it arrives: to copy it somewhere with
78    /// `tokio::io::copy`, or to decode it with a reader. A failure reading the body is an `std::io::Error` whose
79    /// source is the [`Error`].
80    #[cfg(all(feature = "fs", not(target_family = "wasm")))]
81    pub fn into_async_read(self) -> impl tokio::io::AsyncRead + Send + Unpin + 'static {
82        crate::download::BodyReader::new(self.bytes_stream())
83    }
84
85    /// The underlying `http::Response`.
86    pub fn into_http(self) -> http::Response<Body> {
87        self.inner
88    }
89}
90
91impl From<http::Response<Body>> for Response {
92    fn from(inner: http::Response<Body>) -> Self {
93        Self { inner }
94    }
95}
96
97impl fmt::Debug for Response {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        f.debug_struct("Response")
100            .field("status", &self.inner.status())
101            .field("headers", self.inner.headers())
102            .finish_non_exhaustive()
103    }
104}