Skip to main content

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    GuildRoleMemberCounts { guild_id: GuildId },
314    api!("/guilds/{}/roles/member-counts", guild_id),
315    Some(RatelimitingKind::PathAndId(guild_id.into()));
316
317    GuildScheduledEvent { guild_id: GuildId, event_id: ScheduledEventId },
318    api!("/guilds/{}/scheduled-events/{}", guild_id, event_id),
319    Some(RatelimitingKind::PathAndId(guild_id.into()));
320
321    GuildScheduledEvents { guild_id: GuildId },
322    api!("/guilds/{}/scheduled-events", guild_id),
323    Some(RatelimitingKind::PathAndId(guild_id.into()));
324
325    GuildScheduledEventUsers { guild_id: GuildId, event_id: ScheduledEventId },
326    api!("/guilds/{}/scheduled-events/{}/users", guild_id, event_id),
327    Some(RatelimitingKind::PathAndId(guild_id.into()));
328
329    GuildSticker { guild_id: GuildId, sticker_id: StickerId },
330    api!("/guilds/{}/stickers/{}", guild_id, sticker_id),
331    Some(RatelimitingKind::PathAndId(guild_id.into()));
332
333    GuildStickers { guild_id: GuildId },
334    api!("/guilds/{}/stickers", guild_id),
335    Some(RatelimitingKind::PathAndId(guild_id.into()));
336
337    GuildVanityUrl { guild_id: GuildId },
338    api!("/guilds/{}/vanity-url", guild_id),
339    Some(RatelimitingKind::PathAndId(guild_id.into()));
340
341    GuildVoiceStates { guild_id: GuildId, user_id: UserId },
342    api!("/guilds/{}/voice-states/{}", guild_id, user_id),
343    Some(RatelimitingKind::PathAndId(guild_id.into()));
344
345    GuildVoiceStateMe { guild_id: GuildId },
346    api!("/guilds/{}/voice-states/@me", guild_id),
347    Some(RatelimitingKind::PathAndId(guild_id.into()));
348
349    GuildWebhooks { guild_id: GuildId },
350    api!("/guilds/{}/webhooks", guild_id),
351    Some(RatelimitingKind::PathAndId(guild_id.into()));
352
353    GuildWelcomeScreen { guild_id: GuildId },
354    api!("/guilds/{}/welcome-screen", guild_id),
355    Some(RatelimitingKind::PathAndId(guild_id.into()));
356
357    GuildThreadsActive { guild_id: GuildId },
358    api!("/guilds/{}/threads/active", guild_id),
359    Some(RatelimitingKind::PathAndId(guild_id.into()));
360
361    GuildIncidentActions { guild_id: GuildId },
362    api!("/guilds/{}/incident-actions", guild_id),
363    Some(RatelimitingKind::PathAndId(guild_id.into()));
364
365    Guilds,
366    api!("/guilds"),
367    Some(RatelimitingKind::Path);
368
369    Invite { code: &'a str },
370    api!("/invites/{}", code),
371    Some(RatelimitingKind::Path);
372
373    Oauth2ApplicationCurrent,
374    api!("/oauth2/applications/@me"),
375    None;
376
377    SoundboardSend { channel_id: ChannelId },
378    api!("/channels/{}/send-soundboard-sound", channel_id),
379    Some(RatelimitingKind::PathAndId(channel_id.into()));
380
381    SoundboardDefaultSounds,
382    api!("/soundboard-default-sounds"),
383    Some(RatelimitingKind::Path);
384
385    GuildSoundboards { guild_id: GuildId },
386    api!("/guilds/{}/soundboard-sounds", guild_id),
387    Some(RatelimitingKind::PathAndId(guild_id.into()));
388
389    GuildSoundboard { guild_id: GuildId, sound_id: SoundId },
390    api!("/guilds/{}/soundboard-sounds/{}", guild_id, sound_id),
391    Some(RatelimitingKind::PathAndId(guild_id.into()));
392
393    StatusIncidentsUnresolved,
394    status!("/incidents/unresolved.json"),
395    None;
396
397    StatusMaintenancesActive,
398    status!("/scheduled-maintenances/active.json"),
399    None;
400
401    StatusMaintenancesUpcoming,
402    status!("/scheduled-maintenances/upcoming.json"),
403    None;
404
405    Sticker { sticker_id: StickerId },
406    api!("/stickers/{}", sticker_id),
407    Some(RatelimitingKind::Path);
408
409    StickerPacks,
410    api!("/sticker-packs"),
411    Some(RatelimitingKind::Path);
412
413    StickerPack { sticker_pack_id: StickerPackId },
414    api!("/sticker-packs/{}", sticker_pack_id),
415    Some(RatelimitingKind::Path);
416
417    User { user_id: UserId },
418    api!("/users/{}", user_id),
419    Some(RatelimitingKind::Path);
420
421    UserMe,
422    api!("/users/@me"),
423    Some(RatelimitingKind::Path);
424
425    UserMeConnections,
426    api!("/users/@me/connections"),
427    Some(RatelimitingKind::Path);
428
429    UserMeDmChannels,
430    api!("/users/@me/channels"),
431    Some(RatelimitingKind::Path);
432
433    UserMeGuild { guild_id: GuildId },
434    api!("/users/@me/guilds/{}", guild_id),
435    Some(RatelimitingKind::Path);
436
437    UserMeGuildMember { guild_id: GuildId },
438    api!("/users/@me/guilds/{}/member", guild_id),
439    Some(RatelimitingKind::Path);
440
441    UserMeGuilds,
442    api!("/users/@me/guilds"),
443    Some(RatelimitingKind::Path);
444
445    VoiceRegions,
446    api!("/voice/regions"),
447    Some(RatelimitingKind::Path);
448
449    Webhook { webhook_id: WebhookId },
450    api!("/webhooks/{}", webhook_id),
451    Some(RatelimitingKind::PathAndId(webhook_id.into()));
452
453    WebhookWithToken { webhook_id: WebhookId, token: &'a str },
454    api!("/webhooks/{}/{}", webhook_id, token),
455    Some(RatelimitingKind::PathAndId(webhook_id.into()));
456
457    WebhookMessage { webhook_id: WebhookId, token: &'a str, message_id: MessageId },
458    api!("/webhooks/{}/{}/messages/{}", webhook_id, token, message_id),
459    Some(RatelimitingKind::PathAndId(webhook_id.into()));
460
461    WebhookOriginalInteractionResponse { application_id: ApplicationId, token: &'a str },
462    api!("/webhooks/{}/{}/messages/@original", application_id, token),
463    Some(RatelimitingKind::PathAndId(application_id.into()));
464
465    WebhookFollowupMessage { application_id: ApplicationId, token: &'a str, message_id: MessageId },
466    api!("/webhooks/{}/{}/messages/{}", application_id, token, message_id),
467    Some(RatelimitingKind::PathAndId(application_id.into()));
468
469    WebhookFollowupMessages { application_id: ApplicationId, token: &'a str },
470    api!("/webhooks/{}/{}", application_id, token),
471    Some(RatelimitingKind::PathAndId(application_id.into()));
472
473    InteractionResponse { interaction_id: InteractionId, token: &'a str },
474    api!("/interactions/{}/{}/callback", interaction_id, token),
475    Some(RatelimitingKind::PathAndId(interaction_id.into()));
476
477    Command { application_id: ApplicationId, command_id: CommandId },
478    api!("/applications/{}/commands/{}", application_id, command_id),
479    Some(RatelimitingKind::PathAndId(application_id.into()));
480
481    Commands { application_id: ApplicationId },
482    api!("/applications/{}/commands", application_id),
483    Some(RatelimitingKind::PathAndId(application_id.into()));
484
485    GuildCommand { application_id: ApplicationId, guild_id: GuildId, command_id: CommandId },
486    api!("/applications/{}/guilds/{}/commands/{}", application_id, guild_id, command_id),
487    Some(RatelimitingKind::PathAndId(application_id.into()));
488
489    GuildCommandPermissions { application_id: ApplicationId, guild_id: GuildId, command_id: CommandId },
490    api!("/applications/{}/guilds/{}/commands/{}/permissions", application_id, guild_id, command_id),
491    Some(RatelimitingKind::PathAndId(application_id.into()));
492
493    GuildCommands { application_id: ApplicationId, guild_id: GuildId },
494    api!("/applications/{}/guilds/{}/commands", application_id, guild_id),
495    Some(RatelimitingKind::PathAndId(application_id.into()));
496
497    GuildCommandsPermissions { application_id: ApplicationId, guild_id: GuildId },
498    api!("/applications/{}/guilds/{}/commands/permissions", application_id, guild_id),
499    Some(RatelimitingKind::PathAndId(application_id.into()));
500
501    Skus { application_id: ApplicationId },
502    api!("/applications/{}/skus", application_id),
503    Some(RatelimitingKind::PathAndId(application_id.into()));
504
505    Emoji { application_id: ApplicationId, emoji_id: EmojiId },
506    api!("/applications/{}/emojis/{}", application_id, emoji_id),
507    Some(RatelimitingKind::PathAndId(application_id.into()));
508
509    Emojis { application_id: ApplicationId },
510    api!("/applications/{}/emojis", application_id),
511    Some(RatelimitingKind::PathAndId(application_id.into()));
512
513    Entitlement { application_id: ApplicationId, entitlement_id: EntitlementId },
514    api!("/applications/{}/entitlements/{}", application_id, entitlement_id),
515    Some(RatelimitingKind::PathAndId(application_id.into()));
516
517    ConsumeEntitlement { application_id: ApplicationId, entitlement_id: EntitlementId },
518    api!("/applications/{}/entitlements/{}/consume", application_id, entitlement_id),
519    Some(RatelimitingKind::PathAndId(application_id.into()));
520
521    Entitlements { application_id: ApplicationId },
522    api!("/applications/{}/entitlements", application_id),
523    Some(RatelimitingKind::PathAndId(application_id.into()));
524
525    StageInstances,
526    api!("/stage-instances"),
527    Some(RatelimitingKind::Path);
528
529    StageInstance { channel_id: ChannelId },
530    api!("/stage-instances/{}", channel_id),
531    Some(RatelimitingKind::Path);
532});