Skip to main content

roblox_api/
endpoint.rs

1#[macro_export]
2macro_rules! endpoint {
3    // -----------------------------------------------------------------------
4    // Internal: method ident → reqwest::Method
5    // -----------------------------------------------------------------------
6    (@method GET)    => { ::reqwest::Method::GET    };
7    (@method POST)   => { ::reqwest::Method::POST   };
8    (@method PUT)    => { ::reqwest::Method::PUT    };
9    (@method PATCH)  => { ::reqwest::Method::PATCH  };
10    (@method DELETE) => { ::reqwest::Method::DELETE };
11
12    // -----------------------------------------------------------------------
13    // Internal: emit Some(&body) or None::<&()>
14    // -----------------------------------------------------------------------
15    (@body $body:expr) => { Some(&$body) };
16    (@body)            => { None::<&()> };
17
18    // -----------------------------------------------------------------------
19    // Internal: decode dispatch
20    // -----------------------------------------------------------------------
21    (@decode $resp:ident, $ret:ty, json)              => { $resp.json::<$ret>().await };
22    (@decode $resp:ident, $ret:ty, bytes)             => { $resp.bytes().await        };
23    (@decode $resp:ident, $ret:ty, void)              => { Ok(())                     };
24    (@decode $resp:ident, $ret:ty, [map $r:ident : $wrap:ty => $proj:expr]) => {
25        $resp.json::<$wrap>().await.map(|$r| $proj)
26    };
27
28    // -----------------------------------------------------------------------
29    // Internal: type definition dispatcher (TT muncher)
30    //
31    // Direct struct emission — fields matched inline with optional rename:
32    //   name("literal"): Type    →   #[serde(rename = "literal")] pub name: Type,
33    //   name: Type               →   pub name: Type,
34    //
35    // Derive selection by type name:
36    //   Request  →  Serialize
37    //   Response →  Deserialize
38    //   other    →  Serialize, Deserialize
39    // -----------------------------------------------------------------------
40    // Request<'a>
41    (@types_entry Request<$lt:lifetime> { $($field:ident $( ( $rename:literal ) )? : $ftype:ty),* $(,)? } $($rest:tt)*) => {
42        #[derive(Serialize)]
43        struct Request<$lt> {
44            $(
45                $(#[serde(rename = $rename)])?
46                pub $field: $ftype,
47            )*
48        }
49        endpoint!(@types_entry $($rest)*)
50    };
51    // Request
52    (@types_entry Request { $($field:ident $( ( $rename:literal ) )? : $ftype:ty),* $(,)? } $($rest:tt)*) => {
53        #[derive(Serialize)]
54        struct Request {
55            $(
56                $(#[serde(rename = $rename)])?
57                pub $field: $ftype,
58            )*
59        }
60        endpoint!(@types_entry $($rest)*)
61    };
62
63    // Response<'a>
64    (@types_entry Response<$lt:lifetime> { $($field:ident $( ( $rename:literal ) )? : $ftype:ty),* $(,)? } $($rest:tt)*) => {
65        #[derive(Deserialize)]
66        struct Response<$lt> {
67            $(
68                $(#[serde(rename = $rename)])?
69                pub $field: $ftype,
70            )*
71        }
72        endpoint!(@types_entry $($rest)*)
73    };
74    // Response
75    (@types_entry Response { $($field:ident $( ( $rename:literal ) )? : $ftype:ty),* $(,)? } $($rest:tt)*) => {
76        #[derive(Deserialize)]
77        struct Response {
78            $(
79                $(#[serde(rename = $rename)])?
80                pub $field: $ftype,
81            )*
82        }
83        endpoint!(@types_entry $($rest)*)
84    };
85
86    // Named type with lifetime <'a>
87    (@types_entry $name:ident<$lt:lifetime> { $($field:ident $( ( $rename:literal ) )? : $ftype:ty),* $(,)? } $($rest:tt)*) => {
88        #[derive(Serialize, Deserialize)]
89        struct $name<$lt> {
90            $(
91                $(#[serde(rename = $rename)])?
92                pub $field: $ftype,
93            )*
94        }
95        endpoint!(@types_entry $($rest)*)
96    };
97    // Named type (no lifetime)
98    (@types_entry $name:ident { $($field:ident $( ( $rename:literal ) )? : $ftype:ty),* $(,)? } $($rest:tt)*) => {
99        #[derive(Serialize, Deserialize)]
100        struct $name {
101            $(
102                $(#[serde(rename = $rename)])?
103                pub $field: $ftype,
104            )*
105        }
106        endpoint!(@types_entry $($rest)*)
107    };
108
109    // Unit structs (bare name, no braces)
110    (@types_entry Request $($rest:tt)*) => {
111        #[derive(Serialize)] struct Request;
112        endpoint!(@types_entry $($rest)*)
113    };
114    (@types_entry Response $($rest:tt)*) => {
115        #[derive(Deserialize)] struct Response;
116        endpoint!(@types_entry $($rest)*)
117    };
118    (@types_entry $name:ident $($rest:tt)*) => {
119        #[derive(Serialize, Deserialize)] struct $name;
120        endpoint!(@types_entry $($rest)*)
121    };
122
123    // Base case — no more types
124    (@types_entry) => {};
125
126    // -----------------------------------------------------------------------
127    // Internal: generate the function body
128    // -----------------------------------------------------------------------
129    (@fn
130        attrs     = [$(#[$attr:meta])*],
131        client    = $client:ident,
132        name      = $name:ident,
133        params    = [$($param:ident : $param_ty:ty),*],
134        ret       = $ret:ty,
135        method    = $method:ident,
136        url       = $url:expr,
137        paging    = [$($p_var:ident, $p_limit:literal)?],
138        types     = [$($types:tt)*],
139        pre       = [$($pre:tt)*],
140        query     = [$($qk:literal => $qv:expr),*],
141        body      = [$($body_expr:expr)?],
142        decode    = $decode:tt,
143    ) => {
144        $(#[$attr])*
145        #[allow(unused_variables)]
146        pub async fn $name(
147            $client: &mut $crate::client::Client,
148            $($param: $param_ty,)*
149        ) -> Result<$ret, $crate::Error> {
150            endpoint!(@types_entry $($types)*);
151            $($pre)*
152
153            // Paging query — generated when paging_query { ... } is present
154            let mut __query_pairs: ::std::vec::Vec<(&str, &str)> = ::std::vec::Vec::new();
155            $(
156                let __pq_limit = $p_var.limit.unwrap_or($p_limit).to_string();
157                let __pq_order = $p_var.order.unwrap_or_default().to_string();
158                let __pq_cursor = match $p_var.cursor {
159                    Some(cursor) => cursor.to_string(),
160                    None => ::std::string::String::new(),
161                };
162                __query_pairs.push(("limit", &__pq_limit));
163                __query_pairs.push(("sortOrder", &__pq_order));
164                __query_pairs.push(("cursor", &__pq_cursor));
165            )?
166
167            let url: ::std::string::String = ::std::format!($url);
168
169            $( __query_pairs.push(($qk, $qv)); )*
170            let query = if __query_pairs.is_empty() { None } else { Some(__query_pairs.as_slice()) };
171
172            let __response = $client
173                .requestor
174                .request(
175                    endpoint!(@method $method),
176                    &url,
177                    endpoint!(@body $($body_expr)?),
178                    query,
179                    None,
180                )
181                .await?;
182            endpoint!(@decode __response, $ret, $decode)
183        }
184    };
185
186    // -----------------------------------------------------------------------
187    // Internal: parse body — map variant
188    // -----------------------------------------------------------------------
189    (@body_inner
190        attrs     = [$(#[$attr:meta])*],
191        client    = $client:ident,
192        name      = $name:ident,
193        params    = [$($param:ident : $param_ty:ty),*],
194        ret       = $ret:ty,
195        body      = [
196            $method:ident $url:expr;
197            $(paging_query { $p_var:ident, limit = $p_limit:literal })?
198            $(types    { $($types:tt)* })?
199            $(prelude  { $($pre:tt)* })?
200            $(query    { $($qk:literal => $qv:expr),* $(,)? })?
201            $(body_serialize { $body_expr:expr })?
202            map |$r:ident : $wrap:ty| $proj:expr
203        ],
204    ) => {
205        endpoint!(@fn
206            attrs     = [$(#[$attr])*],
207            client    = $client,
208            name      = $name,
209            params    = [$($param : $param_ty),*],
210            ret       = $ret,
211            method    = $method,
212            url       = $url,
213            paging    = [$($p_var, $p_limit)?],
214            types     = [$($($types)*)?],
215            pre       = [$($($pre)*)?],
216            query     = [$($($qk => $qv),*)?],
217            body      = [$($body_expr)?],
218            decode    = [map $r : $wrap => $proj],
219        );
220    };
221
222    // -----------------------------------------------------------------------
223    // Internal: parse body — raw_bytes variant
224    // -----------------------------------------------------------------------
225    (@body_inner
226        attrs     = [$(#[$attr:meta])*],
227        client    = $client:ident,
228        name      = $name:ident,
229        params    = [$($param:ident : $param_ty:ty),*],
230        ret       = $ret:ty,
231        body      = [
232            $method:ident $url:expr;
233            $(paging_query { $p_var:ident, limit = $p_limit:literal })?
234            $(types    { $($types:tt)* })?
235            $(prelude  { $($pre:tt)* })?
236            $(query    { $($qk:literal => $qv:expr),* $(,)? })?
237            $(body_serialize { $body_expr:expr })?
238            raw_bytes
239        ],
240    ) => {
241        endpoint!(@fn
242            attrs     = [$(#[$attr])*],
243            client    = $client,
244            name      = $name,
245            params    = [$($param : $param_ty),*],
246            ret       = $ret,
247            method    = $method,
248            url       = $url,
249            paging    = [$($p_var, $p_limit)?],
250            types     = [$($($types)*)?],
251            pre       = [$($($pre)*)?],
252            query     = [$($($qk => $qv),*)?],
253            body      = [$($body_expr)?],
254            decode    = bytes,
255        );
256    };
257
258    // -----------------------------------------------------------------------
259    // Internal: parse body — void variant
260    // -----------------------------------------------------------------------
261    (@body_inner
262        attrs     = [$(#[$attr:meta])*],
263        client    = $client:ident,
264        name      = $name:ident,
265        params    = [$($param:ident : $param_ty:ty),*],
266        ret       = $ret:ty,
267        body      = [
268            $method:ident $url:expr;
269            $(paging_query { $p_var:ident, limit = $p_limit:literal })?
270            $(types    { $($types:tt)* })?
271            $(prelude  { $($pre:tt)* })?
272            $(query    { $($qk:literal => $qv:expr),* $(,)? })?
273            $(body_serialize { $body_expr:expr })?
274            void
275        ],
276    ) => {
277        endpoint!(@fn
278            attrs     = [$(#[$attr])*],
279            client    = $client,
280            name      = $name,
281            params    = [$($param : $param_ty),*],
282            ret       = (),
283            method    = $method,
284            url       = $url,
285            paging    = [$($p_var, $p_limit)?],
286            types     = [$($($types)*)?],
287            pre       = [$($($pre)*)?],
288            query     = [$($($qk => $qv),*)?],
289            body      = [$($body_expr)?],
290            decode    = void,
291        );
292    };
293
294    // -----------------------------------------------------------------------
295    // Internal: parse body — plain JSON variant
296    // -----------------------------------------------------------------------
297    (@body_inner
298        attrs     = [$(#[$attr:meta])*],
299        client    = $client:ident,
300        name      = $name:ident,
301        params    = [$($param:ident : $param_ty:ty),*],
302        ret       = $ret:ty,
303        body      = [
304            $method:ident $url:expr;
305            $(paging_query { $p_var:ident, limit = $p_limit:literal })?
306            $(types    { $($types:tt)* })?
307            $(prelude  { $($pre:tt)* })?
308            $(query    { $($qk:literal => $qv:expr),* $(,)? })?
309            $(body_serialize { $body_expr:expr })?
310        ],
311    ) => {
312        endpoint!(@fn
313            attrs     = [$(#[$attr])*],
314            client    = $client,
315            name      = $name,
316            params    = [$($param : $param_ty),*],
317            ret       = $ret,
318            method    = $method,
319            url       = $url,
320            paging    = [$($p_var, $p_limit)?],
321            types     = [$($($types)*)?],
322            pre       = [$($($pre)*)?],
323            query     = [$($($qk => $qv),*)?],
324            body      = [$($body_expr)?],
325            decode    = json,
326        );
327    };
328
329    // -----------------------------------------------------------------------
330    // Entry: top-level types block
331    // -----------------------------------------------------------------------
332    (
333        types { $($types:tt)* }
334        $($rest:tt)*
335    ) => {
336        endpoint!(@types_entry $($types)*);
337        endpoint!($($rest)*);
338    };
339
340    // -----------------------------------------------------------------------
341    // Entry: with explicit `client` parameter
342    // -----------------------------------------------------------------------
343    (
344        $(#[$attr:meta])*
345        $name:ident($cli:ident $(, $param:ident : $param_ty:ty)*) -> $ret:ty {
346            $($body:tt)*
347        }
348        $($rest:tt)*
349    ) => {
350        endpoint!(@body_inner
351            attrs     = [$(#[$attr])*],
352            client    = $cli,
353            name      = $name,
354            params    = [$($param : $param_ty),*],
355            ret       = $ret,
356            body      = [$($body)*],
357        );
358        endpoint!($($rest)*);
359    };
360
361    // -----------------------------------------------------------------------
362    // Entry: without explicit `client` parameter
363    // -----------------------------------------------------------------------
364    (
365        $(#[$attr:meta])*
366        $name:ident($($param:ident : $param_ty:ty),*) -> $ret:ty {
367            $($body:tt)*
368        }
369        $($rest:tt)*
370    ) => {
371        endpoint!(@body_inner
372            attrs     = [$(#[$attr])*],
373            client    = client,
374            name      = $name,
375            params    = [$($param : $param_ty),*],
376            ret       = $ret,
377            body      = [$($body)*],
378        );
379        endpoint!($($rest)*);
380    };
381
382    // -----------------------------------------------------------------------
383    // Base case: nothing left to process
384    // -----------------------------------------------------------------------
385    () => {};
386}