rosu_v2/request/
mod.rs

1/// Implements [`OsuFutureData`] and [`IntoFuture`] for requests.
2///
3/// [`OsuFutureData`]: crate::future::OsuFutureData
4/// [`IntoFuture`]: std::future::IntoFuture
5macro_rules! into_future {
6    // New OsuFuture with optional post processing
7    (
8        |$self:ident: $ty:ty| -> $from_bytes:ty { $( $req_body:tt )+ }
9        $( =>
10            |
11                $from_bytes_arg:tt,
12                $post_process_data_arg:tt $(: $post_process_data:ty )?
13            | -> $output:ty { $( $post_process_body:tt )+ }
14        )?
15    ) => {
16        impl crate::future::OsuFutureData for $ty {
17            type FromBytes = $from_bytes;
18            type OsuOutput = into_future!(OUTPUT_TY $from_bytes $( | $output )?);
19            type FromUserData = ();
20            type PostProcessData = into_future!(POST_PROCESS_DATA $( $( $post_process_data )? )?);
21        }
22
23        impl std::future::IntoFuture for $ty {
24            type Output = crate::OsuResult<into_future!(OUTPUT_TY $from_bytes $( | $output )?)>;
25            type IntoFuture = crate::future::OsuFuture<Self>;
26
27            fn into_future($self) -> Self::IntoFuture {
28                let res = { $( $req_body )* };
29                let (req, data) =
30                    crate::future::IntoPostProcessData::into_data(res);
31
32                let post_process_fn = into_future!(POST_PROCESS_FN $(
33                    |
34                        $from_bytes_arg: $from_bytes,
35                        $post_process_data_arg $(: $post_process_data )?
36                    | { $( $post_process_body )* }
37                )?);
38
39                crate::future::OsuFuture::new(
40                    $self.osu,
41                    req,
42                    data,
43                    post_process_fn,
44                )
45            }
46        }
47    };
48
49    // OsuFuture from UserId with optional post processing
50    (
51        |$self:ident: $ty:ty| -> $from_bytes:ty {
52            $from_user_data:ident {
53                $( $data_field_name:ident: $data_field_ty:ty = $data_field_value:expr, )*
54            }
55        } => |$user_id_arg:tt, $from_user_data_arg:tt| { $( $req_body:tt )* }
56        $(
57            => |$from_bytes_arg:tt, $post_process_data_arg:tt $(: $post_process_data:ty )?|
58                -> $output:ty { $( $post_process_body:tt )* }
59        )?
60    ) => {
61        #[doc(hidden)]
62        pub struct $from_user_data {
63            $( $data_field_name: $data_field_ty, )*
64        }
65
66        impl crate::future::OsuFutureData for $ty {
67            type FromBytes = $from_bytes;
68            type OsuOutput = into_future!(OUTPUT_TY $from_bytes $( | $output )?);
69            type FromUserData = $from_user_data;
70            type PostProcessData = into_future!(POST_PROCESS_DATA $( $( $post_process_data )? )?);
71        }
72
73        impl std::future::IntoFuture for $ty {
74            type Output = crate::OsuResult<into_future!(OUTPUT_TY $from_bytes $( | $output )?)>;
75            type IntoFuture = crate::future::OsuFuture<Self>;
76
77            fn into_future($self) -> Self::IntoFuture {
78                let from_user_data = $from_user_data {
79                    $( $data_field_name: $data_field_value, )*
80                };
81
82                let from_user_fn = |$user_id_arg: u32, $from_user_data_arg: $from_user_data| {
83                    $( $req_body )*
84                };
85
86                let post_process_fn = into_future!(POST_PROCESS_FN $(
87                    |
88                        $from_bytes_arg: $from_bytes,
89                        $post_process_data_arg $(: $post_process_data )?
90                    | { $( $post_process_body )* }
91                )?);
92
93                crate::future::OsuFuture::from_user_id(
94                    $self.osu,
95                    $self.user_id,
96                    from_user_data,
97                    from_user_fn,
98                    (), // FIXME: handle different post process data types
99                    post_process_fn,
100                )
101            }
102        }
103    };
104
105    // Helper rules
106
107    ( OUTPUT_TY $output:ty ) => {
108        $output
109    };
110    ( OUTPUT_TY $from_bytes:ty | $output:ty ) => {
111        $output
112    };
113    ( POST_PROCESS_DATA ) => {
114        ()
115    };
116    ( POST_PROCESS_DATA $data:ty ) => {
117        $data
118    };
119    ( POST_PROCESS_FN ) => {
120        crate::future::noop_post_process
121    };
122    ( POST_PROCESS_FN
123        |$from_bytes_arg:tt: $from_bytes:ty, $data_arg:tt $(: $data:ty )?|
124            { $( $post_process_body:tt )* }
125    ) => {
126        |
127            #[allow(unused_mut)]
128            mut $from_bytes_arg: $from_bytes,
129            $data_arg: into_future!(POST_PROCESS_DATA $( $data )?),
130        | { $( $post_process_body )* }
131    };
132}
133
134use itoa::{Buffer, Integer};
135use serde::Serialize;
136
137use crate::routing::Route;
138
139pub use crate::future::OsuFuture;
140
141pub use self::{
142    beatmap::*, comments::*, event::*, forum::*, matches::*, news::*, ranking::*, replay::*,
143    score::*, seasonal_backgrounds::*, user::*, wiki::*,
144};
145
146mod beatmap;
147mod comments;
148mod event;
149mod forum;
150mod matches;
151mod news;
152mod ranking;
153mod replay;
154mod score;
155mod seasonal_backgrounds;
156mod serialize;
157mod user;
158mod wiki;
159
160#[derive(Copy, Clone)]
161pub(crate) enum Method {
162    Get,
163    Post,
164}
165
166impl Method {
167    pub const fn into_hyper(self) -> hyper::Method {
168        match self {
169            Method::Get => hyper::Method::GET,
170            Method::Post => hyper::Method::POST,
171        }
172    }
173}
174
175pub(crate) struct Request {
176    pub query: Option<String>,
177    pub route: Route,
178    pub body: JsonBody,
179    pub api_version: u32,
180}
181
182impl Request {
183    #[allow(clippy::unreadable_literal)]
184    const API_VERSION: u32 = 20220705;
185
186    const fn new(route: Route) -> Self {
187        Self::with_body(route, JsonBody::new())
188    }
189
190    const fn with_body(route: Route, body: JsonBody) -> Self {
191        Self {
192            query: None,
193            route,
194            body,
195            api_version: Self::API_VERSION,
196        }
197    }
198
199    const fn with_query(route: Route, query: String) -> Self {
200        Self::with_query_and_body(route, query, JsonBody::new())
201    }
202
203    const fn with_query_and_body(route: Route, query: String, body: JsonBody) -> Self {
204        Self {
205            query: Some(query),
206            route,
207            body,
208            api_version: Self::API_VERSION,
209        }
210    }
211
212    const fn api_version(&mut self, api_version: u32) {
213        self.api_version = api_version;
214    }
215}
216
217pub(crate) struct JsonBody {
218    inner: Vec<u8>,
219}
220
221impl JsonBody {
222    pub(crate) const fn new() -> Self {
223        Self { inner: Vec::new() }
224    }
225
226    fn push_prefix(&mut self) {
227        let prefix = if self.inner.is_empty() { b'{' } else { b',' };
228        self.inner.push(prefix);
229    }
230
231    fn push_key(&mut self, key: &[u8]) {
232        self.push_prefix();
233        self.inner.push(b'\"');
234        self.inner.extend_from_slice(key);
235        self.inner.extend_from_slice(b"\":");
236    }
237
238    fn push_value(&mut self, value: &[u8]) {
239        self.inner.push(b'\"');
240        self.inner.extend_from_slice(value);
241        self.inner.push(b'\"');
242    }
243
244    pub(crate) fn push_str(&mut self, key: &str, value: &str) {
245        self.inner.reserve(4 + key.len() + 2 + value.len());
246
247        self.push_key(key.as_bytes());
248        self.push_value(value.as_bytes());
249    }
250
251    pub(crate) fn push_int(&mut self, key: &str, int: impl Integer) {
252        let mut buf = Buffer::new();
253        let int = buf.format(int);
254
255        self.inner.reserve(4 + key.len() + int.len());
256
257        self.push_key(key.as_bytes());
258        self.push_value(int.as_bytes());
259    }
260
261    pub(crate) fn into_bytes(mut self) -> Vec<u8> {
262        if !self.inner.is_empty() {
263            self.inner.push(b'}');
264        }
265
266        self.inner
267    }
268}
269
270struct Query;
271
272impl Query {
273    fn encode<T: Serialize>(query: &T) -> String {
274        serde_urlencoded::to_string(query).expect("serde_urlencoded should not fail")
275    }
276}