serenity/http/
routing.rs

1use std::borrow::Cow;
2use std::mem::Discriminant;
3use std::num::NonZeroU64;
4
5use crate::model::id::*;
6
7/// Used to group requests together for ratelimiting.
8#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
9pub struct RatelimitingBucket(Option<(std::mem::Discriminant<Route<'static>>, Option<NonZeroU64>)>);
10
11impl RatelimitingBucket {
12    #[must_use]
13    pub fn is_none(&self) -> bool {
14        self.0.is_none()
15    }
16}
17
18enum RatelimitingKind {
19    /// Requests with the same path and major parameter (usually an Id) should be grouped together
20    /// for ratelimiting.
21    PathAndId(NonZeroU64),
22    /// Requests with the same path should be ratelimited together.
23    Path,
24}
25
26/// A macro for defining routes as well as the type of ratelimiting they perform. Takes as input a
27/// list of route definitions, and generates a definition for the `Route` enum and implements
28/// methods on it.
29macro_rules! routes {
30    ($lt:lifetime, {
31        $(
32            $name:ident $({ $($field_name:ident: $field_type:ty),* })?,
33            $path:expr,
34            $ratelimiting_kind:expr;
35        )+
36    }) => {
37        #[derive(Clone, Copy, Debug)]
38        pub enum Route<$lt> {
39            $(
40                $name $({ $($field_name: $field_type),* })?,
41            )+
42        }
43
44        impl<$lt> Route<$lt> {
45            #[must_use]
46            pub fn path(self) -> Cow<'static, str> {
47                match self {
48                    $(
49                        Self::$name $({ $($field_name),* })? => $path.into(),
50                    )+
51                }
52            }
53
54            #[must_use]
55            pub fn ratelimiting_bucket(&self) -> RatelimitingBucket {
56                #[allow(unused_variables)]
57                let ratelimiting_kind = match *self {
58                    $(
59                        Self::$name $({ $($field_name),* })? => $ratelimiting_kind,
60                    )+
61                };
62
63                // This avoids adding a lifetime on RatelimitingBucket and causing lifetime infection
64                // SAFETY: std::mem::discriminant erases lifetimes.
65                let discriminant = unsafe {
66                    std::mem::transmute::<Discriminant<Route<'a>>, Discriminant<Route<'static>>>(
67                        std::mem::discriminant(self),
68                    )
69                };
70
71                RatelimitingBucket(ratelimiting_kind.map(|r| {
72                    let id = match r {
73                        RatelimitingKind::PathAndId(id) => Some(id),
74                        RatelimitingKind::Path => None,
75                    };
76                    (discriminant, id)
77                }))
78            }
79
80        }
81    };
82}
83
84// This macro takes as input a list of route definitions, represented in the following way:
85// 1. The first line defines an enum variant representing an endpoint.
86// 2. The second line provides the url for that endpoint.
87// 3. The third line indicates what type of ratelimiting the endpoint employs.
88routes! ('a, {
89    Channel { channel_id: ChannelId },
90    api!("/channels/{}", channel_id),
91    Some(RatelimitingKind::PathAndId(channel_id.into()));
92
93    ChannelInvites { channel_id: ChannelId },
94    api!("/channels/{}/invites", channel_id),
95    Some(RatelimitingKind::PathAndId(channel_id.into()));
96
97    ChannelMessage { channel_id: ChannelId, message_id: MessageId },
98    api!("/channels/{}/messages/{}", channel_id, message_id),
99    Some(RatelimitingKind::PathAndId(channel_id.into()));
100
101    ChannelMessageCrosspost { channel_id: ChannelId, message_id: MessageId },
102    api!("/channels/{}/messages/{}/crosspost", channel_id, message_id),
103    Some(RatelimitingKind::PathAndId(channel_id.into()));
104
105    ChannelMessageReaction { channel_id: ChannelId, message_id: MessageId, user_id: UserId, reaction: &'a str },
106    api!("/channels/{}/messages/{}/reactions/{}/{}", channel_id, message_id, reaction, user_id),
107    Some(RatelimitingKind::PathAndId(channel_id.into()));
108
109    ChannelMessageReactionMe { channel_id: ChannelId, message_id: MessageId, reaction: &'a str },
110    api!("/channels/{}/messages/{}/reactions/{}/@me", channel_id, message_id, reaction),
111    Some(RatelimitingKind::PathAndId(channel_id.into()));
112
113    ChannelMessageReactionEmoji { channel_id: ChannelId, message_id: MessageId, reaction: &'a str },
114    api!("/channels/{}/messages/{}/reactions/{}", channel_id, message_id, reaction),
115    Some(RatelimitingKind::PathAndId(channel_id.into()));
116
117    ChannelMessageReactions { channel_id: ChannelId, message_id: MessageId },
118    api!("/channels/{}/messages/{}/reactions", channel_id, message_id),
119    Some(RatelimitingKind::PathAndId(channel_id.into()));
120
121    ChannelMessages { channel_id: ChannelId },
122    api!("/channels/{}/messages", channel_id),
123    Some(RatelimitingKind::PathAndId(channel_id.into()));
124
125    ChannelMessagesBulkDelete { channel_id: ChannelId },
126    api!("/channels/{}/messages/bulk-delete", channel_id),
127    Some(RatelimitingKind::PathAndId(channel_id.into()));
128
129    ChannelFollowNews { channel_id: ChannelId },
130    api!("/channels/{}/followers", channel_id),
131    Some(RatelimitingKind::PathAndId(channel_id.into()));
132
133    ChannelPermission { channel_id: ChannelId, target_id: TargetId },
134    api!("/channels/{}/permissions/{}", channel_id, target_id),
135    Some(RatelimitingKind::PathAndId(channel_id.into()));
136
137    ChannelPin { channel_id: ChannelId, message_id: MessageId },
138    api!("/channels/{}/pins/{}", channel_id, message_id),
139    Some(RatelimitingKind::PathAndId(channel_id.into()));
140
141    ChannelPins { channel_id: ChannelId },
142    api!("/channels/{}/pins", channel_id),
143    Some(RatelimitingKind::PathAndId(channel_id.into()));
144
145    ChannelTyping { channel_id: ChannelId },
146    api!("/channels/{}/typing", channel_id),
147    Some(RatelimitingKind::PathAndId(channel_id.into()));
148
149    ChannelWebhooks { channel_id: ChannelId },
150    api!("/channels/{}/webhooks", channel_id),
151    Some(RatelimitingKind::PathAndId(channel_id.into()));
152
153    ChannelMessageThreads { channel_id: ChannelId, message_id: MessageId },
154    api!("/channels/{}/messages/{}/threads", channel_id, message_id),
155    Some(RatelimitingKind::PathAndId(channel_id.into()));
156
157    ChannelThreads { channel_id: ChannelId },
158    api!("/channels/{}/threads", channel_id),
159    Some(RatelimitingKind::PathAndId(channel_id.into()));
160
161    ChannelForumPosts { channel_id: ChannelId },
162    api!("/channels/{}/threads", channel_id),
163    Some(RatelimitingKind::PathAndId(channel_id.into()));
164
165    ChannelThreadMember { channel_id: ChannelId, user_id: UserId },
166    api!("/channels/{}/thread-members/{}", channel_id, user_id),
167    Some(RatelimitingKind::PathAndId(channel_id.into()));
168
169    ChannelThreadMemberMe { channel_id: ChannelId },
170    api!("/channels/{}/thread-members/@me", channel_id),
171    Some(RatelimitingKind::PathAndId(channel_id.into()));
172
173    ChannelThreadMembers { channel_id: ChannelId },
174    api!("/channels/{}/thread-members", channel_id),
175    Some(RatelimitingKind::PathAndId(channel_id.into()));
176
177    ChannelArchivedPublicThreads { channel_id: ChannelId },
178    api!("/channels/{}/threads/archived/public", channel_id),
179    Some(RatelimitingKind::PathAndId(channel_id.into()));
180
181    ChannelArchivedPrivateThreads { channel_id: ChannelId },
182    api!("/channels/{}/threads/archived/private", channel_id),
183    Some(RatelimitingKind::PathAndId(channel_id.into()));
184
185    ChannelJoinedPrivateThreads { channel_id: ChannelId },
186    api!("/channels/{}/users/@me/threads/archived/private", channel_id),
187    Some(RatelimitingKind::PathAndId(channel_id.into()));
188
189    ChannelPollGetAnswerVoters { channel_id: ChannelId, message_id: MessageId, answer_id: AnswerId },
190    api!("/channels/{}/polls/{}/answers/{}", channel_id, message_id, answer_id),
191    Some(RatelimitingKind::PathAndId(channel_id.into()));
192
193    ChannelPollExpire { channel_id: ChannelId, message_id: MessageId },
194    api!("/channels/{}/polls/{}/expire", channel_id, message_id),
195    Some(RatelimitingKind::PathAndId(channel_id.into()));
196
197    ChannelVoiceStatus { channel_id: ChannelId },
198    api!("/channels/{}/voice-status", channel_id),
199    Some(RatelimitingKind::PathAndId(channel_id.into()));
200
201    Gateway,
202    api!("/gateway"),
203    Some(RatelimitingKind::Path);
204
205    GatewayBot,
206    api!("/gateway/bot"),
207    Some(RatelimitingKind::Path);
208
209    Guild { guild_id: GuildId },
210    api!("/guilds/{}", guild_id),
211    Some(RatelimitingKind::PathAndId(guild_id.into()));
212
213    GuildAuditLogs { guild_id: GuildId },
214    api!("/guilds/{}/audit-logs", guild_id),
215    Some(RatelimitingKind::PathAndId(guild_id.into()));
216
217    GuildAutomodRule { guild_id: GuildId, rule_id: RuleId },
218    api!("/guilds/{}/auto-moderation/rules/{}", guild_id, rule_id),
219    Some(RatelimitingKind::PathAndId(guild_id.into()));
220
221    GuildAutomodRules { guild_id: GuildId },
222    api!("/guilds/{}/auto-moderation/rules", guild_id),
223    Some(RatelimitingKind::PathAndId(guild_id.into()));
224
225    GuildBan { guild_id: GuildId, user_id: UserId },
226    api!("/guilds/{}/bans/{}", guild_id, user_id),
227    Some(RatelimitingKind::PathAndId(guild_id.into()));
228
229    GuildBulkBan { guild_id: GuildId },
230    api!("/guilds/{}/bulk-ban", guild_id),
231    Some(RatelimitingKind::PathAndId(guild_id.into()));
232
233    GuildBans { guild_id: GuildId },
234    api!("/guilds/{}/bans", guild_id),
235    Some(RatelimitingKind::PathAndId(guild_id.into()));
236
237    GuildChannels { guild_id: GuildId },
238    api!("/guilds/{}/channels", guild_id),
239    Some(RatelimitingKind::PathAndId(guild_id.into()));
240
241    GuildWidget { guild_id: GuildId },
242    api!("/guilds/{}/widget", guild_id),
243    Some(RatelimitingKind::PathAndId(guild_id.into()));
244
245    GuildPreview { guild_id: GuildId },
246    api!("/guilds/{}/preview", guild_id),
247    Some(RatelimitingKind::PathAndId(guild_id.into()));
248
249    GuildEmojis { guild_id: GuildId },
250    api!("/guilds/{}/emojis", guild_id),
251    Some(RatelimitingKind::PathAndId(guild_id.into()));
252
253    GuildEmoji { guild_id: GuildId, emoji_id: EmojiId },
254    api!("/guilds/{}/emojis/{}", guild_id, emoji_id),
255    Some(RatelimitingKind::PathAndId(guild_id.into()));
256
257    GuildIntegration { guild_id: GuildId, integration_id: IntegrationId },
258    api!("/guilds/{}/integrations/{}", guild_id, integration_id),
259    Some(RatelimitingKind::PathAndId(guild_id.into()));
260
261    GuildIntegrationSync { guild_id: GuildId, integration_id: IntegrationId },
262    api!("/guilds/{}/integrations/{}/sync", guild_id, integration_id),
263    Some(RatelimitingKind::PathAndId(guild_id.into()));
264
265    GuildIntegrations { guild_id: GuildId },
266    api!("/guilds/{}/integrations", guild_id),
267    Some(RatelimitingKind::PathAndId(guild_id.into()));
268
269    GuildInvites { guild_id: GuildId },
270    api!("/guilds/{}/invites", guild_id),
271    Some(RatelimitingKind::PathAndId(guild_id.into()));
272
273    GuildMember { guild_id: GuildId, user_id: UserId },
274    api!("/guilds/{}/members/{}", guild_id, user_id),
275    Some(RatelimitingKind::PathAndId(guild_id.into()));
276
277    GuildMemberRole { guild_id: GuildId, user_id: UserId, role_id: RoleId },
278    api!("/guilds/{}/members/{}/roles/{}", guild_id, user_id, role_id),
279    Some(RatelimitingKind::PathAndId(guild_id.into()));
280
281    GuildMembers { guild_id: GuildId },
282    api!("/guilds/{}/members", guild_id),
283    Some(RatelimitingKind::PathAndId(guild_id.into()));
284
285    GuildMembersSearch { guild_id: GuildId },
286    api!("/guilds/{}/members/search", guild_id),
287    Some(RatelimitingKind::PathAndId(guild_id.into()));
288
289    GuildMemberMe { guild_id: GuildId },
290    api!("/guilds/{}/members/@me", guild_id),
291    Some(RatelimitingKind::PathAndId(guild_id.into()));
292
293    GuildMfa { guild_id: GuildId },
294    api!("/guilds/{}/mfa", guild_id),
295    Some(RatelimitingKind::PathAndId(guild_id.into()));
296
297    GuildPrune { guild_id: GuildId },
298    api!("/guilds/{}/prune", guild_id),
299    Some(RatelimitingKind::PathAndId(guild_id.into()));
300
301    GuildRegions { guild_id: GuildId },
302    api!("/guilds/{}/regions", guild_id),
303    Some(RatelimitingKind::PathAndId(guild_id.into()));
304
305    GuildRole { guild_id: GuildId, role_id: RoleId },
306    api!("/guilds/{}/roles/{}", guild_id, role_id),
307    Some(RatelimitingKind::PathAndId(guild_id.into()));
308
309    GuildRoles { guild_id: GuildId },
310    api!("/guilds/{}/roles", guild_id),
311    Some(RatelimitingKind::PathAndId(guild_id.into()));
312
313    GuildScheduledEvent { guild_id: GuildId, event_id: ScheduledEventId },
314    api!("/guilds/{}/scheduled-events/{}", guild_id, event_id),
315    Some(RatelimitingKind::PathAndId(guild_id.into()));
316
317    GuildScheduledEvents { guild_id: GuildId },
318    api!("/guilds/{}/scheduled-events", guild_id),
319    Some(RatelimitingKind::PathAndId(guild_id.into()));
320
321    GuildScheduledEventUsers { guild_id: GuildId, event_id: ScheduledEventId },
322    api!("/guilds/{}/scheduled-events/{}/users", guild_id, event_id),
323    Some(RatelimitingKind::PathAndId(guild_id.into()));
324
325    GuildSticker { guild_id: GuildId, sticker_id: StickerId },
326    api!("/guilds/{}/stickers/{}", guild_id, sticker_id),
327    Some(RatelimitingKind::PathAndId(guild_id.into()));
328
329    GuildStickers { guild_id: GuildId },
330    api!("/guilds/{}/stickers", guild_id),
331    Some(RatelimitingKind::PathAndId(guild_id.into()));
332
333    GuildVanityUrl { guild_id: GuildId },
334    api!("/guilds/{}/vanity-url", guild_id),
335    Some(RatelimitingKind::PathAndId(guild_id.into()));
336
337    GuildVoiceStates { guild_id: GuildId, user_id: UserId },
338    api!("/guilds/{}/voice-states/{}", guild_id, user_id),
339    Some(RatelimitingKind::PathAndId(guild_id.into()));
340
341    GuildVoiceStateMe { guild_id: GuildId },
342    api!("/guilds/{}/voice-states/@me", guild_id),
343    Some(RatelimitingKind::PathAndId(guild_id.into()));
344
345    GuildWebhooks { guild_id: GuildId },
346    api!("/guilds/{}/webhooks", guild_id),
347    Some(RatelimitingKind::PathAndId(guild_id.into()));
348
349    GuildWelcomeScreen { guild_id: GuildId },
350    api!("/guilds/{}/welcome-screen", guild_id),
351    Some(RatelimitingKind::PathAndId(guild_id.into()));
352
353    GuildThreadsActive { guild_id: GuildId },
354    api!("/guilds/{}/threads/active", guild_id),
355    Some(RatelimitingKind::PathAndId(guild_id.into()));
356
357    Guilds,
358    api!("/guilds"),
359    Some(RatelimitingKind::Path);
360
361    Invite { code: &'a str },
362    api!("/invites/{}", code),
363    Some(RatelimitingKind::Path);
364
365    Oauth2ApplicationCurrent,
366    api!("/oauth2/applications/@me"),
367    None;
368
369    StatusIncidentsUnresolved,
370    status!("/incidents/unresolved.json"),
371    None;
372
373    StatusMaintenancesActive,
374    status!("/scheduled-maintenances/active.json"),
375    None;
376
377    StatusMaintenancesUpcoming,
378    status!("/scheduled-maintenances/upcoming.json"),
379    None;
380
381    Sticker { sticker_id: StickerId },
382    api!("/stickers/{}", sticker_id),
383    Some(RatelimitingKind::Path);
384
385    StickerPacks,
386    api!("/sticker-packs"),
387    Some(RatelimitingKind::Path);
388
389    StickerPack { sticker_pack_id: StickerPackId },
390    api!("/sticker-packs/{}", sticker_pack_id),
391    Some(RatelimitingKind::Path);
392
393    User { user_id: UserId },
394    api!("/users/{}", user_id),
395    Some(RatelimitingKind::Path);
396
397    UserMe,
398    api!("/users/@me"),
399    Some(RatelimitingKind::Path);
400
401    UserMeConnections,
402    api!("/users/@me/connections"),
403    Some(RatelimitingKind::Path);
404
405    UserMeDmChannels,
406    api!("/users/@me/channels"),
407    Some(RatelimitingKind::Path);
408
409    UserMeGuild { guild_id: GuildId },
410    api!("/users/@me/guilds/{}", guild_id),
411    Some(RatelimitingKind::Path);
412
413    UserMeGuildMember { guild_id: GuildId },
414    api!("/users/@me/guilds/{}/member", guild_id),
415    Some(RatelimitingKind::Path);
416
417    UserMeGuilds,
418    api!("/users/@me/guilds"),
419    Some(RatelimitingKind::Path);
420
421    VoiceRegions,
422    api!("/voice/regions"),
423    Some(RatelimitingKind::Path);
424
425    Webhook { webhook_id: WebhookId },
426    api!("/webhooks/{}", webhook_id),
427    Some(RatelimitingKind::PathAndId(webhook_id.into()));
428
429    WebhookWithToken { webhook_id: WebhookId, token: &'a str },
430    api!("/webhooks/{}/{}", webhook_id, token),
431    Some(RatelimitingKind::PathAndId(webhook_id.into()));
432
433    WebhookMessage { webhook_id: WebhookId, token: &'a str, message_id: MessageId },
434    api!("/webhooks/{}/{}/messages/{}", webhook_id, token, message_id),
435    Some(RatelimitingKind::PathAndId(webhook_id.into()));
436
437    WebhookOriginalInteractionResponse { application_id: ApplicationId, token: &'a str },
438    api!("/webhooks/{}/{}/messages/@original", application_id, token),
439    Some(RatelimitingKind::PathAndId(application_id.into()));
440
441    WebhookFollowupMessage { application_id: ApplicationId, token: &'a str, message_id: MessageId },
442    api!("/webhooks/{}/{}/messages/{}", application_id, token, message_id),
443    Some(RatelimitingKind::PathAndId(application_id.into()));
444
445    WebhookFollowupMessages { application_id: ApplicationId, token: &'a str },
446    api!("/webhooks/{}/{}", application_id, token),
447    Some(RatelimitingKind::PathAndId(application_id.into()));
448
449    InteractionResponse { interaction_id: InteractionId, token: &'a str },
450    api!("/interactions/{}/{}/callback", interaction_id, token),
451    Some(RatelimitingKind::PathAndId(interaction_id.into()));
452
453    Command { application_id: ApplicationId, command_id: CommandId },
454    api!("/applications/{}/commands/{}", application_id, command_id),
455    Some(RatelimitingKind::PathAndId(application_id.into()));
456
457    Commands { application_id: ApplicationId },
458    api!("/applications/{}/commands", application_id),
459    Some(RatelimitingKind::PathAndId(application_id.into()));
460
461    GuildCommand { application_id: ApplicationId, guild_id: GuildId, command_id: CommandId },
462    api!("/applications/{}/guilds/{}/commands/{}", application_id, guild_id, command_id),
463    Some(RatelimitingKind::PathAndId(application_id.into()));
464
465    GuildCommandPermissions { application_id: ApplicationId, guild_id: GuildId, command_id: CommandId },
466    api!("/applications/{}/guilds/{}/commands/{}/permissions", application_id, guild_id, command_id),
467    Some(RatelimitingKind::PathAndId(application_id.into()));
468
469    GuildCommands { application_id: ApplicationId, guild_id: GuildId },
470    api!("/applications/{}/guilds/{}/commands", application_id, guild_id),
471    Some(RatelimitingKind::PathAndId(application_id.into()));
472
473    GuildCommandsPermissions { application_id: ApplicationId, guild_id: GuildId },
474    api!("/applications/{}/guilds/{}/commands/permissions", application_id, guild_id),
475    Some(RatelimitingKind::PathAndId(application_id.into()));
476
477    Skus { application_id: ApplicationId },
478    api!("/applications/{}/skus", application_id),
479    Some(RatelimitingKind::PathAndId(application_id.into()));
480
481    Emoji { application_id: ApplicationId, emoji_id: EmojiId },
482    api!("/applications/{}/emojis/{}", application_id, emoji_id),
483    Some(RatelimitingKind::PathAndId(application_id.into()));
484
485    Emojis { application_id: ApplicationId },
486    api!("/applications/{}/emojis", application_id),
487    Some(RatelimitingKind::PathAndId(application_id.into()));
488
489    Entitlement { application_id: ApplicationId, entitlement_id: EntitlementId },
490    api!("/applications/{}/entitlements/{}", application_id, entitlement_id),
491    Some(RatelimitingKind::PathAndId(application_id.into()));
492
493    Entitlements { application_id: ApplicationId },
494    api!("/applications/{}/entitlements", application_id),
495    Some(RatelimitingKind::PathAndId(application_id.into()));
496
497    StageInstances,
498    api!("/stage-instances"),
499    Some(RatelimitingKind::Path);
500
501    StageInstance { channel_id: ChannelId },
502    api!("/stage-instances/{}", channel_id),
503    Some(RatelimitingKind::Path);
504});