diff --git a/src/http/mod.rs b/src/http/mod.rs index ff01cb4f3a0..b3af412bfcb 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -24,6 +24,7 @@ //! [model]: ../model/index.html pub mod ratelimiting; +pub mod routing; mod error; @@ -39,7 +40,7 @@ use hyper::{ Request as HyperRequest, Response as HyperResponse }, - header::{ContentType, Headers}, + header::{Authorization, ContentType, Headers, UserAgent}, method::Method, mime::{Mime, SubLevel, TopLevel}, net::HttpsConnector, @@ -53,13 +54,12 @@ use internal::prelude::*; use model::prelude::*; use multipart::client::Multipart; use parking_lot::Mutex; -use self::ratelimiting::Route; +use self::routing::RouteInfo; use serde::de::DeserializeOwned; use serde_json; use std::{ collections::BTreeMap, default::Default, - fmt::Write as FmtWrite, fs::File, io::ErrorKind as IoErrorKind, path::{Path, PathBuf}, @@ -75,12 +75,42 @@ lazy_static! { }; } -struct Request { - body: Option>, +#[derive(Clone, Debug)] +struct Request<'a> { + body: Option<&'a [u8]>, headers: Option, - method: Method, - route: Route, - url: String, + route: RouteInfo<'a>, +} + +impl<'a> Request<'a> { + fn build(&'a self) -> RequestBuilder<'a> { + let Request { + body, + headers: request_headers, + route: route_info, + } = self; + let (method, _, path) = route_info.deconstruct(); + + let mut builder = CLIENT.request( + method.hyper_method(), + &path.into_owned(), + ); + + if let Some(bytes) = body { + builder = builder.body(HyperBody::BufBody(bytes, bytes.len())); + } + + let mut headers = Headers::new(); + headers.set(UserAgent(constants::USER_AGENT.to_string())); + headers.set(Authorization(TOKEN.lock().clone())); + headers.set(ContentType::json()); + + if let Some(request_headers) = request_headers.clone() { + headers.extend(request_headers.iter()); + } + + builder.headers(headers) + } } /// An method used for ratelimiting special routes. @@ -88,8 +118,6 @@ struct Request { /// This is needed because `hyper`'s `Method` enum does not derive Copy. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum LightMethod { - /// Indicates that a route is for "any" method. - Any, /// Indicates that a route is for the `DELETE` method only. Delete, /// Indicates that a route is for the `GET` method only. @@ -102,6 +130,18 @@ pub enum LightMethod { Put, } +impl LightMethod { + pub fn hyper_method(&self) -> Method { + match *self { + LightMethod::Delete => Method::Delete, + LightMethod::Get => Method::Get, + LightMethod::Patch => Method::Patch, + LightMethod::Post => Method::Post, + LightMethod::Put => Method::Put, + } + } +} + lazy_static! { static ref TOKEN: Arc> = Arc::new(Mutex::new(String::default())); } @@ -143,9 +183,7 @@ pub fn add_group_recipient(group_id: u64, user_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Put, - route: Route::None, - url: api!("/channels/{}/recipients/{}", group_id, user_id), + route: RouteInfo::AddGroupRecipient { group_id, user_id }, }) } @@ -162,9 +200,7 @@ pub fn add_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> wind(204, Request { body: None, headers: None, - method: Method::Put, - route: Route::GuildsIdMembersIdRolesId(guild_id), - url: api!("/guilds/{}/members/{}/roles/{}", guild_id, user_id, role_id), + route: RouteInfo::AddMemberRole { guild_id, role_id, user_id }, }) } @@ -183,15 +219,12 @@ pub fn ban_user(guild_id: u64, user_id: u64, delete_message_days: u8, reason: &s wind(204, Request { body: None, headers: None, - method: Method::Put, - route: Route::GuildsIdBansUserId(guild_id), - url: api!( - "/guilds/{}/bans/{}?delete_message_days={}&reason={}", + route: RouteInfo::GuildBanUser { + delete_message_days: Some(delete_message_days), + reason: Some(reason), guild_id, user_id, - delete_message_days, - reason, - ), + }, }) } @@ -251,9 +284,7 @@ pub fn broadcast_typing(channel_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Post, - route: Route::ChannelsIdTyping(channel_id), - url: api!("/channels/{}/typing", channel_id), + route: RouteInfo::BroadcastTyping { channel_id }, }) } @@ -269,11 +300,9 @@ pub fn broadcast_typing(channel_id: u64) -> Result<()> { /// [Manage Channels]: ../model/permissions/constant.MANAGE_CHANNELS.html pub fn create_channel(guild_id: u64, map: &Value) -> Result { fire(Request { - body: Some(map.to_string().into_bytes()), + body: Some(map.to_string().as_bytes()), headers: None, - method: Method::Post, - route: Route::GuildsIdChannels(guild_id), - url: api!("/guilds/{}/channels", guild_id), + route: RouteInfo::CreateChannel { guild_id }, }) } @@ -289,11 +318,9 @@ pub fn create_channel(guild_id: u64, map: &Value) -> Result { /// [Manage Emojis]: ../model/permissions/constant.MANAGE_EMOJIS.html pub fn create_emoji(guild_id: u64, map: &Value) -> Result { fire(Request { - body: Some(map.to_string().into_bytes()), + body: Some(map.to_string().as_bytes()), headers: None, - method: Method::Post, - route: Route::GuildsIdEmojis(guild_id), - url: api!("/guilds/{}/emojis", guild_id), + route: RouteInfo::CreateEmoji { guild_id }, }) } @@ -335,11 +362,9 @@ pub fn create_emoji(guild_id: u64, map: &Value) -> Result { /// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild pub fn create_guild(map: &Value) -> Result { fire(Request { - body: Some(map.to_string().into_bytes()), + body: Some(map.to_string().as_bytes()), headers: None, - method: Method::Post, - route: Route::Guilds, - url: api!("/guilds").to_owned(), + route: RouteInfo::CreateGuild, }) } @@ -355,11 +380,9 @@ pub fn create_guild(map: &Value) -> Result { /// [docs]: https://discordapp.com/developers/docs/resources/guild#create-guild-integration pub fn create_guild_integration(guild_id: u64, integration_id: u64, map: &Value) -> Result<()> { wind(204, Request { - body: Some(map.to_string().into_bytes()), + body: Some(map.to_string().as_bytes()), headers: None, - method: Method::Post, - route: Route::GuildsIdIntegrations(guild_id), - url: api!("/guilds/{}/integrations/{}", guild_id, integration_id), + route: RouteInfo::CreateGuildIntegration { guild_id, integration_id }, }) } @@ -379,33 +402,31 @@ pub fn create_invite(channel_id: u64, map: &JsonMap) -> Result { let body = serde_json::to_vec(map)?; fire(Request { - body: Some(body), + body: Some(&body), headers: None, - method: Method::Post, - route: Route::ChannelsIdInvites(channel_id), - url: api!("/channels/{}/invites", channel_id), + route: RouteInfo::CreateInvite { channel_id }, }) } /// Creates a permission override for a member or a role in a channel. pub fn create_permission(channel_id: u64, target_id: u64, map: &Value) -> Result<()> { + let body = serde_json::to_vec(map)?; + wind(204, Request { - body: Some(map.to_string().into_bytes()), + body: Some(&body), headers: None, - method: Method::Put, - route: Route::ChannelsIdPermissionsOverwriteId(channel_id), - url: api!("/channels/{}/permissions/{}", channel_id, target_id), + route: RouteInfo::CreatePermission { channel_id, target_id }, }) } /// Creates a private channel with a user. pub fn create_private_channel(map: &Value) -> Result { + let body = serde_json::to_vec(map)?; + fire(Request { - body: Some(map.to_string().into_bytes()), + body: Some(&body), headers: None, - method: Method::Post, - route: Route::UsersMeChannels, - url: api!("/users/@me/channels").to_owned(), + route: RouteInfo::GetUserDmChannels, }) } @@ -417,14 +438,11 @@ pub fn create_reaction(channel_id: u64, wind(204, Request { body: None, headers: None, - method: Method::Put, - route: Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), - url: api!( - "/channels/{}/messages/{}/reactions/{}/@me", + route: RouteInfo::CreateReaction { + reaction: &reaction_type.as_data(), channel_id, message_id, - reaction_type.as_data(), - ), + }, }) } @@ -433,11 +451,9 @@ pub fn create_role(guild_id: u64, map: &JsonMap) -> Result { let body = serde_json::to_vec(map)?; fire(Request { - body: Some(body), + body: Some(&body), headers: None, - method: Method::Post, - route: Route::GuildsIdRoles(guild_id), - url: api!("/guilds/{}/roles", guild_id), + route: RouteInfo::CreateRole {guild_id }, }) } @@ -472,12 +488,12 @@ pub fn create_role(guild_id: u64, map: &JsonMap) -> Result { /// /// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html pub fn create_webhook(channel_id: u64, map: &Value) -> Result { + let body = serde_json::to_vec(map)?; + fire(Request { - body: Some(map.to_string().into_bytes()), + body: Some(&body), headers: None, - method: Method::Post, - route: Route::ChannelsIdWebhooks(channel_id), - url: api!("/channels/{}/webhooks", channel_id), + route: RouteInfo::CreateWebhook { channel_id }, }) } @@ -486,9 +502,7 @@ pub fn delete_channel(channel_id: u64) -> Result { fire(Request { body: None, headers: None, - route: Route::ChannelsId(channel_id), - method: Method::Delete, - url: api!("/channels/{}", channel_id), + route: RouteInfo::DeleteChannel { channel_id }, }) } @@ -497,9 +511,7 @@ pub fn delete_emoji(guild_id: u64, emoji_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::GuildsIdEmojisId(guild_id), - url: api!("/guilds/{}/emojis/{}", guild_id, emoji_id), + route: RouteInfo::DeleteEmoji { guild_id, emoji_id }, }) } @@ -508,9 +520,7 @@ pub fn delete_guild(guild_id: u64) -> Result { fire(Request { body: None, headers: None, - method: Method::Delete, - route: Route::GuildsId(guild_id), - url: api!("/guilds/{}", guild_id), + route: RouteInfo::DeleteGuild { guild_id }, }) } @@ -519,9 +529,7 @@ pub fn delete_guild_integration(guild_id: u64, integration_id: u64) -> Result<() wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::GuildsIdIntegrationsId(guild_id), - url: api!("/guilds/{}/integrations/{}", guild_id, integration_id), + route: RouteInfo::DeleteGuildIntegration { guild_id, integration_id }, }) } @@ -530,9 +538,7 @@ pub fn delete_invite(code: &str) -> Result { fire(Request { body: None, headers: None, - method: Method::Delete, - route: Route::InvitesCode, - url: api!("/invites/{}", code), + route: RouteInfo::DeleteInvite { code }, }) } @@ -542,20 +548,16 @@ pub fn delete_message(channel_id: u64, message_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::ChannelsIdMessagesId(LightMethod::Delete, channel_id), - url: api!("/channels/{}/messages/{}", channel_id, message_id), + route: RouteInfo::DeleteMessage { channel_id, message_id }, }) } /// Deletes a bunch of messages, only works for bots. pub fn delete_messages(channel_id: u64, map: &Value) -> Result<()> { wind(204, Request { - body: Some(map.to_string().into_bytes()), + body: Some(map.to_string().as_bytes()), headers: None, - method: Method::Delete, - route: Route::ChannelsIdMessagesBulkDelete(channel_id), - url: api!("/channels/{}/messages/bulk-delete", channel_id), + route: RouteInfo::DeleteMessages { channel_id }, }) } @@ -580,9 +582,7 @@ pub fn delete_message_reactions(channel_id: u64, message_id: u64) -> Result<()> wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::ChannelsIdMessagesIdReactions(channel_id), - url: api!("/channels/{}/messages/{}/reactions", channel_id, message_id), + route: RouteInfo::DeleteMessageReactions { channel_id, message_id }, }) } @@ -591,9 +591,7 @@ pub fn delete_permission(channel_id: u64, target_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::ChannelsIdPermissionsOverwriteId(channel_id), - url: api!("/channels/{}/permissions/{}", channel_id, target_id), + route: RouteInfo::DeletePermission { channel_id, target_id }, }) } @@ -611,15 +609,12 @@ pub fn delete_reaction(channel_id: u64, wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), - url: api!( - "/channels/{}/messages/{}/reactions/{}/{}", + route: RouteInfo::DeleteReaction { + reaction: &reaction_type.as_data(), + user: &user, channel_id, message_id, - reaction_type.as_data(), - user, - ), + }, }) } @@ -628,9 +623,7 @@ pub fn delete_role(guild_id: u64, role_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::GuildsIdRolesId(guild_id), - url: api!("/guilds/{}/roles/{}", guild_id, role_id), + route: RouteInfo::DeleteRole { guild_id, role_id }, }) } @@ -660,9 +653,7 @@ pub fn delete_webhook(webhook_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::WebhooksId(webhook_id), - url: api!("/webhooks/{}", webhook_id), + route: RouteInfo::DeleteWebhook { webhook_id }, }) } @@ -688,9 +679,7 @@ pub fn delete_webhook_with_token(webhook_id: u64, token: &str) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::None, - url: api!("/webhooks/{}/{}", webhook_id, token), + route: RouteInfo::DeleteWebhookWithToken { token, webhook_id }, }) } @@ -699,22 +688,20 @@ pub fn edit_channel(channel_id: u64, map: &JsonMap) -> Result { let body = serde_json::to_vec(map)?; fire(Request { - body: Some(body), + body: Some(&body), headers: None, - method: Method::Patch, - route: Route::ChannelsId(channel_id), - url: api!("/channels/{}", channel_id), + route: RouteInfo::EditChannel {channel_id }, }) } /// Changes emoji information. pub fn edit_emoji(guild_id: u64, emoji_id: u64, map: &Value) -> Result { + let body = serde_json::to_vec(map)?; + fire(Request { - body: Some(map.to_string().into_bytes()), + body: Some(&body), headers: None, - method: Method::Patch, - route: Route::GuildsIdEmojisId(guild_id), - url: api!("/guilds/{}/emojis/{}", guild_id, emoji_id), + route: RouteInfo::EditEmoji { guild_id, emoji_id }, }) } @@ -723,23 +710,21 @@ pub fn edit_guild(guild_id: u64, map: &JsonMap) -> Result { let body = serde_json::to_vec(map)?; fire(Request { - body: Some(body), + body: Some(&body), headers: None, - method: Method::Patch, - route: Route::GuildsId(guild_id), - url: api!("/guilds/{}", guild_id), + route: RouteInfo::EditGuild { guild_id }, }) } /// Edits the positions of a guild's channels. pub fn edit_guild_channel_positions(guild_id: u64, value: &Value) -> Result<()> { + let body = serde_json::to_vec(value)?; + wind(204, Request { - body: Some(value.to_string().into_bytes()), + body: Some(&body), headers: None, - method: Method::Patch, - route: Route::GuildsIdChannels(guild_id), - url: api!("/guilds/{}/channels", guild_id), + route: RouteInfo::EditGuildChannels { guild_id }, }) } @@ -747,12 +732,12 @@ pub fn edit_guild_channel_positions(guild_id: u64, value: &Value) /// /// [`Guild`]: ../model/guild/struct.Guild.html pub fn edit_guild_embed(guild_id: u64, map: &Value) -> Result { + let body = serde_json::to_vec(map)?; + fire(Request { - body: Some(map.to_string().into_bytes()), + body: Some(&body), headers: None, - method: Method::Patch, - route: Route::GuildsIdEmbed(guild_id), - url: api!("/guilds/{}/embed", guild_id), + route: RouteInfo::EditGuildEmbed { guild_id }, }) } @@ -761,11 +746,9 @@ pub fn edit_member(guild_id: u64, user_id: u64, map: &JsonMap) -> Result<()> { let body = serde_json::to_vec(map)?; wind(204, Request { - body: Some(body), + body: Some(&body), headers: None, - method: Method::Patch, - route: Route::GuildsIdMembersId(guild_id), - url: api!("/guilds/{}/members/{}", guild_id, user_id), + route: RouteInfo::EditMember { guild_id, user_id }, }) } @@ -773,12 +756,12 @@ pub fn edit_member(guild_id: u64, user_id: u64, map: &JsonMap) -> Result<()> { /// /// **Note**: Only the author of a message can modify it. pub fn edit_message(channel_id: u64, message_id: u64, map: &Value) -> Result { + let body = serde_json::to_vec(map)?; + fire(Request { - body: Some(map.to_string().into_bytes()), + body: Some(&body), headers: None, - method: Method::Patch, - route: Route::ChannelsIdMessagesId(LightMethod::Any, channel_id), - url: api!("/channels/{}/messages/{}", channel_id, message_id), + route: RouteInfo::EditMessage { channel_id, message_id }, }) } @@ -789,13 +772,12 @@ pub fn edit_message(channel_id: u64, message_id: u64, map: &Value) -> Result) -> Result<()> { let map = json!({ "nick": new_nickname }); + let body = serde_json::to_vec(&map)?; wind(200, Request { - body: Some(map.to_string().into_bytes()), + body: Some(&body), headers: None, - method: Method::Patch, - route: Route::GuildsIdMembersMeNick(guild_id), - url: api!("/guilds/{}/members/@me/nick", guild_id), + route: RouteInfo::EditNickname { guild_id }, }) } @@ -815,11 +797,9 @@ pub fn edit_profile(map: &JsonMap) -> Result { let body = serde_json::to_vec(map)?; let response = request(Request { - body: Some(body), + body: Some(&body), headers: None, - method: Method::Patch, - route: Route::UsersMe, - url: api!("/users/@me").to_owned(), + route: RouteInfo::EditProfile, })?; let mut value = serde_json::from_reader::(response)?; @@ -840,27 +820,23 @@ pub fn edit_role(guild_id: u64, role_id: u64, map: &JsonMap) -> Result { let body = serde_json::to_vec(&map)?; fire(Request { - body: Some(body), + body: Some(&body), headers: None, - method: Method::Patch, - route: Route::GuildsIdRolesId(guild_id), - url: api!("/guilds/{}/roles/{}", guild_id, role_id), + route: RouteInfo::EditRole { guild_id, role_id }, }) } /// Changes the position of a role in a guild. pub fn edit_role_position(guild_id: u64, role_id: u64, position: u64) -> Result> { - let body = serde_json::to_string(&json!({ + let body = serde_json::to_vec(&json!({ "id": role_id, "position": position, }))?; fire(Request { - body: Some(body.into_bytes()), + body: Some(&body), headers: None, - method: Method::Patch, - route: Route::GuildsIdRolesId(guild_id), - url: api!("/guilds/{}/roles/{}", guild_id, role_id), + route: RouteInfo::EditRole { guild_id, role_id }, }) } @@ -905,11 +881,9 @@ pub fn edit_role_position(guild_id: u64, role_id: u64, position: u64) -> Result< // external crates being incredibly messy and misleading in the end user's view. pub fn edit_webhook(webhook_id: u64, map: &Value) -> Result { fire(Request { - body: Some(map.to_string().into_bytes()), + body: Some(map.to_string().as_bytes()), headers: None, - method: Method::Patch, - route: Route::WebhooksId(webhook_id), - url: api!("/webhooks/{}", webhook_id), + route: RouteInfo::EditWebhook { webhook_id }, }) } @@ -943,11 +917,9 @@ pub fn edit_webhook_with_token(webhook_id: u64, token: &str, map: &JsonMap) -> R let body = serde_json::to_vec(map)?; fire(Request { - body: Some(body), + body: Some(&body), headers: None, - method: Method::Patch, - route: Route::None, - url: api!("/webhooks/{}/{}", webhook_id, token), + route: RouteInfo::EditWebhookWithToken { token, webhook_id }, }) } @@ -1024,11 +996,9 @@ pub fn execute_webhook(webhook_id: u64, )); let response = request(Request { - body: Some(body), + body: Some(&body), headers: Some(headers), - method: Method::Get, - route: Route::None, - url: api!("/webhooks/{}/{}?wait={}", webhook_id, token, wait), + route: RouteInfo::ExecuteWebhook { token, wait, webhook_id }, })?; if response.status == StatusCode::NoContent { @@ -1047,9 +1017,7 @@ pub fn get_active_maintenances() -> Result> { let response = request(Request { body: None, headers: None, - method: Method::Get, - route: Route::None, - url: status!("/scheduled-maintenances/active.json").to_owned(), + route: RouteInfo::GetActiveMaintenance, })?; let mut map: BTreeMap = serde_json::from_reader(response)?; @@ -1066,9 +1034,7 @@ pub fn get_bans(guild_id: u64) -> Result> { fire(Request { body: None, headers: None, - route: Route::GuildsIdBans(guild_id), - method: Method::Get, - url: api!("/guilds/{}/bans", guild_id), + route: RouteInfo::GetBans { guild_id }, }) } @@ -1078,32 +1044,16 @@ pub fn get_audit_logs(guild_id: u64, user_id: Option, before: Option, limit: Option) -> Result { - let mut params = Vec::with_capacity(4); - - if let Some(action_type) = action_type { - params.push(format!("action_type={}", action_type)); - } - if let Some(user_id) = user_id { - params.push(format!("user_id={}", user_id)); - } - if let Some(before) = before { - params.push(format!("before={}", before)); - } - if let Some(limit) = limit { - params.push(format!("limit={}", limit)); - } - - let mut query_string = params.join("&"); - if !query_string.is_empty() { - query_string.insert(0, '?'); - } - fire(Request { body: None, headers: None, - route: Route::GuildsIdAuditLogs(guild_id), - method: Method::Get, - url: api!("/guilds/{}/audit-logs{}", guild_id, query_string), + route: RouteInfo::GetAuditLogs { + action_type, + before, + guild_id, + limit, + user_id, + }, }) } @@ -1112,9 +1062,7 @@ pub fn get_bot_gateway() -> Result { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::GatewayBot, - url: api!("/gateway/bot").to_owned(), + route: RouteInfo::GetBotGateway, }) } @@ -1123,9 +1071,7 @@ pub fn get_channel_invites(channel_id: u64) -> Result> { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::ChannelsIdInvites(channel_id), - url: api!("/channels/{}/invites", channel_id), + route: RouteInfo::GetChannelInvites { channel_id }, }) } @@ -1151,9 +1097,7 @@ pub fn get_channel_webhooks(channel_id: u64) -> Result> { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::ChannelsIdWebhooks(channel_id), - url: api!("/channels/{}/webhooks", channel_id), + route: RouteInfo::GetChannelWebhooks { channel_id }, }) } @@ -1162,9 +1106,7 @@ pub fn get_channel(channel_id: u64) -> Result { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::ChannelsId(channel_id), - url: api!("/channels/{}", channel_id), + route: RouteInfo::GetChannel { channel_id }, }) } @@ -1173,9 +1115,7 @@ pub fn get_channels(guild_id: u64) -> Result> { fire(Request { body: None, headers: None, - route: Route::ChannelsId(guild_id), - method: Method::Get, - url: api!("/guilds/{}/channels", guild_id), + route: RouteInfo::GetChannels { guild_id }, }) } @@ -1186,9 +1126,7 @@ pub fn get_current_application_info() -> Result { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::None, - url: api!("/oauth2/applications/@me").to_owned(), + route: RouteInfo::GetCurrentApplicationInfo, }) } @@ -1197,9 +1135,7 @@ pub fn get_current_user() -> Result { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::UsersMe, - url: api!("/users/@me").to_owned(), + route: RouteInfo::GetCurrentUser, }) } @@ -1208,9 +1144,7 @@ pub fn get_gateway() -> Result { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::Gateway, - url: api!("/gateway").to_owned(), + route: RouteInfo::GetGateway, }) } @@ -1219,9 +1153,7 @@ pub fn get_guild(guild_id: u64) -> Result { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::GuildsId(guild_id), - url: api!("/guilds/{}", guild_id), + route: RouteInfo::GetGuild { guild_id }, }) } @@ -1230,9 +1162,7 @@ pub fn get_guild_embed(guild_id: u64) -> Result { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::GuildsIdEmbed(guild_id), - url: api!("/guilds/{}/embeds", guild_id), + route: RouteInfo::GetGuildEmbed { guild_id }, }) } @@ -1241,9 +1171,7 @@ pub fn get_guild_integrations(guild_id: u64) -> Result> { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::GuildsIdIntegrations(guild_id), - url: api!("/guilds/{}/integrations", guild_id), + route: RouteInfo::GetGuildIntegrations { guild_id }, }) } @@ -1252,9 +1180,7 @@ pub fn get_guild_invites(guild_id: u64) -> Result> { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::GuildsIdInvites(guild_id), - url: api!("/guilds/{}/invites", guild_id), + route: RouteInfo::GetGuildInvites { guild_id }, }) } @@ -1268,9 +1194,7 @@ pub fn get_guild_vanity_url(guild_id: u64) -> Result { let response = request(Request { body: None, headers: None, - method: Method::Get, - route: Route::GuildsIdVanityUrl(guild_id), - url: api!("/guilds/{}/vanity-url", guild_id), + route: RouteInfo::GetGuildVanityUrl { guild_id }, })?; serde_json::from_reader::(response) @@ -1287,14 +1211,7 @@ pub fn get_guild_members(guild_id: u64, let response = request(Request { body: None, headers: None, - method: Method::Get, - route: Route::GuildsIdMembers(guild_id), - url: api!( - "/guilds/{}/members?limit={}&after={}", - guild_id, - limit.unwrap_or(500), - after.unwrap_or(0), - ), + route: RouteInfo::GetGuildMembers { after, guild_id, limit }, })?; let mut v = serde_json::from_reader::(response)?; @@ -1314,12 +1231,21 @@ pub fn get_guild_members(guild_id: u64, /// Gets the amount of users that can be pruned. pub fn get_guild_prune_count(guild_id: u64, map: &Value) -> Result { + // Note for 0.6.x: turn this into a function parameter. + #[derive(Deserialize)] + struct GetGuildPruneCountRequest { + days: u64, + } + + let req = serde_json::from_value::(map.clone())?; + fire(Request { - body: Some(map.to_string().into_bytes()), + body: None, headers: None, - method: Method::Get, - route: Route::GuildsIdPrune(guild_id), - url: api!("/guilds/{}/prune", guild_id), + route: RouteInfo::GetGuildPruneCount { + days: req.days, + guild_id, + }, }) } @@ -1329,9 +1255,7 @@ pub fn get_guild_regions(guild_id: u64) -> Result> { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::GuildsIdRegions(guild_id), - url: api!("/guilds/{}/regions", guild_id), + route: RouteInfo::GetGuildRegions { guild_id }, }) } @@ -1342,9 +1266,7 @@ pub fn get_guild_roles(guild_id: u64) -> Result> { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::GuildsIdRoles(guild_id), - url: api!("/guilds/{}/roles", guild_id), + route: RouteInfo::GetGuildRoles { guild_id }, }) } @@ -1370,9 +1292,7 @@ pub fn get_guild_webhooks(guild_id: u64) -> Result> { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::GuildsIdWebhooks(guild_id), - url: api!("/guilds/{}/webhooks", guild_id), + route: RouteInfo::GetGuildWebhooks { guild_id }, }) } @@ -1397,48 +1317,30 @@ pub fn get_guild_webhooks(guild_id: u64) -> Result> { /// /// [docs]: https://discordapp.com/developers/docs/resources/user#get-current-user-guilds pub fn get_guilds(target: &GuildPagination, limit: u64) -> Result> { - let mut uri = format!("/users/@me/guilds?limit={}", limit); - - match *target { - GuildPagination::After(id) => { - write!(uri, "&after={}", id)?; - }, - GuildPagination::Before(id) => { - write!(uri, "&before={}", id)?; - }, - } + let (after, before) = match *target { + GuildPagination::After(id) => (Some(id.0), None), + GuildPagination::Before(id) => (None, Some(id.0)), + }; fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::UsersMeGuilds, - url: api!("{}", uri), + route: RouteInfo::GetGuilds { after, before, limit }, }) } /// Gets information about a specific invite. #[allow(unused_mut)] -pub fn get_invite(code: &str, stats: bool) -> Result { - let mut invite = code; - +pub fn get_invite(mut code: &str, stats: bool) -> Result { #[cfg(feature = "utils")] { - invite = ::utils::parse_invite(invite); - } - - let mut uri = format!("/invites/{}", invite); - - if stats { - uri.push_str("?with_counts=true"); + code = ::utils::parse_invite(code); } fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::InvitesCode, - url: api!("{}", uri), + route: RouteInfo::GetInvite { code, stats }, }) } @@ -1447,9 +1349,7 @@ pub fn get_member(guild_id: u64, user_id: u64) -> Result { let response = request(Request { body: None, headers: None, - method: Method::Get, - route: Route::GuildsIdMembersId(guild_id), - url: api!("/guilds/{}/members/{}", guild_id, user_id), + route: RouteInfo::GetMember { guild_id, user_id }, })?; let mut v = serde_json::from_reader::(response)?; @@ -1466,9 +1366,7 @@ pub fn get_message(channel_id: u64, message_id: u64) -> Result { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::ChannelsIdMessagesId(LightMethod::Any, channel_id), - url: api!("/channels/{}/messages/{}", channel_id, message_id), + route: RouteInfo::GetMessage { channel_id, message_id }, }) } @@ -1477,9 +1375,10 @@ pub fn get_messages(channel_id: u64, query: &str) -> Result> { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::ChannelsIdMessages(channel_id), - url: api!("/channels/{}/messages{}", channel_id, query), + route: RouteInfo::GetMessages { + query: query.to_owned(), + channel_id, + }, }) } @@ -1488,9 +1387,7 @@ pub fn get_pins(channel_id: u64) -> Result> { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::ChannelsIdPins(channel_id), - url: api!("/channels/{}/pins", channel_id), + route: RouteInfo::GetPins { channel_id }, }) } @@ -1501,24 +1398,18 @@ pub fn get_reaction_users(channel_id: u64, limit: u8, after: Option) -> Result> { - let mut uri = format!( - "/channels/{}/messages/{}/reactions/{}?limit={}", - channel_id, - message_id, - reaction_type.as_data(), - limit - ); - - if let Some(user_id) = after { - write!(uri, "&after={}", user_id)?; - } + let reaction = reaction_type.as_data(); fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), - url: api!("{}", uri), + route: RouteInfo::GetReactionUsers { + after, + channel_id, + limit, + message_id, + reaction, + }, }) } @@ -1529,9 +1420,7 @@ pub fn get_unresolved_incidents() -> Result> { let response = request(Request { body: None, headers: None, - method: Method::Get, - route: Route::None, - url: status!("/incidents/unresolved.json").to_owned(), + route: RouteInfo::GetUnresolvedIncidents, })?; let mut map: BTreeMap = serde_json::from_reader(response)?; @@ -1550,9 +1439,7 @@ pub fn get_upcoming_maintenances() -> Result> { let response = request(Request { body: None, headers: None, - method: Method::Get, - route: Route::None, - url: status!("/scheduled-maintenances/upcoming.json").to_owned(), + route: RouteInfo::GetUpcomingMaintenances, })?; let mut map: BTreeMap = serde_json::from_reader(response)?; @@ -1569,9 +1456,7 @@ pub fn get_user(user_id: u64) -> Result { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::UsersId, - url: api!("/users/{}", user_id), + route: RouteInfo::GetUser { user_id }, }) } @@ -1580,9 +1465,7 @@ pub fn get_user_dm_channels() -> Result> { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::UsersMeChannels, - url: api!("/users/@me/channels").to_owned(), + route: RouteInfo::GetUserDmChannels, }) } @@ -1591,9 +1474,7 @@ pub fn get_voice_regions() -> Result> { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::VoiceRegions, - url: api!("/voice/regions").to_owned(), + route: RouteInfo::GetVoiceRegions, }) } @@ -1618,9 +1499,7 @@ pub fn get_webhook(webhook_id: u64) -> Result { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::WebhooksId(webhook_id), - url: api!("/webhooks/{}", webhook_id), + route: RouteInfo::GetWebhook { webhook_id }, }) } @@ -1645,9 +1524,7 @@ pub fn get_webhook_with_token(webhook_id: u64, token: &str) -> Result { fire(Request { body: None, headers: None, - method: Method::Get, - route: Route::None, - url: api!("/webhooks/{}/{}", webhook_id, token), + route: RouteInfo::GetWebhookWithToken { token, webhook_id }, }) } @@ -1656,20 +1533,16 @@ pub fn kick_member(guild_id: u64, user_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::GuildsIdMembersId(guild_id), - url: api!("/guilds/{}/members/{}", guild_id, user_id), + route: RouteInfo::KickMember { guild_id, user_id }, }) } /// Leaves a group DM. -pub fn leave_group(guild_id: u64) -> Result { +pub fn leave_group(group_id: u64) -> Result { fire(Request { body: None, headers: None, - method: Method::Delete, - route: Route::None, - url: api!("/channels/{}", guild_id), + route: RouteInfo::LeaveGroup { group_id }, }) } @@ -1678,9 +1551,7 @@ pub fn leave_guild(guild_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::UsersMeGuildsId, - url: api!("/users/@me/guilds/{}", guild_id), + route: RouteInfo::LeaveGuild { guild_id }, }) } @@ -1689,9 +1560,7 @@ pub fn remove_group_recipient(group_id: u64, user_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::None, - url: api!("/channels/{}/recipients/{}", group_id, user_id), + route: RouteInfo::RemoveGroupRecipient { group_id, user_id }, }) } @@ -1768,12 +1637,12 @@ pub fn send_files<'a, T, It: IntoIterator>(channel_id: u64, files: It, m /// Sends a message to a channel. pub fn send_message(channel_id: u64, map: &Value) -> Result { + let body = serde_json::to_vec(map)?; + fire(Request { - body: Some(map.to_string().into_bytes()), + body: Some(&body), headers: None, - method: Method::Post, - route: Route::ChannelsIdMessages(channel_id), - url: api!("/channels/{}/messages", channel_id), + route: RouteInfo::CreateMessage { channel_id }, }) } @@ -1782,9 +1651,7 @@ pub fn pin_message(channel_id: u64, message_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Put, - route: Route::ChannelsIdPinsMessageId(channel_id), - url: api!("/channels/{}/pins/{}", channel_id, message_id), + route: RouteInfo::PinMessage { channel_id, message_id }, }) } @@ -1793,9 +1660,7 @@ pub fn remove_ban(guild_id: u64, user_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::GuildsIdBansUserId(guild_id), - url: api!("/guilds/{}/bans/{}", guild_id, user_id), + route: RouteInfo::RemoveBan { guild_id, user_id }, }) } @@ -1812,20 +1677,27 @@ pub fn remove_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<( wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::GuildsIdMembersIdRolesId(guild_id), - url: api!("/guilds/{}/members/{}/roles/{}", guild_id, user_id, role_id), + route: RouteInfo::RemoveMemberRole { guild_id, user_id, role_id }, }) } /// Starts removing some members from a guild based on the last time they've been online. pub fn start_guild_prune(guild_id: u64, map: &Value) -> Result { + // Note for 0.6.x: turn this into a function parameter. + #[derive(Deserialize)] + struct StartGuildPruneRequest { + days: u64, + } + + let req = serde_json::from_value::(map.clone())?; + fire(Request { - body: Some(map.to_string().into_bytes()), + body: None, headers: None, - method: Method::Post, - route: Route::GuildsIdPrune(guild_id), - url: api!("/guilds/{}/prune", guild_id), + route: RouteInfo::StartGuildPrune { + days: req.days, + guild_id, + }, }) } @@ -1834,9 +1706,7 @@ pub fn start_integration_sync(guild_id: u64, integration_id: u64) -> Result<()> wind(204, Request { body: None, headers: None, - method: Method::Post, - route: Route::GuildsIdIntegrationsIdSync(guild_id), - url: api!("/guilds/{}/integrations/{}/sync", guild_id, integration_id), + route: RouteInfo::StartIntegrationSync { guild_id, integration_id }, }) } @@ -1845,9 +1715,7 @@ pub fn unpin_message(channel_id: u64, message_id: u64) -> Result<()> { wind(204, Request { body: None, headers: None, - method: Method::Delete, - route: Route::ChannelsIdPinsMessageId(channel_id), - url: api!("/channels/{}/pins/{}", channel_id, message_id), + route: RouteInfo::UnpinMessage { channel_id, message_id }, }) } @@ -1873,33 +1741,19 @@ fn request(req: Request) -> Result { } } -fn build_req(req: &Request) -> RequestBuilder { - let mut builder = CLIENT.request(req.method.clone(), &req.url); - - if let Some(ref bytes) = req.body { - builder = builder.body(HyperBody::BufBody(bytes, bytes.len())); - } - - if let Some(headers) = req.headers.clone() { - builder = builder.headers(headers); - } - - builder -} - -fn retry(req: &Request) -> HyperResult { +fn retry(request: &Request) -> HyperResult { // Retry the request twice in a loop until it succeeds. // // If it doesn't and the loop breaks, try one last time. for _ in 0..=2 { - match build_req(req).send() { + match request.build().send() { Err(HyperError::Io(ref io)) if io.kind() == IoErrorKind::ConnectionAborted => continue, other => return other, } } - build_req(req).send() + request.build().send() } /// Performs a request and then verifies that the response status code is equal @@ -1908,16 +1762,16 @@ fn retry(req: &Request) -> HyperResult { /// This is a function that performs a light amount of work and returns an /// empty tuple, so it's called "wind" to denote that it's lightweight. fn wind(expected: u16, req: Request) -> Result<()> { - let response = request(req)?; + let resp = request(req)?; - if response.status.to_u16() == expected { + if resp.status.to_u16() == expected { return Ok(()); } - debug!("Expected {}, got {}", expected, response.status); - trace!("Unsuccessful response: {:?}", response); + debug!("Expected {}, got {}", expected, resp.status); + trace!("Unsuccessful response: {:?}", resp); - Err(Error::Http(HttpError::UnsuccessfulRequest(response))) + Err(Error::Http(HttpError::UnsuccessfulRequest(resp))) } /// Enum that allows a user to pass a `Path` or a `File` type to `send_files` diff --git a/src/http/ratelimiting.rs b/src/http/ratelimiting.rs index 1c31c9f8169..309ba350d20 100644 --- a/src/http/ratelimiting.rs +++ b/src/http/ratelimiting.rs @@ -40,6 +40,8 @@ //! [Taken from]: https://discordapp.com/developers/docs/topics/rate-limits#rate-limits #![allow(zero_ptr)] +pub use super::routing::Route; + use chrono::{DateTime, Utc}; use hyper::client::Response; use hyper::header::Headers; @@ -54,7 +56,7 @@ use std::{ thread, i64 }; -use super::{HttpError, LightMethod, Request}; +use super::{HttpError, Request}; /// Refer to [`offset`]. /// @@ -102,271 +104,22 @@ lazy_static! { }; } -/// A representation of all routes registered within the library. These are safe -/// and memory-efficient representations of each path that functions exist for -/// in the [`http`] module. -/// -/// [`http`]: ../index.html -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum Route { - /// Route for the `/channels/:channel_id` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsId(u64), - /// Route for the `/channels/:channel_id/invites` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdInvites(u64), - /// Route for the `/channels/:channel_id/messages` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdMessages(u64), - /// Route for the `/channels/:channel_id/messages/bulk-delete` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdMessagesBulkDelete(u64), - /// Route for the `/channels/:channel_id/messages/:message_id` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - // This route is a unique case. The ratelimit for message _deletions_ is - // different than the overall route ratelimit. - // - // Refer to the docs on [Rate Limits] in the yellow warning section. - // - // Additionally, this needs to be a `LightMethod` from the parent module - // and _not_ a `hyper` `Method` due to `hyper`'s not deriving `Copy`. - // - // [Rate Limits]: https://discordapp.com/developers/docs/topics/rate-limits - ChannelsIdMessagesId(LightMethod, u64), - /// Route for the `/channels/:channel_id/messages/:message_id/ack` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdMessagesIdAck(u64), - /// Route for the `/channels/:channel_id/messages/:message_id/reactions` - /// path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdMessagesIdReactions(u64), - /// Route for the - /// `/channels/:channel_id/messages/:message_id/reactions/:reaction/@me` - /// path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdMessagesIdReactionsUserIdType(u64), - /// Route for the `/channels/:channel_id/permissions/:target_id` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdPermissionsOverwriteId(u64), - /// Route for the `/channels/:channel_id/pins` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdPins(u64), - /// Route for the `/channels/:channel_id/pins/:message_id` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdPinsMessageId(u64), - /// Route for the `/channels/:channel_id/typing` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdTyping(u64), - /// Route for the `/channels/:channel_id/webhooks` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdWebhooks(u64), - /// Route for the `/gateway` path. - Gateway, - /// Route for the `/gateway/bot` path. - GatewayBot, - /// Route for the `/guilds` path. - Guilds, - /// Route for the `/guilds/:guild_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsId(u64), - /// Route for the `/guilds/:guild_id/bans` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdBans(u64), - /// Route for the `/guilds/:guild_id/audit-logs` path. - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdAuditLogs(u64), - /// Route for the `/guilds/:guild_id/bans/:user_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdBansUserId(u64), - /// Route for the `/guilds/:guild_id/channels/:channel_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdChannels(u64), - /// Route for the `/guilds/:guild_id/embed` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdEmbed(u64), - /// Route for the `/guilds/:guild_id/emojis` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdEmojis(u64), - /// Route for the `/guilds/:guild_id/emojis/:emoji_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdEmojisId(u64), - /// Route for the `/guilds/:guild_id/integrations` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdIntegrations(u64), - /// Route for the `/guilds/:guild_id/integrations/:integration_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdIntegrationsId(u64), - /// Route for the `/guilds/:guild_id/integrations/:integration_id/sync` - /// path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdIntegrationsIdSync(u64), - /// Route for the `/guilds/:guild_id/invites` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdInvites(u64), - /// Route for the `/guilds/:guild_id/members` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdMembers(u64), - /// Route for the `/guilds/:guild_id/members/:user_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdMembersId(u64), - /// Route for the `/guilds/:guild_id/members/:user_id/roles/:role_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdMembersIdRolesId(u64), - /// Route for the `/guilds/:guild_id/members/@me/nick` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdMembersMeNick(u64), - /// Route for the `/guilds/:guild_id/prune` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdPrune(u64), - /// Route for the `/guilds/:guild_id/regions` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdRegions(u64), - /// Route for the `/guilds/:guild_id/roles` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdRoles(u64), - /// Route for the `/guilds/:guild_id/roles/:role_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdRolesId(u64), - /// Route for the `/guilds/:guild_id/vanity-url` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdVanityUrl(u64), - /// Route for the `/guilds/:guild_id/webhooks` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdWebhooks(u64), - /// Route for the `/invites/:code` path. - InvitesCode, - /// Route for the `/users/:user_id` path. - UsersId, - /// Route for the `/users/@me` path. - UsersMe, - /// Route for the `/users/@me/channels` path. - UsersMeChannels, - /// Route for the `/users/@me/guilds` path. - UsersMeGuilds, - /// Route for the `/users/@me/guilds/:guild_id` path. - UsersMeGuildsId, - /// Route for the `/voice/regions` path. - VoiceRegions, - /// Route for the `/webhooks/:webhook_id` path. - WebhooksId(u64), - /// Route where no ratelimit headers are in place (i.e. user account-only - /// routes). - /// - /// This is a special case, in that if the route is `None` then pre- and - /// post-hooks are not executed. - None, -} - pub(super) fn perform(req: Request) -> Result { loop { // This will block if another thread already has the global // unlocked already (due to receiving an x-ratelimit-global). let _ = GLOBAL.lock(); + // Destructure the tuple instead of retrieving the third value to + // take advantage of the type system. If `RouteInfo::deconstruct` + // returns a different number of tuple elements in the future, directly + // accessing a certain index (e.g. `req.route.deconstruct().1`) would + // mean this code would not indicate it might need to be updated for the + // new tuple element amount. + // + // This isn't normally important, but might be for ratelimiting. + let (_, route, _) = req.route.deconstruct(); + // Perform pre-checking here: // // - get the route's relevant rate @@ -377,7 +130,7 @@ pub(super) fn perform(req: Request) -> Result { // - then, perform the request let bucket = Arc::clone(ROUTES .lock() - .entry(req.route) + .entry(route) .or_insert_with(|| { Arc::new(Mutex::new(RateLimit { limit: i64::MAX, @@ -387,7 +140,7 @@ pub(super) fn perform(req: Request) -> Result { })); let mut lock = bucket.lock(); - lock.pre_hook(&req.route); + lock.pre_hook(&route); let response = super::retry(&req)?; @@ -415,7 +168,7 @@ pub(super) fn perform(req: Request) -> Result { // It _may_ be possible for the limit to be raised at any time, // so check if it did from the value of the 'x-ratelimit-limit' // header. If the limit was 5 and is now 7, add 2 to the 'remaining' - if req.route == Route::None { + if route == Route::None { return Ok(response); } else { let redo = if response.headers.get_raw("x-ratelimit-global").is_some() { @@ -423,7 +176,7 @@ pub(super) fn perform(req: Request) -> Result { Ok( if let Some(retry_after) = parse_header(&response.headers, "retry-after")? { - debug!("Ratelimited on route {:?} for {:?}ms", req.route, retry_after); + debug!("Ratelimited on route {:?} for {:?}ms", route, retry_after); thread::sleep(Duration::from_millis(retry_after as u64)); true @@ -432,7 +185,7 @@ pub(super) fn perform(req: Request) -> Result { }, ) } else { - lock.post_hook(&response, &req.route) + lock.post_hook(&response, &route) }; if !redo.unwrap_or(true) { diff --git a/src/http/routing.rs b/src/http/routing.rs new file mode 100644 index 00000000000..dfbbb7fec96 --- /dev/null +++ b/src/http/routing.rs @@ -0,0 +1,1433 @@ +use std::{ + borrow::Cow, + fmt::{Display, Write}, +}; +use super::LightMethod; + +/// A representation of all routes registered within the library. These are safe +/// and memory-efficient representations of each path that functions exist for +/// in the [`http`] module. +/// +/// [`http`]: ../index.html +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Route { + /// Route for the `/channels/:channel_id` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsId(u64), + /// Route for the `/channels/:channel_id/invites` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdInvites(u64), + /// Route for the `/channels/:channel_id/messages` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdMessages(u64), + /// Route for the `/channels/:channel_id/messages/bulk-delete` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdMessagesBulkDelete(u64), + /// Route for the `/channels/:channel_id/messages/:message_id` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + // This route is a unique case. The ratelimit for message _deletions_ is + // different than the overall route ratelimit. + // + // Refer to the docs on [Rate Limits] in the yellow warning section. + // + // Additionally, this needs to be a `LightMethod` from the parent module + // and _not_ a `hyper` `Method` due to `hyper`'s not deriving `Copy`. + // + // [Rate Limits]: https://discordapp.com/developers/docs/topics/rate-limits + ChannelsIdMessagesId(LightMethod, u64), + /// Route for the `/channels/:channel_id/messages/:message_id/ack` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdMessagesIdAck(u64), + /// Route for the `/channels/:channel_id/messages/:message_id/reactions` + /// path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdMessagesIdReactions(u64), + /// Route for the + /// `/channels/:channel_id/messages/:message_id/reactions/:reaction/@me` + /// path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdMessagesIdReactionsUserIdType(u64), + /// Route for the `/channels/:channel_id/permissions/:target_id` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdPermissionsOverwriteId(u64), + /// Route for the `/channels/:channel_id/pins` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdPins(u64), + /// Route for the `/channels/:channel_id/pins/:message_id` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdPinsMessageId(u64), + /// Route for the `/channels/:channel_id/typing` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdTyping(u64), + /// Route for the `/channels/:channel_id/webhooks` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdWebhooks(u64), + /// Route for the `/gateway` path. + Gateway, + /// Route for the `/gateway/bot` path. + GatewayBot, + /// Route for the `/guilds` path. + Guilds, + /// Route for the `/guilds/:guild_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsId(u64), + /// Route for the `/guilds/:guild_id/bans` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdBans(u64), + /// Route for the `/guilds/:guild_id/audit-logs` path. + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdAuditLogs(u64), + /// Route for the `/guilds/:guild_id/bans/:user_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdBansUserId(u64), + /// Route for the `/guilds/:guild_id/channels/:channel_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdChannels(u64), + /// Route for the `/guilds/:guild_id/embed` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdEmbed(u64), + /// Route for the `/guilds/:guild_id/emojis` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdEmojis(u64), + /// Route for the `/guilds/:guild_id/emojis/:emoji_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdEmojisId(u64), + /// Route for the `/guilds/:guild_id/integrations` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdIntegrations(u64), + /// Route for the `/guilds/:guild_id/integrations/:integration_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdIntegrationsId(u64), + /// Route for the `/guilds/:guild_id/integrations/:integration_id/sync` + /// path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdIntegrationsIdSync(u64), + /// Route for the `/guilds/:guild_id/invites` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdInvites(u64), + /// Route for the `/guilds/:guild_id/members` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdMembers(u64), + /// Route for the `/guilds/:guild_id/members/:user_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdMembersId(u64), + /// Route for the `/guilds/:guild_id/members/:user_id/roles/:role_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdMembersIdRolesId(u64), + /// Route for the `/guilds/:guild_id/members/@me/nick` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdMembersMeNick(u64), + /// Route for the `/guilds/:guild_id/prune` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdPrune(u64), + /// Route for the `/guilds/:guild_id/regions` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdRegions(u64), + /// Route for the `/guilds/:guild_id/roles` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdRoles(u64), + /// Route for the `/guilds/:guild_id/roles/:role_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdRolesId(u64), + /// Route for the `/guilds/:guild_id/vanity-url` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdVanityUrl(u64), + /// Route for the `/guilds/:guild_id/webhooks` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: struct.GuildId.html + GuildsIdWebhooks(u64), + /// Route for the `/invites/:code` path. + InvitesCode, + /// Route for the `/users/:user_id` path. + UsersId, + /// Route for the `/users/@me` path. + UsersMe, + /// Route for the `/users/@me/channels` path. + UsersMeChannels, + /// Route for the `/users/@me/guilds` path. + UsersMeGuilds, + /// Route for the `/users/@me/guilds/:guild_id` path. + UsersMeGuildsId, + /// Route for the `/voice/regions` path. + VoiceRegions, + /// Route for the `/webhooks/:webhook_id` path. + WebhooksId(u64), + /// Route where no ratelimit headers are in place (i.e. user account-only + /// routes). + /// + /// This is a special case, in that if the route is `None` then pre- and + /// post-hooks are not executed. + None, +} + +impl Route { + pub fn channel(channel_id: u64) -> String { + format!(api!("/channels/{}"), channel_id) + } + + pub fn channel_invites(channel_id: u64) -> String { + format!(api!("/channels/{}/invites"), channel_id) + } + + pub fn channel_message(channel_id: u64, message_id: u64) -> String { + format!(api!("/channels/{}/messages/{}"), channel_id, message_id) + } + + pub fn channel_message_reaction( + channel_id: u64, + message_id: u64, + user_id: D, + reaction_type: T + ) -> String where D: Display, T: Display { + format!( + api!("/channels/{}/messages/{}/reactions/{}/{}"), + channel_id, + message_id, + reaction_type, + user_id, + ) + } + + pub fn channel_message_reactions( + channel_id: u64, + message_id: u64, + ) -> String { + api!("/channels/{}/messages/{}/reactions", channel_id, message_id) + } + + pub fn channel_message_reactions_list( + channel_id: u64, + message_id: u64, + reaction: &str, + limit: u8, + after: Option, + ) -> String { + let mut uri = format!( + api!("/channels/{}/messages/{}/reactions/{}?limit={}"), + channel_id, + message_id, + reaction, + limit, + ); + + if let Some(after) = after { + let _ = write!(uri, "&after={}", after); + } + + uri + } + + pub fn channel_messages(channel_id: u64, query: Option<&str>) -> String { + format!( + api!("/channels/{}/messages{}"), + channel_id, + query.unwrap_or(""), + ) + } + + pub fn channel_messages_bulk_delete(channel_id: u64) -> String { + format!(api!("/channels/{}/messages/bulk-delete"), channel_id) + } + + pub fn channel_permission(channel_id: u64, target_id: u64) -> String { + format!(api!("/channels/{}/permissions/{}"), channel_id, target_id) + } + + pub fn channel_pin(channel_id: u64, message_id: u64) -> String { + format!(api!("/channels/{}/pins/{}"), channel_id, message_id) + } + + pub fn channel_pins(channel_id: u64) -> String { + format!(api!("/channels/{}/pins"), channel_id) + } + + pub fn channel_typing(channel_id: u64) -> String { + format!(api!("/channels/{}/typing"), channel_id) + } + + pub fn channel_webhooks(channel_id: u64) -> String { + format!(api!("/channels/{}/webhooks"), channel_id) + } + + pub fn gateway() -> &'static str { + api!("/gateway") + } + + pub fn gateway_bot() -> &'static str { + api!("/gateway/bot") + } + + pub fn group_recipient(group_id: u64, user_id: u64) -> String { + format!(api!("/channels/{}/recipients/{}"), group_id, user_id) + } + + pub fn guild(guild_id: u64) -> String { + format!(api!("/guilds/{}"), guild_id) + } + + pub fn guild_audit_logs( + guild_id: u64, + action_type: Option, + user_id: Option, + before: Option, + limit: Option, + ) -> String { + let mut s = format!( + api!("/guilds/{}/audit-logs?"), + guild_id, + ); + + if let Some(action_type) = action_type { + let _ = write!(s, "&action_type={}", action_type); + } + + if let Some(before) = before { + let _ = write!(s, "&before={}", before); + } + + if let Some(limit) = limit { + let _ = write!(s, "&limit={}", limit); + } + + if let Some(user_id) = user_id { + let _ = write!(s, "&user_id={}", user_id); + } + + s + } + + pub fn guild_ban(guild_id: u64, user_id: u64) -> String { + format!(api!("/guilds/{}/bans/{}"), guild_id, user_id) + } + + pub fn guild_ban_optioned( + guild_id: u64, + user_id: u64, + delete_message_days: u8, + reason: &str, + ) -> String { + format!( + api!("/guilds/{}/bans/{}?delete_message_days={}&reason={}"), + guild_id, + user_id, + delete_message_days, + reason, + ) + } + + pub fn guild_bans(guild_id: u64) -> String { + format!(api!("/guilds/{}/bans"), guild_id) + } + + pub fn guild_channels(guild_id: u64) -> String { + format!(api!("/guilds/{}/channels"), guild_id) + } + + pub fn guild_embed(guild_id: u64) -> String { + format!(api!("/guilds/{}/embed"), guild_id) + } + + pub fn guild_emojis(guild_id: u64) -> String { + format!(api!("/guilds/{}/emojis"), guild_id) + } + + pub fn guild_emoji(guild_id: u64, emoji_id: u64) -> String { + format!(api!("/guilds/{}/emojis/{}"), guild_id, emoji_id) + } + + pub fn guild_integration( + guild_id: u64, + integration_id: u64, + ) -> String { + format!(api!("/guilds/{}/integrations/{}"), guild_id, integration_id) + } + + pub fn guild_integration_sync( + guild_id: u64, + integration_id: u64, + ) -> String { + format!( + api!("/guilds/{}/integrations/{}/sync"), + guild_id, + integration_id, + ) + } + + pub fn guild_integrations(guild_id: u64) -> String { + format!(api!("/guilds/{}/integrations"), guild_id) + } + + pub fn guild_invites(guild_id: u64) -> String { + format!(api!("/guilds/{}/invites"), guild_id) + } + + pub fn guild_member(guild_id: u64, user_id: u64) -> String { + format!(api!("/guilds/{}/members/{}"), guild_id, user_id) + } + + pub fn guild_member_role( + guild_id: u64, + user_id: u64, + role_id: u64, + ) -> String { + format!( + api!("/guilds/{}/members/{}/roles/{}"), + guild_id, + user_id, + role_id, + ) + } + + pub fn guild_members(guild_id: u64) -> String { + format!(api!("/guilds/{}/members"), guild_id) + } + + pub fn guild_members_optioned( + guild_id: u64, + after: Option, + limit: Option, + ) -> String { + let mut s = format!(api!("/guilds/{}/members?"), guild_id); + + if let Some(after) = after { + let _ = write!(s, "&after={}", after); + } + + if let Some(limit) = limit { + let _ = write!(s, "&limit={}", limit); + } + + s + } + + pub fn guild_nickname(guild_id: u64) -> String { + format!(api!("/guilds/{}/members/@me/nick"), guild_id) + } + + pub fn guild_prune(guild_id: u64, days: u64) -> String { + format!(api!("/guilds/{}/prune?days={}"), guild_id, days) + } + + pub fn guild_regions(guild_id: u64) -> String { + format!(api!("/guilds/{}/regions"), guild_id) + } + + pub fn guild_role(guild_id: u64, role_id: u64) -> String { + format!(api!("/guilds/{}/roles/{}"), guild_id, role_id) + } + + pub fn guild_roles(guild_id: u64) -> String { + format!(api!("/guilds/{}/roles"), guild_id) + } + + pub fn guild_vanity_url(guild_id: u64) -> String { + format!(api!("/guilds/{}/vanity-url"), guild_id) + } + + pub fn guild_webhooks(guild_id: u64) -> String { + format!(api!("/guilds/{}/webhooks"), guild_id) + } + + pub fn guilds() -> &'static str { + api!("/guilds") + } + + pub fn invite(code: &str) -> String { + format!(api!("/invites/{}"), code) + } + + pub fn invite_optioned(code: &str, stats: bool) -> String { + format!(api!("/invites/{}?with_counts={}"), code, stats) + } + + pub fn oauth2_application_current() -> &'static str { + api!("/oauth2/applications/@me") + } + + pub fn private_channel() -> &'static str { + api!("/users/@me/channels") + } + + pub fn status_incidents_unresolved() -> &'static str { + status!("/incidents/unresolved.json") + } + + pub fn status_maintenances_active() -> &'static str { + status!("/scheduled-maintenances/active.json") + } + + pub fn status_maintenances_upcoming() -> &'static str { + status!("/scheduled-maintenances/upcoming.json") + } + + pub fn user(target: D) -> String { + format!(api!("/users/{}"), target) + } + + pub fn user_dm_channels(target: D) -> String { + format!(api!("/users/{}/channels"), target) + } + + pub fn user_guild(target: D, guild_id: u64) -> String { + format!(api!("/users/{}/guilds/{}"), target, guild_id) + } + + pub fn user_guilds(target: D) -> String { + format!(api!("/users/{}/guilds"), target) + } + + pub fn user_guilds_optioned( + target: D, + after: Option, + before: Option, + limit: u64, + ) -> String { + let mut s = format!(api!("/users/{}/guilds?limit={}&"), target, limit); + + if let Some(after) = after { + let _ = write!(s, "&after={}", after); + } + + if let Some(before) = before { + let _ = write!(s, "&before={}", before); + } + + s + } + + pub fn voice_regions() -> &'static str { + api!("/voice/regions") + } + + pub fn webhook(webhook_id: u64) -> String { + format!(api!("/webhooks/{}"), webhook_id) + } + + pub fn webhook_with_token(webhook_id: u64, token: D) -> String + where D: Display { + format!(api!("/webhooks/{}/{}"), webhook_id, token) + } + + pub fn webhook_with_token_optioned(webhook_id: u64, token: D, wait: bool) + -> String where D: Display { + format!(api!("/webhooks/{}/{}?wait={}"), webhook_id, token, wait) + } +} + +#[derive(Clone, Debug)] +pub enum RouteInfo<'a> { + AddGroupRecipient { + group_id: u64, + user_id: u64, + }, + AddMemberRole { + guild_id: u64, + role_id: u64, + user_id: u64, + }, + GuildBanUser { + guild_id: u64, + user_id: u64, + delete_message_days: Option, + reason: Option<&'a str>, + }, + BroadcastTyping { + channel_id: u64, + }, + CreateChannel { + guild_id: u64, + }, + CreateEmoji { + guild_id: u64, + }, + CreateGuild, + CreateGuildIntegration { + guild_id: u64, + integration_id: u64, + }, + CreateInvite { + channel_id: u64, + }, + CreateMessage { + channel_id: u64, + }, + CreatePermission { + channel_id: u64, + target_id: u64, + }, + CreatePrivateChannel, + CreateReaction { + channel_id: u64, + message_id: u64, + reaction: &'a str, + }, + CreateRole { + guild_id: u64, + }, + CreateWebhook { + channel_id: u64, + }, + DeleteChannel { + channel_id: u64, + }, + DeleteEmoji { + guild_id: u64, + emoji_id: u64, + }, + DeleteGuild { + guild_id: u64, + }, + DeleteGuildIntegration { + guild_id: u64, + integration_id: u64, + }, + DeleteInvite { + code: &'a str, + }, + DeleteMessage { + channel_id: u64, + message_id: u64, + }, + DeleteMessages { + channel_id: u64, + }, + DeleteMessageReactions { + channel_id: u64, + message_id: u64, + }, + DeletePermission { + channel_id: u64, + target_id: u64, + }, + DeleteReaction { + channel_id: u64, + message_id: u64, + user: &'a str, + reaction: &'a str, + }, + DeleteRole { + guild_id: u64, + role_id: u64, + }, + DeleteWebhook { + webhook_id: u64, + }, + DeleteWebhookWithToken { + token: &'a str, + webhook_id: u64, + }, + EditChannel { + channel_id: u64, + }, + EditEmoji { + guild_id: u64, + emoji_id: u64, + }, + EditGuild { + guild_id: u64, + }, + EditGuildChannels { + guild_id: u64, + }, + EditGuildEmbed { + guild_id: u64, + }, + EditMember { + guild_id: u64, + user_id: u64, + }, + EditMessage { + channel_id: u64, + message_id: u64, + }, + EditNickname { + guild_id: u64, + }, + EditProfile, + EditRole { + guild_id: u64, + role_id: u64, + }, + EditWebhook { + webhook_id: u64, + }, + EditWebhookWithToken { + token: &'a str, + webhook_id: u64, + }, + ExecuteWebhook { + token: &'a str, + wait: bool, + webhook_id: u64, + }, + GetActiveMaintenance, + GetAuditLogs { + action_type: Option, + before: Option, + guild_id: u64, + limit: Option, + user_id: Option, + }, + GetBans { + guild_id: u64, + }, + GetBotGateway, + GetChannel { + channel_id: u64, + }, + GetChannelInvites { + channel_id: u64, + }, + GetChannelWebhooks { + channel_id: u64, + }, + GetChannels { + guild_id: u64, + }, + GetCurrentApplicationInfo, + GetCurrentUser, + GetGateway, + GetGuild { + guild_id: u64, + }, + GetGuildEmbed { + guild_id: u64, + }, + GetGuildIntegrations { + guild_id: u64, + }, + GetGuildInvites { + guild_id: u64, + }, + GetGuildMembers { + after: Option, + limit: Option, + guild_id: u64, + }, + GetGuildPruneCount { + days: u64, + guild_id: u64, + }, + GetGuildRegions { + guild_id: u64, + }, + GetGuildRoles { + guild_id: u64, + }, + GetGuildVanityUrl { + guild_id: u64, + }, + GetGuildWebhooks { + guild_id: u64, + }, + GetGuilds { + after: Option, + before: Option, + limit: u64, + }, + GetInvite { + code: &'a str, + stats: bool, + }, + GetMember { + guild_id: u64, + user_id: u64, + }, + GetMessage { + channel_id: u64, + message_id: u64, + }, + GetMessages { + channel_id: u64, + query: String, + }, + GetPins { + channel_id: u64, + }, + GetReactionUsers { + after: Option, + channel_id: u64, + limit: u8, + message_id: u64, + reaction: String, + }, + GetUnresolvedIncidents, + GetUpcomingMaintenances, + GetUser { + user_id: u64, + }, + GetUserDmChannels, + GetVoiceRegions, + GetWebhook { + webhook_id: u64, + }, + GetWebhookWithToken { + token: &'a str, + webhook_id: u64, + }, + KickMember { + guild_id: u64, + user_id: u64, + }, + LeaveGroup { + group_id: u64, + }, + LeaveGuild { + guild_id: u64, + }, + RemoveGroupRecipient { + group_id: u64, + user_id: u64, + }, + PinMessage { + channel_id: u64, + message_id: u64, + }, + RemoveBan { + guild_id: u64, + user_id: u64, + }, + RemoveMemberRole { + guild_id: u64, + role_id: u64, + user_id: u64, + }, + StartGuildPrune { + days: u64, + guild_id: u64, + }, + StartIntegrationSync { + guild_id: u64, + integration_id: u64, + }, + StatusIncidentsUnresolved, + StatusMaintenancesActive, + StatusMaintenancesUpcoming, + UnpinMessage { + channel_id: u64, + message_id: u64, + }, +} + +impl<'a> RouteInfo<'a> { + pub fn deconstruct(&self) -> (LightMethod, Route, Cow) { + match *self { + RouteInfo::AddGroupRecipient { group_id, user_id } => ( + LightMethod::Post, + Route::None, + Cow::from(Route::group_recipient(group_id, user_id)), + ), + RouteInfo::AddMemberRole { guild_id, role_id, user_id } => ( + LightMethod::Delete, + Route::GuildsIdMembersIdRolesId(guild_id), + Cow::from(Route::guild_member_role(guild_id, user_id, role_id)), + ), + RouteInfo::GuildBanUser { + guild_id, + delete_message_days, + reason, + user_id, + } => ( + // TODO + LightMethod::Delete, + Route::GuildsIdBansUserId(guild_id), + Cow::from(Route::guild_ban_optioned( + guild_id, + user_id, + delete_message_days.unwrap_or(0), + reason.unwrap_or(""), + )), + ), + RouteInfo::BroadcastTyping { channel_id } => ( + LightMethod::Post, + Route::ChannelsIdTyping(channel_id), + Cow::from(Route::channel_typing(channel_id)), + ), + RouteInfo::CreateChannel { guild_id } => ( + LightMethod::Post, + Route::GuildsIdChannels(guild_id), + Cow::from(Route::guild_channels(guild_id)), + ), + RouteInfo::CreateEmoji { guild_id } => ( + LightMethod::Post, + Route::GuildsIdEmojis(guild_id), + Cow::from(Route::guild_emojis(guild_id)), + ), + RouteInfo::CreateGuild => ( + LightMethod::Post, + Route::Guilds, + Cow::from(Route::guilds()), + ), + RouteInfo::CreateGuildIntegration { guild_id, integration_id } => ( + LightMethod::Post, + Route::GuildsIdIntegrationsId(guild_id), + Cow::from(Route::guild_integration(guild_id, integration_id)), + ), + RouteInfo::CreateInvite { channel_id } => ( + LightMethod::Post, + Route::ChannelsIdInvites(channel_id), + Cow::from(Route::channel_invites(channel_id)), + ), + RouteInfo::CreateMessage { channel_id } => ( + LightMethod::Post, + Route::ChannelsIdMessages(channel_id), + Cow::from(Route::channel_messages(channel_id, None)), + ), + RouteInfo::CreatePermission { channel_id, target_id } => ( + LightMethod::Post, + Route::ChannelsIdPermissionsOverwriteId(channel_id), + Cow::from(Route::channel_permission(channel_id, target_id)), + ), + RouteInfo::CreatePrivateChannel => ( + LightMethod::Post, + Route::UsersMeChannels, + Cow::from(Route::user_dm_channels("@me")), + ), + RouteInfo::CreateReaction { channel_id, message_id, reaction } => ( + LightMethod::Put, + Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), + Cow::from(Route::channel_message_reaction( + channel_id, + message_id, + "@me", + reaction, + )), + ), + RouteInfo::CreateRole { guild_id } => ( + LightMethod::Delete, + Route::GuildsIdRoles(guild_id), + Cow::from(Route::guild_roles(guild_id)), + ), + RouteInfo::CreateWebhook { channel_id } => ( + LightMethod::Delete, + Route::ChannelsIdWebhooks(channel_id), + Cow::from(Route::channel_webhooks(channel_id)), + ), + RouteInfo::DeleteChannel { channel_id } => ( + LightMethod::Delete, + Route::ChannelsId(channel_id), + Cow::from(Route::channel(channel_id)), + ), + RouteInfo::DeleteEmoji { emoji_id, guild_id } => ( + LightMethod::Delete, + Route::GuildsIdEmojisId(guild_id), + Cow::from(Route::guild_emoji(guild_id, emoji_id)), + ), + RouteInfo::DeleteGuild { guild_id } => ( + LightMethod::Delete, + Route::GuildsId(guild_id), + Cow::from(Route::guild(guild_id)), + ), + RouteInfo::DeleteGuildIntegration { guild_id, integration_id } => ( + LightMethod::Delete, + Route::GuildsIdIntegrationsId(guild_id), + Cow::from(Route::guild_integration(guild_id, integration_id)), + ), + RouteInfo::DeleteInvite { code } => ( + LightMethod::Delete, + Route::InvitesCode, + Cow::from(Route::invite(code)), + ), + RouteInfo::DeleteMessageReactions { channel_id, message_id } => ( + LightMethod::Delete, + Route::ChannelsIdMessagesIdReactions(channel_id), + Cow::from(Route::channel_message_reactions( + channel_id, + message_id, + )), + ), + RouteInfo::DeleteMessage { channel_id, message_id } => ( + LightMethod::Delete, + Route::ChannelsIdMessagesId(LightMethod::Delete, message_id), + Cow::from(Route::channel_message(channel_id, message_id)), + ), + RouteInfo::DeleteMessages { channel_id } => ( + LightMethod::Delete, + Route::ChannelsIdMessagesBulkDelete(channel_id), + Cow::from(Route::channel_messages_bulk_delete(channel_id)), + ), + RouteInfo::DeletePermission { channel_id, target_id } => ( + LightMethod::Delete, + Route::ChannelsIdPermissionsOverwriteId(channel_id), + Cow::from(Route::channel_permission(channel_id, target_id)), + ), + RouteInfo::DeleteReaction { + channel_id, + message_id, + reaction, + user, + } => ( + LightMethod::Delete, + Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), + Cow::from(Route::channel_message_reaction( + channel_id, + message_id, + user, + reaction, + )) + ), + RouteInfo::DeleteRole { guild_id, role_id } => ( + LightMethod::Delete, + Route::GuildsIdRolesId(guild_id), + Cow::from(Route::guild_role(guild_id, role_id)), + ), + RouteInfo::DeleteWebhook { webhook_id } => ( + LightMethod::Delete, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook(webhook_id)), + ), + RouteInfo::DeleteWebhookWithToken { token, webhook_id } => ( + LightMethod::Delete, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook_with_token(webhook_id, token)), + ), + RouteInfo::EditChannel { channel_id } => ( + LightMethod::Patch, + Route::ChannelsId(channel_id), + Cow::from(Route::channel(channel_id)), + ), + RouteInfo::EditEmoji { emoji_id, guild_id } => ( + LightMethod::Patch, + Route::GuildsIdEmojisId(guild_id), + Cow::from(Route::guild_emoji(guild_id, emoji_id)), + ), + RouteInfo::EditGuild { guild_id } => ( + LightMethod::Patch, + Route::GuildsId(guild_id), + Cow::from(Route::guild(guild_id)), + ), + RouteInfo::EditGuildChannels { guild_id } => ( + LightMethod::Patch, + Route::GuildsIdChannels(guild_id), + Cow::from(Route::guild_channels(guild_id)), + ), + RouteInfo::EditGuildEmbed { guild_id } => ( + LightMethod::Patch, + Route::GuildsIdEmbed(guild_id), + Cow::from(Route::guild_embed(guild_id)), + ), + RouteInfo::EditMember { guild_id, user_id } => ( + LightMethod::Patch, + Route::GuildsIdMembersId(guild_id), + Cow::from(Route::guild_member(guild_id, user_id)), + ), + RouteInfo::EditMessage { channel_id, message_id } => ( + LightMethod::Patch, + Route::ChannelsIdMessagesId(LightMethod::Patch, channel_id), + Cow::from(Route::channel_message(channel_id, message_id)), + ), + RouteInfo::EditNickname { guild_id } => ( + LightMethod::Patch, + Route::GuildsIdMembersMeNick(guild_id), + Cow::from(Route::guild_nickname(guild_id)), + ), + RouteInfo::EditProfile => ( + LightMethod::Patch, + Route::UsersMe, + Cow::from(Route::user("@me")), + ), + RouteInfo::EditRole { guild_id, role_id } => ( + LightMethod::Patch, + Route::GuildsIdRolesId(guild_id), + Cow::from(Route::guild_role(guild_id, role_id)), + ), + RouteInfo::EditWebhook { webhook_id } => ( + LightMethod::Patch, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook(webhook_id)), + ), + RouteInfo::EditWebhookWithToken { token, webhook_id } => ( + LightMethod::Patch, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook_with_token(webhook_id, token)), + ), + RouteInfo::ExecuteWebhook { token, wait, webhook_id } => ( + LightMethod::Post, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook_with_token_optioned( + webhook_id, + token, + wait, + )), + ), + RouteInfo::GetActiveMaintenance => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_maintenances_active()), + ), + RouteInfo::GetAuditLogs { + action_type, + before, + guild_id, + limit, + user_id, + } => ( + LightMethod::Get, + Route::GuildsIdAuditLogs(guild_id), + Cow::from(Route::guild_audit_logs( + guild_id, + action_type, + user_id, + before, + limit, + )), + ), + RouteInfo::GetBans { guild_id } => ( + LightMethod::Get, + Route::GuildsIdBans(guild_id), + Cow::from(Route::guild_bans(guild_id)), + ), + RouteInfo::GetBotGateway => ( + LightMethod::Get, + Route::GatewayBot, + Cow::from(Route::gateway_bot()), + ), + RouteInfo::GetChannel { channel_id } => ( + LightMethod::Get, + Route::ChannelsId(channel_id), + Cow::from(Route::channel(channel_id)), + ), + RouteInfo::GetChannelInvites { channel_id } => ( + LightMethod::Get, + Route::ChannelsIdInvites(channel_id), + Cow::from(Route::channel_invites(channel_id)), + ), + RouteInfo::GetChannelWebhooks { channel_id } => ( + LightMethod::Get, + Route::ChannelsIdWebhooks(channel_id), + Cow::from(Route::channel_webhooks(channel_id)), + ), + RouteInfo::GetChannels { guild_id } => ( + LightMethod::Get, + Route::GuildsIdChannels(guild_id), + Cow::from(Route::guild_channels(guild_id)), + ), + RouteInfo::GetCurrentApplicationInfo => ( + LightMethod::Get, + Route::None, + Cow::from(Route::oauth2_application_current()), + ), + RouteInfo::GetCurrentUser => ( + LightMethod::Get, + Route::UsersMe, + Cow::from(Route::user("@me")), + ), + RouteInfo::GetGateway => ( + LightMethod::Get, + Route::Gateway, + Cow::from(Route::gateway()), + ), + RouteInfo::GetGuild { guild_id } => ( + LightMethod::Get, + Route::GuildsId(guild_id), + Cow::from(Route::guild(guild_id)), + ), + RouteInfo::GetGuildEmbed { guild_id } => ( + LightMethod::Get, + Route::GuildsIdEmbed(guild_id), + Cow::from(Route::guild_embed(guild_id)), + ), + RouteInfo::GetGuildIntegrations { guild_id } => ( + LightMethod::Get, + Route::GuildsIdIntegrations(guild_id), + Cow::from(Route::guild_integrations(guild_id)), + ), + RouteInfo::GetGuildInvites { guild_id } => ( + LightMethod::Get, + Route::GuildsIdInvites(guild_id), + Cow::from(Route::guild_invites(guild_id)), + ), + RouteInfo::GetGuildMembers { after, guild_id, limit } => ( + LightMethod::Get, + Route::GuildsIdMembers(guild_id), + Cow::from(Route::guild_members_optioned(guild_id, after, limit)), + ), + RouteInfo::GetGuildPruneCount { days, guild_id } => ( + LightMethod::Get, + Route::GuildsIdPrune(guild_id), + Cow::from(Route::guild_prune(guild_id, days)), + ), + RouteInfo::GetGuildRegions { guild_id } => ( + LightMethod::Get, + Route::GuildsIdRegions(guild_id), + Cow::from(Route::guild_regions(guild_id)), + ), + RouteInfo::GetGuildRoles { guild_id } => ( + LightMethod::Get, + Route::GuildsIdRoles(guild_id), + Cow::from(Route::guild_roles(guild_id)), + ), + RouteInfo::GetGuildVanityUrl { guild_id } => ( + LightMethod::Get, + Route::GuildsIdVanityUrl(guild_id), + Cow::from(Route::guild_vanity_url(guild_id)), + ), + RouteInfo::GetGuildWebhooks { guild_id } => ( + LightMethod::Get, + Route::GuildsIdWebhooks(guild_id), + Cow::from(Route::guild_webhooks(guild_id)), + ), + RouteInfo::GetGuilds { after, before, limit } => ( + LightMethod::Get, + Route::UsersMeGuilds, + Cow::from(Route::user_guilds_optioned( + "@me", + after, + before, + limit, + )), + ), + RouteInfo::GetInvite { code, stats } => ( + LightMethod::Get, + Route::InvitesCode, + Cow::from(Route::invite_optioned(code, stats)), + ), + RouteInfo::GetMember { guild_id, user_id } => ( + LightMethod::Get, + Route::GuildsIdMembersId(guild_id), + Cow::from(Route::guild_member(guild_id, user_id)), + ), + RouteInfo::GetMessage { channel_id, message_id } => ( + LightMethod::Get, + Route::ChannelsIdMessagesId(LightMethod::Get, channel_id), + Cow::from(Route::channel_message(channel_id, message_id)), + ), + RouteInfo::GetMessages { channel_id, ref query } => ( + LightMethod::Get, + Route::ChannelsIdMessages(channel_id), + Cow::from(Route::channel_messages( + channel_id, + Some(query.as_ref()), + )), + ), + RouteInfo::GetPins { channel_id } => ( + LightMethod::Get, + Route::ChannelsIdPins(channel_id), + Cow::from(Route::channel_pins(channel_id)), + ), + RouteInfo::GetReactionUsers { + after, + channel_id, + limit, + message_id, + ref reaction, + } => ( + LightMethod::Get, + Route::ChannelsIdMessagesIdReactions(channel_id), + Cow::from(Route::channel_message_reactions_list( + channel_id, + message_id, + reaction, + limit, + after, + )), + ), + RouteInfo::GetUnresolvedIncidents => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_incidents_unresolved()), + ), + RouteInfo::GetUpcomingMaintenances => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_maintenances_upcoming()), + ), + RouteInfo::GetUser { user_id } => ( + LightMethod::Get, + Route::UsersId, + Cow::from(Route::user(user_id)), + ), + RouteInfo::GetUserDmChannels => ( + LightMethod::Get, + Route::UsersMeChannels, + Cow::from(Route::user_dm_channels("@me")), + ), + RouteInfo::GetVoiceRegions => ( + LightMethod::Get, + Route::VoiceRegions, + Cow::from(Route::voice_regions()), + ), + RouteInfo::GetWebhook { webhook_id } => ( + LightMethod::Get, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook(webhook_id)), + ), + RouteInfo::GetWebhookWithToken { token, webhook_id } => ( + LightMethod::Get, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook_with_token(webhook_id, token)), + ), + RouteInfo::KickMember { guild_id, user_id } => ( + LightMethod::Delete, + Route::GuildsIdMembersId(guild_id), + Cow::from(Route::guild_member(guild_id, user_id)), + ), + RouteInfo::LeaveGroup { group_id } => ( + LightMethod::Delete, + Route::ChannelsId(group_id), + Cow::from(Route::channel(group_id)), + ), + RouteInfo::LeaveGuild { guild_id } => ( + LightMethod::Delete, + Route::UsersMeGuildsId, + Cow::from(Route::user_guild("@me", guild_id)), + ), + RouteInfo::RemoveGroupRecipient { group_id, user_id } => ( + LightMethod::Delete, + Route::None, + Cow::from(Route::group_recipient(group_id, user_id)), + ), + RouteInfo::PinMessage { channel_id, message_id } => ( + LightMethod::Put, + Route::ChannelsIdPins(channel_id), + Cow::from(Route::channel_pin(channel_id, message_id)), + ), + RouteInfo::RemoveBan { guild_id, user_id } => ( + LightMethod::Delete, + Route::GuildsIdBansUserId(guild_id), + Cow::from(Route::guild_ban(guild_id, user_id)), + ), + RouteInfo::RemoveMemberRole { guild_id, role_id, user_id } => ( + LightMethod::Delete, + Route::GuildsIdMembersIdRolesId(guild_id), + Cow::from(Route::guild_member_role(guild_id, user_id, role_id)), + ), + RouteInfo::StartGuildPrune { days, guild_id } => ( + LightMethod::Post, + Route::GuildsIdPrune(guild_id), + Cow::from(Route::guild_prune(guild_id, days)), + ), + RouteInfo::StartIntegrationSync { guild_id, integration_id } => ( + LightMethod::Post, + Route::GuildsIdIntegrationsId(guild_id), + Cow::from(Route::guild_integration_sync( + guild_id, + integration_id, + )), + ), + RouteInfo::StatusIncidentsUnresolved => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_incidents_unresolved()), + ), + RouteInfo::StatusMaintenancesActive => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_maintenances_active()), + ), + RouteInfo::StatusMaintenancesUpcoming => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_maintenances_upcoming()), + ), + RouteInfo::UnpinMessage { channel_id, message_id } => ( + LightMethod::Delete, + Route::ChannelsIdPinsMessageId(channel_id), + Cow::from(Route::channel_pin(channel_id, message_id)), + ), + } + } +}