modio/response/
mod.rs

1//! `Response` type and related utility types.
2//!
3//! # Example
4//!
5//! ```no_run
6//! # #[tokio::main]
7//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
8//! use modio::types::games::Game;
9//! use modio::types::id::Id;
10//! use modio::Client;
11//!
12//! let client = Client::builder(std::env::var("MODIO_API_KEY")?).build()?;
13//!
14//! let response = client.get_game(Id::new(51)).await?;
15//! println!("http status: {}", response.status());
16//!
17//! let game: Game = response.data().await?;
18//! println!("name: {}", game.name);
19//! #     Ok(())
20//! # }
21//! ```
22
23use std::future::Future;
24use std::marker::PhantomData;
25use std::pin::Pin;
26use std::task::{Context, Poll};
27
28use bytes::Bytes;
29use http_body_util::{BodyExt, Collected};
30use serde::de::DeserializeOwned;
31
32mod error;
33mod future;
34
35use crate::client;
36use crate::error::Error;
37use crate::types::ErrorResponse;
38
39use self::error::BodyErrorKind;
40
41pub use self::error::BodyError;
42pub use self::future::ResponseFuture;
43
44pub(crate) type Output<T> = Result<Response<T>, Error>;
45
46/// Marker that the response has no content.
47#[non_exhaustive]
48pub struct NoContent;
49
50/// A `Response` from a submitted request.
51pub struct Response<T> {
52    inner: client::service::Response,
53    phantom: PhantomData<T>,
54}
55
56impl<T> Response<T> {
57    pub(crate) const fn new(inner: client::service::Response) -> Self {
58        Self {
59            inner,
60            phantom: PhantomData,
61        }
62    }
63
64    /// Returns a reference to the response headers.
65    pub fn headers(&self) -> &http::HeaderMap {
66        self.inner.headers()
67    }
68
69    /// Returns the status code of the response.
70    pub fn status(&self) -> http::StatusCode {
71        self.inner.status()
72    }
73
74    /// Consumes the response and accumulate the body into bytes.
75    ///
76    /// # Example
77    ///
78    /// ```no_run
79    /// # #[tokio::main]
80    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
81    /// use bytes::Bytes;
82    /// use modio::types::id::Id;
83    /// use modio::Client;
84    ///
85    /// let client = Client::builder(std::env::var("MODIO_API_KEY")?).build()?;
86    /// let response = client.get_game(Id::new(51)).await?;
87    /// let bytes: Bytes = response.bytes().await?;
88    /// #     Ok(())
89    /// # }
90    /// ```
91    pub fn bytes(self) -> BytesFuture {
92        let body = self.inner.into_body();
93
94        let fut = async {
95            body.collect()
96                .await
97                .map(Collected::to_bytes)
98                .map_err(|err| BodyError::new(BodyErrorKind::Loading, Some(err)))
99        };
100        BytesFuture {
101            inner: Box::pin(fut),
102        }
103    }
104
105    /// Consumes the response and accumulate the body into a string.
106    ///
107    /// # Example
108    ///
109    /// ```no_run
110    /// # #[tokio::main]
111    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
112    /// use modio::types::id::Id;
113    /// use modio::Client;
114    ///
115    /// let client = Client::builder(std::env::var("MODIO_API_KEY")?).build()?;
116    /// let response = client.get_game(Id::new(51)).await?;
117    /// let text: String = response.text().await?;
118    /// #     Ok(())
119    /// # }
120    /// ```
121    pub fn text(self) -> TextFuture {
122        TextFuture::new(self.bytes())
123    }
124
125    fn error(self) -> ErrorResponseFuture {
126        ErrorResponseFuture::new(self.bytes())
127    }
128}
129
130impl<T: DeserializeOwned> Response<T> {
131    /// Consume the response and deserialize the body into the request's matching model.
132    ///
133    /// # Example
134    ///
135    /// ```no_run
136    /// # #[tokio::main]
137    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
138    /// use modio::types::games::Game;
139    /// use modio::types::id::Id;
140    /// use modio::Client;
141    ///
142    /// let client = Client::builder(std::env::var("MODIO_API_KEY")?).build()?;
143    /// let response = client.get_game(Id::new(51)).await?;
144    /// let game: Game = response.data().await?;
145    /// #     Ok(())
146    /// # }
147    /// ```
148    pub fn data(self) -> DataFuture<T> {
149        DataFuture::new(self.bytes())
150    }
151}
152
153/// A `Future` that will resolve to the bytes of a response body.
154///
155/// This returned by [`Response::bytes`].
156pub struct BytesFuture {
157    inner: Pin<Box<dyn Future<Output = Result<Bytes, BodyError>> + Send + Sync>>,
158}
159
160impl Future for BytesFuture {
161    type Output = Result<Bytes, BodyError>;
162
163    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
164        self.inner.as_mut().poll(cx)
165    }
166}
167
168/// A `Future` that will resolve to a deserialized model.
169pub struct DataFuture<T> {
170    inner: BytesFuture,
171    phantom: PhantomData<T>,
172}
173
174impl<T> DataFuture<T> {
175    const fn new(bytes: BytesFuture) -> Self {
176        Self {
177            inner: bytes,
178            phantom: PhantomData,
179        }
180    }
181}
182
183impl<T: DeserializeOwned + Unpin> Future for DataFuture<T> {
184    type Output = Result<T, BodyError>;
185
186    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
187        match Pin::new(&mut self.inner).poll(cx) {
188            Poll::Ready(Ok(bytes)) => Poll::Ready(
189                serde_json::from_slice(&bytes).map_err(|err| BodyError::decode(bytes, err)),
190            ),
191            Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
192            Poll::Pending => Poll::Pending,
193        }
194    }
195}
196
197/// A `Future` that will resolve to the text of a response body.
198///
199/// This returned by [`Response::text`].
200pub struct TextFuture {
201    inner: BytesFuture,
202}
203
204impl TextFuture {
205    const fn new(bytes: BytesFuture) -> Self {
206        Self { inner: bytes }
207    }
208}
209
210impl Future for TextFuture {
211    type Output = Result<String, BodyError>;
212
213    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
214        match Pin::new(&mut self.inner).poll(cx) {
215            Poll::Ready(Ok(bytes)) => {
216                Poll::Ready(String::from_utf8(bytes.to_vec()).map_err(|err| {
217                    let utf8_error = err.utf8_error();
218                    BodyError::invalid_utf8(err.into_bytes(), utf8_error)
219                }))
220            }
221            Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
222            Poll::Pending => Poll::Pending,
223        }
224    }
225}
226
227struct ErrorResponseFuture {
228    inner: DataFuture<ErrorResponse>,
229}
230
231impl ErrorResponseFuture {
232    const fn new(bytes: BytesFuture) -> Self {
233        Self {
234            inner: DataFuture::new(bytes),
235        }
236    }
237}
238
239impl Future for ErrorResponseFuture {
240    type Output = Result<ErrorResponse, BodyError>;
241
242    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
243        Pin::new(&mut self.inner).poll(cx)
244    }
245}