1use std::{
2 future::{Future, IntoFuture},
3 ops::ControlFlow,
4 pin::Pin,
5 sync::Arc,
6 task::{Context, Poll},
7};
8
9use pin_project::pin_project;
10
11use crate::{
12 request::{GetUser, Request, UserId},
13 Osu, OsuResult,
14};
15
16use self::stage::{OsuFutureStage, OsuRequestStageInner};
17
18pub(crate) use self::token::TokenFuture;
19
20pub use self::traits::*;
21
22mod request_generator;
23mod stage;
24mod token;
25mod traits;
26
27type FromUserFn<T> = fn(u32, <T as OsuFutureData>::FromUserData) -> Request;
28
29struct FromUser<T: OsuFutureData> {
30 data: T::FromUserData,
31 f: FromUserFn<T>,
32}
33
34type PostProcessFn<T> = fn(
35 <T as OsuFutureData>::FromBytes,
36 <T as OsuFutureData>::PostProcessData,
37) -> OsuResult<<T as OsuFutureData>::OsuOutput>;
38
39struct PostProcess<T: OsuFutureData> {
40 data: T::PostProcessData,
41 f: PostProcessFn<T>,
42}
43
44#[pin_project]
52pub struct OsuFuture<T: OsuFutureData> {
53 #[pin]
54 stage: OsuFutureStage,
55 from_user: Option<FromUser<T>>,
56 post_process: Option<PostProcess<T>>,
57}
58
59impl<T: OsuFutureData> OsuFuture<T> {
60 pub(crate) fn new(
62 osu: &Osu,
63 req: Request,
64 post_process_data: T::PostProcessData,
65 post_process_fn: PostProcessFn<T>,
66 ) -> Self {
67 let osu = Arc::clone(&osu.inner);
68
69 Self {
70 stage: OsuRequestStageInner::new(osu, req)
71 .map_or_else(OsuFutureStage::Failed, OsuFutureStage::Final),
72 from_user: None,
73 post_process: Some(PostProcess {
74 data: post_process_data,
75 f: post_process_fn,
76 }),
77 }
78 }
79
80 pub(crate) fn from_user_id(
83 osu: &Osu,
84 user_id: UserId,
85 from_user_data: T::FromUserData,
86 from_user_fn: FromUserFn<T>,
87 post_process_data: T::PostProcessData,
88 post_process_fn: PostProcessFn<T>,
89 ) -> Self {
90 #[cfg(not(feature = "cache"))]
91 let get_user_id: fn(UserId) -> UserId = std::convert::identity;
92
93 #[cfg(feature = "cache")]
94 fn get_user_id(mut user_id: UserId, osu: &Osu) -> UserId {
95 if let UserId::Name(ref mut name) = user_id {
96 name.make_ascii_lowercase();
97
98 if let Some(id) = osu.inner.cache.get(name) {
99 return UserId::Id(*id);
100 }
101 }
102
103 user_id
104 }
105
106 match get_user_id(
107 user_id,
108 #[cfg(feature = "cache")]
109 osu,
110 ) {
111 UserId::Id(user_id) => {
112 let req = from_user_fn(user_id, from_user_data);
113
114 Self::new(osu, req, post_process_data, post_process_fn)
115 }
116 user_id @ UserId::Name(_) => {
117 #[cfg(not(feature = "cache"))]
118 {
119 static NOTIF: std::sync::Once = std::sync::Once::new();
120
121 NOTIF.call_once(|| {
127 warn!(
128 "Fetching from a user endpoint by username will \
129 always perform two requests because the `cache` \
130 feature is not enabled"
131 );
132 });
133 }
134
135 let osu = Arc::clone(&osu.inner);
136 let req = GetUser::create_request(user_id, None);
137
138 Self {
139 stage: OsuRequestStageInner::new(osu, req)
140 .map_or_else(OsuFutureStage::Failed, OsuFutureStage::User),
141 from_user: Some(FromUser {
142 data: from_user_data,
143 f: from_user_fn,
144 }),
145 post_process: Some(PostProcess {
146 data: post_process_data,
147 f: post_process_fn,
148 }),
149 }
150 }
151 }
152 }
153}
154
155impl<T: OsuFutureData> Future for OsuFuture<T> {
156 type Output = <T as IntoFuture>::Output;
157
158 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
159 let mut this = self.as_mut().project();
160
161 match this.stage.as_mut().poll(cx) {
162 Poll::Ready(ControlFlow::Break(Ok((bytes, osu)))) => {
163 let res = <T::FromBytes>::from_bytes(bytes)?;
164 let PostProcess { data, f } =
165 this.post_process.take().expect("missing post_process");
166
167 let value = f(res, data)?;
168
169 #[cfg(feature = "cache")]
170 crate::model::ContainedUsers::apply_to_users(&value, |id, name| {
171 osu.update_cache(id, name);
172 });
173
174 let _ = osu;
176
177 Poll::Ready(Ok(value))
178 }
179 Poll::Ready(ControlFlow::Continue((user, osu))) => {
180 #[cfg(feature = "cache")]
181 osu.update_cache(user.user_id, &user.username);
182
183 #[cfg(feature = "metrics")]
184 ::metrics::counter!(crate::metrics::USERNAME_CACHE_SIZE).increment(1);
191
192 let FromUser { data, f } = this.from_user.take().expect("missing from_user");
193 let req = f(user.user_id, data);
194
195 let next = OsuRequestStageInner::new(osu, req)?;
196 this.stage.project_replace(OsuFutureStage::Final(next));
197
198 self.poll(cx)
199 }
200 Poll::Ready(ControlFlow::Break(Err(err))) => Poll::Ready(Err(err)),
201 Poll::Pending => Poll::Pending,
202 }
203 }
204}
205
206#[allow(clippy::unnecessary_wraps)]
207pub(crate) const fn noop_post_process<T>(value: T, _: ()) -> OsuResult<T> {
208 Ok(value)
209}