modio/util/
data_from_request.rs

1use std::fmt;
2use std::future::{Future, IntoFuture};
3use std::pin::Pin;
4use std::task::{ready, Context, Poll};
5
6use futures_util::future::Either;
7use serde::de::DeserializeOwned;
8
9use crate::response::{self, BodyError, ResponseFuture};
10use crate::Error;
11
12/// Extension trait for typed request builder objects.
13///
14/// Allows the user to retrieve the deserialized model directly from the request builder without
15/// going through the `Response<T>` object.
16pub trait DataFromRequest<T: DeserializeOwned + Unpin>: private::Sealed {
17    /// Consume the request builder and deserialize the body into the request's matching model.
18    ///
19    /// # Examples
20    ///
21    /// ```no_run
22    /// # #[tokio::main]
23    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
24    /// #     let client = modio::Client::builder(std::env::var("MODIO_API_KEY")?).build()?;
25    /// use modio::types::id::Id;
26    /// use modio::types::mods::Mod;
27    /// use modio::util::DataFromRequest;
28    ///
29    /// let mod_: Mod = client.get_mod(Id::new(51), Id::new(123)).data().await?;
30    /// println!("name: {}", mod_.name);
31    /// #     Ok(())
32    /// # }
33    /// ```
34    fn data(self) -> DataFuture<T>;
35}
36
37/// The errors that may occur when using [`DataFromRequest::data()`].
38#[derive(Debug)]
39pub enum DataError {
40    Request(Error),
41    Body(BodyError),
42}
43
44impl fmt::Display for DataError {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        match self {
47            Self::Request(err) => err.fmt(f),
48            Self::Body(err) => err.fmt(f),
49        }
50    }
51}
52
53impl std::error::Error for DataError {}
54
55pin_project_lite::pin_project! {
56    /// A `Future` that will resolve to a deserialized model.
57    pub struct DataFuture<T> where T: Unpin {
58        #[pin]
59        future: Either<ResponseFuture<T>, response::DataFuture<T>>,
60    }
61}
62
63impl<Builder, Data> DataFromRequest<Data> for Builder
64where
65    Builder: IntoFuture<IntoFuture = ResponseFuture<Data>> + private::Sealed,
66    Data: DeserializeOwned + Unpin,
67{
68    fn data(self) -> DataFuture<Data> {
69        DataFuture {
70            future: Either::Left(self.into_future()),
71        }
72    }
73}
74
75impl<T: DeserializeOwned + Unpin> Future for DataFuture<T> {
76    type Output = Result<T, DataError>;
77
78    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
79        let mut this = self.project();
80        loop {
81            match this.future.as_mut().as_pin_mut() {
82                Either::Left(fut) => {
83                    let resp = ready!(fut.poll(cx).map_err(DataError::Request))?;
84                    this.future.set(Either::Right(resp.data()));
85                }
86                Either::Right(fut) => {
87                    return fut.poll(cx).map_err(DataError::Body);
88                }
89            }
90        }
91    }
92}
93
94mod private {
95    use crate::request::{auth, files, games, mods, user};
96
97    pub trait Sealed {}
98
99    impl Sealed for auth::EmailRequest<'_> {}
100    impl Sealed for auth::EmailExchange<'_> {}
101    impl<T> Sealed for auth::ExternalAuth<'_, T> {}
102    impl Sealed for auth::GetTerms<'_> {}
103
104    impl Sealed for games::AddGameMedia<'_> {}
105    impl Sealed for games::GetGame<'_> {}
106    impl Sealed for games::GetGameStats<'_> {}
107    impl Sealed for games::GetGames<'_> {}
108    impl Sealed for games::tags::AddGameTags<'_> {}
109    impl Sealed for games::tags::GetGameTags<'_> {}
110    impl Sealed for games::tags::RenameGameTag<'_> {}
111
112    impl Sealed for mods::AddMod<'_> {}
113    impl Sealed for mods::EditMod<'_> {}
114    impl Sealed for mods::GetMod<'_> {}
115    impl Sealed for mods::GetModTeamMembers<'_> {}
116    impl Sealed for mods::GetMods<'_> {}
117    impl Sealed for mods::SubmitModRating<'_> {}
118    impl Sealed for mods::comments::AddModComment<'_> {}
119    impl Sealed for mods::comments::EditModComment<'_> {}
120    impl Sealed for mods::comments::GetModComment<'_> {}
121    impl Sealed for mods::comments::GetModComments<'_> {}
122    impl Sealed for mods::comments::UpdateModCommentKarma<'_> {}
123    impl Sealed for mods::dependencies::AddModDependencies<'_> {}
124    impl Sealed for mods::dependencies::GetModDependencies<'_> {}
125    impl Sealed for mods::events::GetModEvents<'_> {}
126    impl Sealed for mods::events::GetModsEvents<'_> {}
127    impl Sealed for mods::media::AddModMedia<'_> {}
128    impl Sealed for mods::metadata::AddModMetadata<'_> {}
129    impl Sealed for mods::metadata::GetModMetadata<'_> {}
130    impl Sealed for mods::stats::GetModStats<'_> {}
131    impl Sealed for mods::stats::GetModsStats<'_> {}
132    impl Sealed for mods::subscribe::SubscribeToMod<'_> {}
133    impl Sealed for mods::tags::AddModTags<'_> {}
134    impl Sealed for mods::tags::GetModTags<'_> {}
135
136    impl Sealed for files::AddFile<'_> {}
137    impl Sealed for files::EditFile<'_> {}
138    impl Sealed for files::GetFile<'_> {}
139    impl Sealed for files::GetFiles<'_> {}
140    impl Sealed for files::ManagePlatformStatus<'_> {}
141    impl Sealed for files::multipart::AddMultipartUploadFile<'_> {}
142    impl<S> Sealed for files::multipart::AddMultipartUploadPart<'_, S> {}
143    impl Sealed for files::multipart::CompleteMultipartUploadSession<'_> {}
144    impl Sealed for files::multipart::CreateMultipartUploadSession<'_> {}
145    impl Sealed for files::multipart::GetMultipartUploadParts<'_> {}
146    impl Sealed for files::multipart::GetMultipartUploadSessions<'_> {}
147
148    impl Sealed for user::GetAuthenticatedUser<'_> {}
149    impl Sealed for user::GetMutedUsers<'_> {}
150    impl Sealed for user::GetUserEvents<'_> {}
151    impl Sealed for user::GetUserFiles<'_> {}
152    impl Sealed for user::GetUserGames<'_> {}
153    impl Sealed for user::GetUserMods<'_> {}
154    impl Sealed for user::GetUserRatings<'_> {}
155    impl Sealed for user::GetUserSubscriptions<'_> {}
156}