diff options
| author | Zeyla Hellyer <[email protected]> | 2018-08-04 21:30:08 -0700 |
|---|---|---|
| committer | Zeyla Hellyer <[email protected]> | 2018-08-04 21:30:08 -0700 |
| commit | a0b0dd226f9ad2476729fa79dbc680bd08aa44b3 (patch) | |
| tree | ac4b45b1c629c8149e74dfdf762f1543cefb9321 /src/http | |
| parent | Maintain a single, re-used HTTP client (diff) | |
| download | serenity-a0b0dd226f9ad2476729fa79dbc680bd08aa44b3.tar.xz serenity-a0b0dd226f9ad2476729fa79dbc680bd08aa44b3.zip | |
Redo how requests are formed in HTTP module
Instead of passing around a function that builds an HTTP client request builder,
pass around a struct that contains the necessary information _to_ build that
request builder.
Additionally, instead of using a macro to generate requests, just call a request
function. This saves some required code expansion and is just easier to read
and maintain.
Diffstat (limited to 'src/http')
| -rw-r--r-- | src/http/mod.rs | 1263 | ||||
| -rw-r--r-- | src/http/ratelimiting.rs | 19 |
2 files changed, 688 insertions, 594 deletions
diff --git a/src/http/mod.rs b/src/http/mod.rs index 05b198a..9151cfd 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -33,12 +33,13 @@ pub use hyper::status::{StatusClass, StatusCode}; use constants; use hyper::{ client::{ + Body as HyperBody, Client as HyperClient, - Request, RequestBuilder, + Request as HyperRequest, Response as HyperResponse }, - header::ContentType, + header::{ContentType, Headers}, method::Method, mime::{Mime, SubLevel, TopLevel}, net::HttpsConnector, @@ -73,6 +74,14 @@ lazy_static! { }; } +struct Request { + body: Option<Vec<u8>>, + headers: Option<Headers>, + method: Method, + route: Route, + url: String, +} + /// An method used for ratelimiting special routes. /// /// This is needed because `hyper`'s `Method` enum does not derive Copy. @@ -130,16 +139,13 @@ pub fn set_token(token: &str) { TOKEN.lock().clone_from(&token.to_string()); } /// [`Group::add_recipient`]: ../model/channel/struct.Group.html#method.add_recipient /// [`User`]: ../model/user/struct.User.html pub fn add_group_recipient(group_id: u64, user_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::None, - put, - "/channels/{}/recipients/{}", - group_id, - user_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Put, + route: Route::None, + url: api!("/channels/{}/recipients/{}", group_id, user_id), + })?) } /// Adds a single [`Role`] to a [`Member`] in a [`Guild`]. @@ -152,17 +158,13 @@ pub fn add_group_recipient(group_id: u64, user_id: u64) -> Result<()> { /// [`Role`]: ../model/guild/struct.Role.html /// [Manage Roles]: ../model/permissions/constant.MANAGE_ROLES.html pub fn add_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdMembersIdRolesId(guild_id), - put, - "/guilds/{}/members/{}/roles/{}", - guild_id, - user_id, - role_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Put, + route: Route::GuildsIdMembersIdRolesId(guild_id), + url: api!("/guilds/{}/members/{}/roles/{}", guild_id, user_id, role_id), + })?) } /// Bans a [`User`] from a [`Guild`], removing their messages sent in the last @@ -177,18 +179,19 @@ pub fn add_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> /// [`User`]: ../model/user/struct.User.html /// [Ban Members]: ../model/permissions/constant.BAN_MEMBERS.html pub fn ban_user(guild_id: u64, user_id: u64, delete_message_days: u8, reason: &str) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdBansUserId(guild_id), - put, + verify(204, request(Request { + body: None, + headers: None, + method: Method::Put, + route: Route::GuildsIdBansUserId(guild_id), + url: api!( "/guilds/{}/bans/{}?delete_message_days={}&reason={}", guild_id, user_id, delete_message_days, - reason + reason, ), - ) + })?) } /// Ban zeyla from a [`Guild`], removing her messages sent in the last X number @@ -244,15 +247,13 @@ pub fn ban_servermoms(guild_id: u64, delete_message_days: u8, reason: &str) -> R /// /// [`Channel`]: ../model/channel/enum.Channel.html pub fn broadcast_typing(channel_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdTyping(channel_id), - post, - "/channels/{}/typing", - channel_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Post, + route: Route::ChannelsIdTyping(channel_id), + url: api!("/channels/{}/typing", channel_id), + })?) } /// Creates a [`GuildChannel`] in the [`Guild`] given its Id. @@ -266,13 +267,13 @@ pub fn broadcast_typing(channel_id: u64) -> Result<()> { /// [docs]: https://discordapp.com/developers/docs/resources/guild#create-guild-channel /// [Manage Channels]: ../model/permissions/constant.MANAGE_CHANNELS.html pub fn create_channel(guild_id: u64, map: &Value) -> Result<GuildChannel> { - let body = map.to_string(); - let response = request!( - Route::GuildsIdChannels(guild_id), - post(body), - "/guilds/{}/channels", - guild_id - ); + let response = request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Post, + route: Route::GuildsIdChannels(guild_id), + url: api!("/guilds/{}/channels", guild_id), + })?; serde_json::from_reader::<HyperResponse, GuildChannel>(response) .map_err(From::from) @@ -289,13 +290,13 @@ pub fn create_channel(guild_id: u64, map: &Value) -> Result<GuildChannel> { /// [`Guild`]: ../model/guild/struct.Guild.html /// [Manage Emojis]: ../model/permissions/constant.MANAGE_EMOJIS.html pub fn create_emoji(guild_id: u64, map: &Value) -> Result<Emoji> { - let body = map.to_string(); - let response = request!( - Route::GuildsIdEmojis(guild_id), - post(body), - "/guilds/{}/emojis", - guild_id - ); + let response = request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Post, + route: Route::GuildsIdEmojis(guild_id), + url: api!("/guilds/{}/emojis", guild_id), + })?; serde_json::from_reader::<HyperResponse, Emoji>(response) .map_err(From::from) @@ -338,8 +339,13 @@ pub fn create_emoji(guild_id: u64, map: &Value) -> Result<Emoji> { /// https://discordapp.com/developers/docs/resources/guild#create-guild /// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild pub fn create_guild(map: &Value) -> Result<PartialGuild> { - let body = map.to_string(); - let response = request!(Route::Guilds, post(body), "/guilds"); + let response = request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Post, + route: Route::Guilds, + url: api!("/guilds").to_owned(), + })?; serde_json::from_reader::<HyperResponse, PartialGuild>(response) .map_err(From::from) @@ -356,18 +362,13 @@ pub fn create_guild(map: &Value) -> Result<PartialGuild> { /// [Manage Guild]: ../model/permissions/constant.MANAGE_GUILD.html /// [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<()> { - let body = map.to_string(); - - verify( - 204, - request!( - Route::GuildsIdIntegrations(guild_id), - post(body), - "/guilds/{}/integrations/{}", - guild_id, - integration_id - ), - ) + verify(204, request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Post, + route: Route::GuildsIdIntegrations(guild_id), + url: api!("/guilds/{}/integrations/{}", guild_id, integration_id), + })?) } /// Creates a [`RichInvite`] for the given [channel][`GuildChannel`]. @@ -383,13 +384,15 @@ pub fn create_guild_integration(guild_id: u64, integration_id: u64, map: &Value) /// [Create Invite]: ../model/permissions/constant.CREATE_INVITE.html /// [docs]: https://discordapp.com/developers/docs/resources/channel#create-channel-invite pub fn create_invite(channel_id: u64, map: &JsonMap) -> Result<RichInvite> { - let body = serde_json::to_string(map)?; - let response = request!( - Route::ChannelsIdInvites(channel_id), - post(body), - "/channels/{}/invites", - channel_id - ); + let body = serde_json::to_vec(map)?; + + let response = request(Request { + body: Some(body), + headers: None, + method: Method::Post, + route: Route::ChannelsIdInvites(channel_id), + url: api!("/channels/{}/invites", channel_id), + })?; serde_json::from_reader::<HyperResponse, RichInvite>(response) .map_err(From::from) @@ -397,24 +400,24 @@ pub fn create_invite(channel_id: u64, map: &JsonMap) -> Result<RichInvite> { /// 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 = map.to_string(); - - verify( - 204, - request!( - Route::ChannelsIdPermissionsOverwriteId(channel_id), - put(body), - "/channels/{}/permissions/{}", - channel_id, - target_id - ), - ) + verify(204, request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Put, + route: Route::ChannelsIdPermissionsOverwriteId(channel_id), + url: api!("/channels/{}/permissions/{}", channel_id, target_id), + })?) } /// Creates a private channel with a user. pub fn create_private_channel(map: &Value) -> Result<PrivateChannel> { - let body = map.to_string(); - let response = request!(Route::UsersMeChannels, post(body), "/users/@me/channels"); + let response = request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Post, + route: Route::UsersMeChannels, + url: api!("/users/@me/channels").to_owned(), + })?; serde_json::from_reader::<HyperResponse, PrivateChannel>(response) .map_err(From::from) @@ -425,28 +428,31 @@ pub fn create_reaction(channel_id: u64, message_id: u64, reaction_type: &ReactionType) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), - put, + verify(204, request(Request { + body: None, + headers: None, + method: Method::Put, + route: Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), + url: api!( "/channels/{}/messages/{}/reactions/{}/@me", channel_id, message_id, - reaction_type.as_data() + reaction_type.as_data(), ), - ) + })?) } /// Creates a role. pub fn create_role(guild_id: u64, map: &JsonMap) -> Result<Role> { - let body = serde_json::to_string(map)?; - let response = request!( - Route::GuildsIdRoles(guild_id), - post(body), - "/guilds/{}/roles", - guild_id - ); + let body = serde_json::to_vec(map)?; + + let response = request(Request { + body: Some(body), + headers: None, + method: Method::Post, + route: Route::GuildsIdRoles(guild_id), + url: api!("/guilds/{}/roles", guild_id), + })?; serde_json::from_reader::<HyperResponse, Role>(response) .map_err(From::from) @@ -483,13 +489,13 @@ pub fn create_role(guild_id: u64, map: &JsonMap) -> Result<Role> { /// /// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html pub fn create_webhook(channel_id: u64, map: &Value) -> Result<Webhook> { - let body = map.to_string(); - let response = request!( - Route::ChannelsIdWebhooks(channel_id), - post(body), - "/channels/{}/webhooks", - channel_id - ); + let response = request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Post, + route: Route::ChannelsIdWebhooks(channel_id), + url: api!("/channels/{}/webhooks", channel_id), + })?; serde_json::from_reader::<HyperResponse, Webhook>(response) .map_err(From::from) @@ -497,12 +503,13 @@ pub fn create_webhook(channel_id: u64, map: &Value) -> Result<Webhook> { /// Deletes a private channel or a channel in a guild. pub fn delete_channel(channel_id: u64) -> Result<Channel> { - let response = request!( - Route::ChannelsId(channel_id), - delete, - "/channels/{}", - channel_id - ); + let response = request(Request { + body: None, + headers: None, + route: Route::ChannelsId(channel_id), + method: Method::Delete, + url: api!("/channels/{}", channel_id), + })?; serde_json::from_reader::<HyperResponse, Channel>(response) .map_err(From::from) @@ -510,21 +517,24 @@ pub fn delete_channel(channel_id: u64) -> Result<Channel> { /// Deletes an emoji from a server. pub fn delete_emoji(guild_id: u64, emoji_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdEmojisId(guild_id), - delete, - "/guilds/{}/emojis/{}", - guild_id, - emoji_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::GuildsIdEmojisId(guild_id), + url: api!("/guilds/{}/emojis/{}", guild_id, emoji_id), + })?) } /// Deletes a guild, only if connected account owns it. pub fn delete_guild(guild_id: u64) -> Result<PartialGuild> { - let response = request!(Route::GuildsId(guild_id), delete, "/guilds/{}", guild_id); + let response = request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::GuildsId(guild_id), + url: api!("/guilds/{}", guild_id), + })?; serde_json::from_reader::<HyperResponse, PartialGuild>(response) .map_err(From::from) @@ -532,21 +542,24 @@ pub fn delete_guild(guild_id: u64) -> Result<PartialGuild> { /// Remvoes an integration from a guild. pub fn delete_guild_integration(guild_id: u64, integration_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdIntegrationsId(guild_id), - delete, - "/guilds/{}/integrations/{}", - guild_id, - integration_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::GuildsIdIntegrationsId(guild_id), + url: api!("/guilds/{}/integrations/{}", guild_id, integration_id), + })?) } /// Deletes an invite by code. pub fn delete_invite(code: &str) -> Result<Invite> { - let response = request!(Route::InvitesCode, delete, "/invites/{}", code); + let response = request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::InvitesCode, + url: api!("/invites/{}", code), + })?; serde_json::from_reader::<HyperResponse, Invite>(response) .map_err(From::from) @@ -555,31 +568,24 @@ pub fn delete_invite(code: &str) -> Result<Invite> { /// Deletes a message if created by us or we have /// specific permissions. pub fn delete_message(channel_id: u64, message_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdMessagesId(LightMethod::Delete, channel_id), - delete, - "/channels/{}/messages/{}", - channel_id, - message_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::ChannelsIdMessagesId(LightMethod::Delete, channel_id), + url: api!("/channels/{}/messages/{}", channel_id, message_id), + })?) } /// Deletes a bunch of messages, only works for bots. pub fn delete_messages(channel_id: u64, map: &Value) -> Result<()> { - let body = map.to_string(); - - verify( - 204, - request!( - Route::ChannelsIdMessagesBulkDelete(channel_id), - post(body), - "/channels/{}/messages/bulk-delete", - channel_id - ), - ) + verify(204, request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Delete, + route: Route::ChannelsIdMessagesBulkDelete(channel_id), + url: api!("/channels/{}/messages/bulk-delete", channel_id), + })?) } /// Deletes all of the [`Reaction`]s associated with a [`Message`]. @@ -600,30 +606,24 @@ pub fn delete_messages(channel_id: u64, map: &Value) -> Result<()> { /// [`Message`]: ../model/channel/struct.Message.html /// [`Reaction`]: ../model/channel/struct.Reaction.html pub fn delete_message_reactions(channel_id: u64, message_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdMessagesIdReactions(channel_id), - delete, - "/channels/{}/messages/{}/reactions", - channel_id, - message_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::ChannelsIdMessagesIdReactions(channel_id), + url: api!("/channels/{}/messages/{}/reactions", channel_id, message_id), + })?) } /// Deletes a permission override from a role or a member in a channel. pub fn delete_permission(channel_id: u64, target_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdPermissionsOverwriteId(channel_id), - delete, - "/channels/{}/permissions/{}", - channel_id, - target_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::ChannelsIdPermissionsOverwriteId(channel_id), + url: api!("/channels/{}/permissions/{}", channel_id, target_id), + })?) } /// Deletes a reaction from a message if owned by us or @@ -637,32 +637,30 @@ pub fn delete_reaction(channel_id: u64, .map(|uid| uid.to_string()) .unwrap_or_else(|| "@me".to_string()); - verify( - 204, - request!( - Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), - delete, + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), + url: api!( "/channels/{}/messages/{}/reactions/{}/{}", channel_id, message_id, reaction_type.as_data(), - user + user, ), - ) + })?) } /// Deletes a role from a server. Can't remove the default everyone role. pub fn delete_role(guild_id: u64, role_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdRolesId(guild_id), - delete, - "/guilds/{}/roles/{}", - guild_id, - role_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::GuildsIdRolesId(guild_id), + url: api!("/guilds/{}/roles/{}", guild_id, role_id), + })?) } /// Deletes a [`Webhook`] given its Id. @@ -688,15 +686,13 @@ pub fn delete_role(guild_id: u64, role_id: u64) -> Result<()> { /// [`Webhook`]: ../model/webhook/struct.Webhook.html /// [`delete_webhook_with_token`]: fn.delete_webhook_with_token.html pub fn delete_webhook(webhook_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::WebhooksId(webhook_id), - delete, - "/webhooks/{}", - webhook_id, - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::WebhooksId(webhook_id), + url: api!("/webhooks/{}", webhook_id), + })?) } /// Deletes a [`Webhook`] given its Id and unique token. @@ -718,24 +714,26 @@ pub fn delete_webhook(webhook_id: u64) -> Result<()> { /// /// [`Webhook`]: ../model/webhook/struct.Webhook.html pub fn delete_webhook_with_token(webhook_id: u64, token: &str) -> Result<()> { - verify( - 204, - retry(|| { - CLIENT - .delete(&format!(api!("/webhooks/{}/{}"), webhook_id, token)) - }).map_err(Error::Hyper)?, - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::None, + url: api!("/webhooks/{}/{}", webhook_id, token), + })?) } /// Changes channel information. pub fn edit_channel(channel_id: u64, map: &JsonMap) -> Result<GuildChannel> { - let body = serde_json::to_string(map)?; - let response = request!( - Route::ChannelsId(channel_id), - patch(body), - "/channels/{}", - channel_id - ); + let body = serde_json::to_vec(map)?; + + let response = request(Request { + body: Some(body), + headers: None, + method: Method::Patch, + route: Route::ChannelsId(channel_id), + url: api!("/channels/{}", channel_id), + })?; serde_json::from_reader::<HyperResponse, GuildChannel>(response) .map_err(From::from) @@ -743,14 +741,13 @@ pub fn edit_channel(channel_id: u64, map: &JsonMap) -> Result<GuildChannel> { /// Changes emoji information. pub fn edit_emoji(guild_id: u64, emoji_id: u64, map: &Value) -> Result<Emoji> { - let body = map.to_string(); - let response = request!( - Route::GuildsIdEmojisId(guild_id), - patch(body), - "/guilds/{}/emojis/{}", - guild_id, - emoji_id - ); + let response = request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Patch, + route: Route::GuildsIdEmojisId(guild_id), + url: api!("/guilds/{}/emojis/{}", guild_id, emoji_id), + })?; serde_json::from_reader::<HyperResponse, Emoji>(response) .map_err(From::from) @@ -758,13 +755,15 @@ pub fn edit_emoji(guild_id: u64, emoji_id: u64, map: &Value) -> Result<Emoji> { /// Changes guild information. pub fn edit_guild(guild_id: u64, map: &JsonMap) -> Result<PartialGuild> { - let body = serde_json::to_string(map)?; - let response = request!( - Route::GuildsId(guild_id), - patch(body), - "/guilds/{}", - guild_id - ); + let body = serde_json::to_vec(map)?; + + let response = request(Request { + body: Some(body), + headers: None, + method: Method::Patch, + route: Route::GuildsId(guild_id), + url: api!("/guilds/{}", guild_id), + })?; serde_json::from_reader::<HyperResponse, PartialGuild>(response) .map_err(From::from) @@ -773,30 +772,26 @@ pub fn edit_guild(guild_id: u64, map: &JsonMap) -> Result<PartialGuild> { /// 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_string(value)?; - - verify( - 204, - request!( - Route::GuildsIdChannels(guild_id), - patch(body), - "/guilds/{}/channels", - guild_id, - ), - ) + verify(204, request(Request { + body: Some(value.to_string().into_bytes()), + headers: None, + method: Method::Patch, + route: Route::GuildsIdChannels(guild_id), + url: api!("/guilds/{}/channels", guild_id), + })?) } /// Edits a [`Guild`]'s embed setting. /// /// [`Guild`]: ../model/guild/struct.Guild.html pub fn edit_guild_embed(guild_id: u64, map: &Value) -> Result<GuildEmbed> { - let body = map.to_string(); - let response = request!( - Route::GuildsIdEmbed(guild_id), - patch(body), - "/guilds/{}/embed", - guild_id - ); + let response = request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Patch, + route: Route::GuildsIdEmbed(guild_id), + url: api!("/guilds/{}/embed", guild_id), + })?; serde_json::from_reader::<HyperResponse, GuildEmbed>(response) .map_err(From::from) @@ -804,32 +799,28 @@ pub fn edit_guild_embed(guild_id: u64, map: &Value) -> Result<GuildEmbed> { /// Does specific actions to a member. pub fn edit_member(guild_id: u64, user_id: u64, map: &JsonMap) -> Result<()> { - let body = serde_json::to_string(map)?; - - verify( - 204, - request!( - Route::GuildsIdMembersId(guild_id), - patch(body), - "/guilds/{}/members/{}", - guild_id, - user_id - ), - ) + let body = serde_json::to_vec(map)?; + + verify(204, request(Request { + body: Some(body), + headers: None, + method: Method::Patch, + route: Route::GuildsIdMembersId(guild_id), + url: api!("/guilds/{}/members/{}", guild_id, user_id), + })?) } /// Edits a message by Id. /// /// **Note**: Only the author of a message can modify it. pub fn edit_message(channel_id: u64, message_id: u64, map: &Value) -> Result<Message> { - let body = map.to_string(); - let response = request!( - Route::ChannelsIdMessagesId(LightMethod::Any, channel_id), - patch(body), - "/channels/{}/messages/{}", - channel_id, - message_id - ); + let response = request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Patch, + route: Route::ChannelsIdMessagesId(LightMethod::Any, channel_id), + url: api!("/channels/{}/messages/{}", channel_id, message_id), + })?; serde_json::from_reader::<HyperResponse, Message>(response) .map_err(From::from) @@ -842,15 +833,14 @@ pub fn edit_message(channel_id: u64, message_id: u64, map: &Value) -> Result<Mes /// [`Guild`]: ../model/guild/struct.Guild.html pub fn edit_nickname(guild_id: u64, new_nickname: Option<&str>) -> Result<()> { let map = json!({ "nick": new_nickname }); - let body = map.to_string(); - let response = request!( - Route::GuildsIdMembersMeNick(guild_id), - patch(body), - "/guilds/{}/members/@me/nick", - guild_id - ); - verify(200, response) + verify(200, request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Patch, + route: Route::GuildsIdMembersMeNick(guild_id), + url: api!("/guilds/{}/members/@me/nick", guild_id), + })?) } /// Edits the current user's profile settings. @@ -866,8 +856,15 @@ pub fn edit_nickname(guild_id: u64, new_nickname: Option<&str>) -> Result<()> { /// change and when the token is internally changed to be invalid requests, as /// the token may be outdated. pub fn edit_profile(map: &JsonMap) -> Result<CurrentUser> { - let body = serde_json::to_string(map)?; - let response = request!(Route::UsersMe, patch(body), "/users/@me"); + let body = serde_json::to_vec(map)?; + + let response = request(Request { + body: Some(body), + headers: None, + method: Method::Patch, + route: Route::UsersMe, + url: api!("/users/@me").to_owned(), + })?; let mut value = serde_json::from_reader::<HyperResponse, Value>(response)?; @@ -885,14 +882,15 @@ pub fn edit_profile(map: &JsonMap) -> Result<CurrentUser> { /// Changes a role in a guild. pub fn edit_role(guild_id: u64, role_id: u64, map: &JsonMap) -> Result<Role> { - let body = serde_json::to_string(map)?; - let response = request!( - Route::GuildsIdRolesId(guild_id), - patch(body), - "/guilds/{}/roles/{}", - guild_id, - role_id - ); + let body = serde_json::to_vec(&map)?; + + let response = request(Request { + body: Some(body), + headers: None, + method: Method::Patch, + route: Route::GuildsIdRolesId(guild_id), + url: api!("/guilds/{}/roles/{}", guild_id, role_id), + })?; serde_json::from_reader::<HyperResponse, Role>(response) .map_err(From::from) @@ -904,13 +902,14 @@ pub fn edit_role_position(guild_id: u64, role_id: u64, position: u64) -> Result< "id": role_id, "position": position, }))?; - let response = request!( - Route::GuildsIdRolesId(guild_id), - patch(body), - "/guilds/{}/roles/{}", - guild_id, - role_id - ); + + let response = request(Request { + body: Some(body.into_bytes()), + headers: None, + method: Method::Patch, + route: Route::GuildsIdRolesId(guild_id), + url: api!("/guilds/{}/roles/{}", guild_id, role_id), + })?; serde_json::from_reader::<HyperResponse, Vec<Role>>(response) .map_err(From::from) @@ -956,13 +955,13 @@ pub fn edit_role_position(guild_id: u64, role_id: u64, position: u64) -> Result< // The tests are ignored, rather than no_run'd, due to rustdoc tests with // external crates being incredibly messy and misleading in the end user's view. pub fn edit_webhook(webhook_id: u64, map: &Value) -> Result<Webhook> { - let body = map.to_string(); - let response = request!( - Route::WebhooksId(webhook_id), - patch(body), - "/webhooks/{}", - webhook_id, - ); + let response = request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Patch, + route: Route::WebhooksId(webhook_id), + url: api!("/webhooks/{}", webhook_id), + })?; serde_json::from_reader::<HyperResponse, Webhook>(response) .map_err(From::from) @@ -995,13 +994,15 @@ pub fn edit_webhook(webhook_id: u64, map: &Value) -> Result<Webhook> { /// /// [`edit_webhook`]: fn.edit_webhook.html pub fn edit_webhook_with_token(webhook_id: u64, token: &str, map: &JsonMap) -> Result<Webhook> { - let body = serde_json::to_string(map)?; - - let response = retry(|| { - CLIENT - .patch(&format!(api!("/webhooks/{}/{}"), webhook_id, token)) - .body(&body) - }).map_err(Error::Hyper)?; + let body = serde_json::to_vec(map)?; + + let response = request(Request { + body: Some(body), + headers: None, + method: Method::Patch, + route: Route::None, + url: api!("/webhooks/{}/{}", webhook_id, token), + })?; serde_json::from_reader::<HyperResponse, Webhook>(response) .map_err(From::from) @@ -1072,21 +1073,20 @@ pub fn execute_webhook(webhook_id: u64, wait: bool, map: &JsonMap) -> Result<Option<Message>> { - let body = serde_json::to_string(map)?; - - let response = retry(|| { - CLIENT - .post(&format!( - api!("/webhooks/{}/{}?wait={}"), - webhook_id, - token, - wait - )) - .body(&body) - .header(ContentType( - Mime(TopLevel::Application, SubLevel::Json, vec![]), - )) - }).map_err(Error::Hyper)?; + let body = serde_json::to_vec(map)?; + + let mut headers = Headers::new(); + headers.set(ContentType( + Mime(TopLevel::Application, SubLevel::Json, vec![]), + )); + + let response = request(Request { + body: Some(body), + headers: Some(headers), + method: Method::Get, + route: Route::None, + url: api!("/webhooks/{}/{}?wait={}", webhook_id, token, wait), + })?; if response.status == StatusCode::NoContent { return Ok(None); @@ -1101,8 +1101,12 @@ pub fn execute_webhook(webhook_id: u64, /// /// Does not require authentication. pub fn get_active_maintenances() -> Result<Vec<Maintenance>> { - let response = retry(|| { - CLIENT.get(status!("/scheduled-maintenances/active.json")) + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::None, + url: status!("/scheduled-maintenances/active.json").to_owned(), })?; let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; @@ -1116,12 +1120,13 @@ pub fn get_active_maintenances() -> Result<Vec<Maintenance>> { /// Gets all the users that are banned in specific guild. pub fn get_bans(guild_id: u64) -> Result<Vec<Ban>> { - let response = request!( - Route::GuildsIdBans(guild_id), - get, - "/guilds/{}/bans", - guild_id - ); + let response = request(Request { + body: None, + headers: None, + route: Route::GuildsIdBans(guild_id), + method: Method::Get, + url: api!("/guilds/{}/bans", guild_id), + })?; serde_json::from_reader::<HyperResponse, Vec<Ban>>(response) .map_err(From::from) @@ -1153,13 +1158,13 @@ pub fn get_audit_logs(guild_id: u64, query_string.insert(0, '?'); } - let response = request!( - Route::GuildsIdAuditLogs(guild_id), - get, - "/guilds/{}/audit-logs{}", - guild_id, - query_string - ); + let response = request(Request { + body: None, + headers: None, + route: Route::GuildsIdAuditLogs(guild_id), + method: Method::Get, + url: api!("/guilds/{}/audit-logs{}", guild_id, query_string), + })?; serde_json::from_reader::<HyperResponse, AuditLogs>(response) .map_err(From::from) @@ -1167,7 +1172,13 @@ pub fn get_audit_logs(guild_id: u64, /// Gets current bot gateway. pub fn get_bot_gateway() -> Result<BotGateway> { - let response = request!(Route::GatewayBot, get, "/gateway/bot"); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::GatewayBot, + url: api!("/gateway/bot").to_owned(), + })?; serde_json::from_reader::<HyperResponse, BotGateway>(response) .map_err(From::from) @@ -1175,12 +1186,13 @@ pub fn get_bot_gateway() -> Result<BotGateway> { /// Gets all invites for a channel. pub fn get_channel_invites(channel_id: u64) -> Result<Vec<RichInvite>> { - let response = request!( - Route::ChannelsIdInvites(channel_id), - get, - "/channels/{}/invites", - channel_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::ChannelsIdInvites(channel_id), + url: api!("/channels/{}/invites", channel_id), + })?; serde_json::from_reader::<HyperResponse, Vec<RichInvite>>(response) .map_err(From::from) @@ -1205,12 +1217,13 @@ pub fn get_channel_invites(channel_id: u64) -> Result<Vec<RichInvite>> { /// /// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html pub fn get_channel_webhooks(channel_id: u64) -> Result<Vec<Webhook>> { - let response = request!( - Route::ChannelsIdWebhooks(channel_id), - get, - "/channels/{}/webhooks", - channel_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::ChannelsIdWebhooks(channel_id), + url: api!("/channels/{}/webhooks", channel_id), + })?; serde_json::from_reader::<HyperResponse, Vec<Webhook>>(response) .map_err(From::from) @@ -1218,12 +1231,13 @@ pub fn get_channel_webhooks(channel_id: u64) -> Result<Vec<Webhook>> { /// Gets channel information. pub fn get_channel(channel_id: u64) -> Result<Channel> { - let response = request!( - Route::ChannelsId(channel_id), - get, - "/channels/{}", - channel_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::ChannelsId(channel_id), + url: api!("/channels/{}", channel_id), + })?; serde_json::from_reader::<HyperResponse, Channel>(response) .map_err(From::from) @@ -1231,12 +1245,13 @@ pub fn get_channel(channel_id: u64) -> Result<Channel> { /// Gets all channels in a guild. pub fn get_channels(guild_id: u64) -> Result<Vec<GuildChannel>> { - let response = request!( - Route::ChannelsId(guild_id), - get, - "/guilds/{}/channels", - guild_id - ); + let response = request(Request { + body: None, + headers: None, + route: Route::ChannelsId(guild_id), + method: Method::Get, + url: api!("/guilds/{}/channels", guild_id), + })?; serde_json::from_reader::<HyperResponse, Vec<GuildChannel>>(response) .map_err(From::from) @@ -1246,7 +1261,13 @@ pub fn get_channels(guild_id: u64) -> Result<Vec<GuildChannel>> { /// /// **Note**: Only applications may use this endpoint. pub fn get_current_application_info() -> Result<CurrentApplicationInfo> { - let response = request!(Route::None, get, "/oauth2/applications/@me"); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::None, + url: api!("/oauth2/applications/@me").to_owned(), + })?; serde_json::from_reader::<HyperResponse, CurrentApplicationInfo>(response) .map_err(From::from) @@ -1254,7 +1275,13 @@ pub fn get_current_application_info() -> Result<CurrentApplicationInfo> { /// Gets information about the user we're connected with. pub fn get_current_user() -> Result<CurrentUser> { - let response = request!(Route::UsersMe, get, "/users/@me"); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::UsersMe, + url: api!("/users/@me").to_owned(), + })?; serde_json::from_reader::<HyperResponse, CurrentUser>(response) .map_err(From::from) @@ -1262,7 +1289,13 @@ pub fn get_current_user() -> Result<CurrentUser> { /// Gets current gateway. pub fn get_gateway() -> Result<Gateway> { - let response = request!(Route::Gateway, get, "/gateway"); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::Gateway, + url: api!("/gateway").to_owned(), + })?; serde_json::from_reader::<HyperResponse, Gateway>(response) .map_err(From::from) @@ -1270,7 +1303,13 @@ pub fn get_gateway() -> Result<Gateway> { /// Gets guild information. pub fn get_guild(guild_id: u64) -> Result<PartialGuild> { - let response = request!(Route::GuildsId(guild_id), get, "/guilds/{}", guild_id); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::GuildsId(guild_id), + url: api!("/guilds/{}", guild_id), + })?; serde_json::from_reader::<HyperResponse, PartialGuild>(response) .map_err(From::from) @@ -1278,12 +1317,13 @@ pub fn get_guild(guild_id: u64) -> Result<PartialGuild> { /// Gets a guild embed information. pub fn get_guild_embed(guild_id: u64) -> Result<GuildEmbed> { - let response = request!( - Route::GuildsIdEmbed(guild_id), - get, - "/guilds/{}/embeds", - guild_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::GuildsIdEmbed(guild_id), + url: api!("/guilds/{}/embeds", guild_id), + })?; serde_json::from_reader::<HyperResponse, GuildEmbed>(response) .map_err(From::from) @@ -1291,12 +1331,13 @@ pub fn get_guild_embed(guild_id: u64) -> Result<GuildEmbed> { /// Gets integrations that a guild has. pub fn get_guild_integrations(guild_id: u64) -> Result<Vec<Integration>> { - let response = request!( - Route::GuildsIdIntegrations(guild_id), - get, - "/guilds/{}/integrations", - guild_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::GuildsIdIntegrations(guild_id), + url: api!("/guilds/{}/integrations", guild_id), + })?; serde_json::from_reader::<HyperResponse, Vec<Integration>>(response) .map_err(From::from) @@ -1304,12 +1345,13 @@ pub fn get_guild_integrations(guild_id: u64) -> Result<Vec<Integration>> { /// Gets all invites to a guild. pub fn get_guild_invites(guild_id: u64) -> Result<Vec<RichInvite>> { - let response = request!( - Route::GuildsIdInvites(guild_id), - get, - "/guilds/{}/invites", - guild_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::GuildsIdInvites(guild_id), + url: api!("/guilds/{}/invites", guild_id), + })?; serde_json::from_reader::<HyperResponse, Vec<RichInvite>>(response) .map_err(From::from) @@ -1322,12 +1364,13 @@ pub fn get_guild_vanity_url(guild_id: u64) -> Result<String> { code: String, } - let response = request!( - Route::GuildsIdVanityUrl(guild_id), - get, - "/guilds/{}/vanity-url", - guild_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::GuildsIdVanityUrl(guild_id), + url: api!("/guilds/{}/vanity-url", guild_id), + })?; serde_json::from_reader::<HyperResponse, GuildVanityUrl>(response) .map(|x| x.code) @@ -1340,14 +1383,18 @@ pub fn get_guild_members(guild_id: u64, limit: Option<u64>, after: Option<u64>) -> Result<Vec<Member>> { - let response = request!( - Route::GuildsIdMembers(guild_id), - get, - "/guilds/{}/members?limit={}&after={}", - guild_id, - limit.unwrap_or(500), - after.unwrap_or(0) - ); + 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), + ), + })?; let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?; @@ -1366,13 +1413,13 @@ 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<GuildPrune> { - let body = map.to_string(); - let response = request!( - Route::GuildsIdPrune(guild_id), - get(body), - "/guilds/{}/prune", - guild_id - ); + let response = request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Get, + route: Route::GuildsIdPrune(guild_id), + url: api!("/guilds/{}/prune", guild_id), + })?; serde_json::from_reader::<HyperResponse, GuildPrune>(response) .map_err(From::from) @@ -1381,12 +1428,13 @@ pub fn get_guild_prune_count(guild_id: u64, map: &Value) -> Result<GuildPrune> { /// Gets regions that a guild can use. If a guild has the `VIP_REGIONS` feature /// enabled, then additional VIP-only regions are returned. pub fn get_guild_regions(guild_id: u64) -> Result<Vec<VoiceRegion>> { - let response = request!( - Route::GuildsIdRegions(guild_id), - get, - "/guilds/{}/regions", - guild_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::GuildsIdRegions(guild_id), + url: api!("/guilds/{}/regions", guild_id), + })?; serde_json::from_reader::<HyperResponse, Vec<VoiceRegion>>(response) .map_err(From::from) @@ -1396,12 +1444,13 @@ pub fn get_guild_regions(guild_id: u64) -> Result<Vec<VoiceRegion>> { /// /// [`Guild`]: ../model/guild/struct.Guild.html pub fn get_guild_roles(guild_id: u64) -> Result<Vec<Role>> { - let response = request!( - Route::GuildsIdRoles(guild_id), - get, - "/guilds/{}/roles", - guild_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::GuildsIdRoles(guild_id), + url: api!("/guilds/{}/roles", guild_id), + })?; serde_json::from_reader::<HyperResponse, Vec<Role>>(response) .map_err(From::from) @@ -1426,12 +1475,13 @@ pub fn get_guild_roles(guild_id: u64) -> Result<Vec<Role>> { /// /// [`Guild`]: ../model/guild/struct.Guild.html pub fn get_guild_webhooks(guild_id: u64) -> Result<Vec<Webhook>> { - let response = request!( - Route::GuildsIdWebhooks(guild_id), - get, - "/guilds/{}/webhooks", - guild_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::GuildsIdWebhooks(guild_id), + url: api!("/guilds/{}/webhooks", guild_id), + })?; serde_json::from_reader::<HyperResponse, Vec<Webhook>>(response) .map_err(From::from) @@ -1469,7 +1519,13 @@ pub fn get_guilds(target: &GuildPagination, limit: u64) -> Result<Vec<GuildInfo> }, } - let response = request!(Route::UsersMeGuilds, get, "{}", uri); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::UsersMeGuilds, + url: api!("{}", uri), + })?; serde_json::from_reader::<HyperResponse, Vec<GuildInfo>>(response) .map_err(From::from) @@ -1491,7 +1547,13 @@ pub fn get_invite(code: &str, stats: bool) -> Result<Invite> { uri.push_str("?with_counts=true"); } - let response = request!(Route::InvitesCode, get, "{}", uri); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::InvitesCode, + url: api!("{}", uri), + })?; serde_json::from_reader::<HyperResponse, Invite>(response) .map_err(From::from) @@ -1499,13 +1561,13 @@ pub fn get_invite(code: &str, stats: bool) -> Result<Invite> { /// Gets member of a guild. pub fn get_member(guild_id: u64, user_id: u64) -> Result<Member> { - let response = request!( - Route::GuildsIdMembersId(guild_id), - get, - "/guilds/{}/members/{}", - guild_id, - user_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::GuildsIdMembersId(guild_id), + url: api!("/guilds/{}/members/{}", guild_id, user_id), + })?; let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?; @@ -1518,13 +1580,13 @@ pub fn get_member(guild_id: u64, user_id: u64) -> Result<Member> { /// Gets a message by an Id, bots only. pub fn get_message(channel_id: u64, message_id: u64) -> Result<Message> { - let response = request!( - Route::ChannelsIdMessagesId(LightMethod::Any, channel_id), - get, - "/channels/{}/messages/{}", - channel_id, - message_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::ChannelsIdMessagesId(LightMethod::Any, channel_id), + url: api!("/channels/{}/messages/{}", channel_id, message_id), + })?; serde_json::from_reader::<HyperResponse, Message>(response) .map_err(From::from) @@ -1532,9 +1594,13 @@ pub fn get_message(channel_id: u64, message_id: u64) -> Result<Message> { /// Gets X messages from a channel. pub fn get_messages(channel_id: u64, query: &str) -> Result<Vec<Message>> { - let url = format!(api!("/channels/{}/messages{}"), channel_id, query); - - let response = request(Route::ChannelsIdMessages(channel_id), || CLIENT.get(&url))?; + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::ChannelsIdMessages(channel_id), + url: api!("/channels/{}/messages{}", channel_id, query), + })?; serde_json::from_reader::<HyperResponse, Vec<Message>>(response) .map_err(From::from) @@ -1542,12 +1608,13 @@ pub fn get_messages(channel_id: u64, query: &str) -> Result<Vec<Message>> { /// Gets all pins of a channel. pub fn get_pins(channel_id: u64) -> Result<Vec<Message>> { - let response = request!( - Route::ChannelsIdPins(channel_id), - get, - "/channels/{}/pins", - channel_id - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::ChannelsIdPins(channel_id), + url: api!("/channels/{}/pins", channel_id), + })?; serde_json::from_reader::<HyperResponse, Vec<Message>>(response) .map_err(From::from) @@ -1572,12 +1639,13 @@ pub fn get_reaction_users(channel_id: u64, write!(uri, "&after={}", user_id)?; } - let response = request!( - Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), - get, - "{}", - uri - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), + url: api!("{}", uri), + })?; serde_json::from_reader::<HyperResponse, Vec<User>>(response) .map_err(From::from) @@ -1587,7 +1655,13 @@ pub fn get_reaction_users(channel_id: u64, /// /// Does not require authentication. pub fn get_unresolved_incidents() -> Result<Vec<Incident>> { - let response = retry(|| CLIENT.get(status!("/incidents/unresolved.json")))?; + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::None, + url: status!("/incidents/unresolved.json").to_owned(), + })?; let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; @@ -1602,8 +1676,12 @@ pub fn get_unresolved_incidents() -> Result<Vec<Incident>> { /// /// Does not require authentication. pub fn get_upcoming_maintenances() -> Result<Vec<Maintenance>> { - let response = retry(|| { - CLIENT.get(status!("/scheduled-maintenances/upcoming.json")) + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::None, + url: status!("/scheduled-maintenances/upcoming.json").to_owned(), })?; let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; @@ -1617,7 +1695,13 @@ pub fn get_upcoming_maintenances() -> Result<Vec<Maintenance>> { /// Gets a user by Id. pub fn get_user(user_id: u64) -> Result<User> { - let response = request!(Route::UsersId, get, "/users/{}", user_id); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::UsersId, + url: api!("/users/{}", user_id), + })?; serde_json::from_reader::<HyperResponse, User>(response) .map_err(From::from) @@ -1625,7 +1709,13 @@ pub fn get_user(user_id: u64) -> Result<User> { /// Gets our DM channels. pub fn get_user_dm_channels() -> Result<Vec<PrivateChannel>> { - let response = request!(Route::UsersMeChannels, get, "/users/@me/channels"); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::UsersMeChannels, + url: api!("/users/@me/channels").to_owned(), + })?; serde_json::from_reader::<HyperResponse, Vec<PrivateChannel>>(response) .map_err(From::from) @@ -1633,7 +1723,13 @@ pub fn get_user_dm_channels() -> Result<Vec<PrivateChannel>> { /// Gets all voice regions. pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> { - let response = request!(Route::VoiceRegions, get, "/voice/regions"); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::VoiceRegions, + url: api!("/voice/regions").to_owned(), + })?; serde_json::from_reader::<HyperResponse, Vec<VoiceRegion>>(response) .map_err(From::from) @@ -1657,12 +1753,13 @@ pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> { /// /// [`get_webhook_with_token`]: fn.get_webhook_with_token.html pub fn get_webhook(webhook_id: u64) -> Result<Webhook> { - let response = request!( - Route::WebhooksId(webhook_id), - get, - "/webhooks/{}", - webhook_id, - ); + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::WebhooksId(webhook_id), + url: api!("/webhooks/{}", webhook_id), + })?; serde_json::from_reader::<HyperResponse, Webhook>(response) .map_err(From::from) @@ -1686,9 +1783,13 @@ pub fn get_webhook(webhook_id: u64) -> Result<Webhook> { /// .expect("Error getting webhook"); /// ``` pub fn get_webhook_with_token(webhook_id: u64, token: &str) -> Result<Webhook> { - let response = retry(|| { - CLIENT.get(&format!(api!("/webhooks/{}/{}"), webhook_id, token)) - }).map_err(Error::Hyper)?; + let response = request(Request { + body: None, + headers: None, + method: Method::Get, + route: Route::None, + url: api!("/webhooks/{}/{}", webhook_id, token), + })?; serde_json::from_reader::<HyperResponse, Webhook>(response) .map_err(From::from) @@ -1696,21 +1797,24 @@ pub fn get_webhook_with_token(webhook_id: u64, token: &str) -> Result<Webhook> { /// Kicks a member from a guild. pub fn kick_member(guild_id: u64, user_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdMembersId(guild_id), - delete, - "/guilds/{}/members/{}", - guild_id, - user_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::GuildsIdMembersId(guild_id), + url: api!("/guilds/{}/members/{}", guild_id, user_id), + })?) } /// Leaves a group DM. pub fn leave_group(guild_id: u64) -> Result<Group> { - let response = request!(Route::None, delete, "/channels/{}", guild_id); + let response = request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::None, + url: api!("/channels/{}", guild_id), + })?; serde_json::from_reader::<HyperResponse, Group>(response) .map_err(From::from) @@ -1718,29 +1822,24 @@ pub fn leave_group(guild_id: u64) -> Result<Group> { /// Leaves a guild. pub fn leave_guild(guild_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::UsersMeGuildsId, - delete, - "/users/@me/guilds/{}", - guild_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::UsersMeGuildsId, + url: api!("/users/@me/guilds/{}", guild_id), + })?) } /// Deletes a user from group DM. pub fn remove_group_recipient(group_id: u64, user_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::None, - delete, - "/channels/{}/recipients/{}", - group_id, - user_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::None, + url: api!("/channels/{}/recipients/{}", group_id, user_id), + })?) } /// Sends file(s) to a channel. @@ -1754,7 +1853,7 @@ pub fn remove_group_recipient(group_id: u64, user_id: u64) -> Result<()> { /// [`HttpError::InvalidRequest`]: enum.HttpError.html#variant.InvalidRequest pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, map: JsonMap) -> Result<Message> where T: Into<AttachmentType<'a>> { - let uri = format!(api!("/channels/{}/messages"), channel_id); + let uri = api!("/channels/{}/messages", channel_id); let url = match Url::parse(&uri) { Ok(url) => url, Err(_) => return Err(Error::Url(uri)), @@ -1762,7 +1861,7 @@ pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, m let tc = NativeTlsClient::new()?; let connector = HttpsConnector::new(tc); - let mut request = Request::with_connector(Method::Post, url, &connector)?; + let mut request = HyperRequest::with_connector(Method::Post, url, &connector)?; request .headers_mut() .set(header::Authorization(TOKEN.lock().clone())); @@ -1817,13 +1916,13 @@ pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, m /// Sends a message to a channel. pub fn send_message(channel_id: u64, map: &Value) -> Result<Message> { - let body = map.to_string(); - let response = request!( - Route::ChannelsIdMessages(channel_id), - post(body), - "/channels/{}/messages", - channel_id - ); + let response = request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Post, + route: Route::ChannelsIdMessages(channel_id), + url: api!("/channels/{}/messages", channel_id), + })?; serde_json::from_reader::<HyperResponse, Message>(response) .map_err(From::from) @@ -1831,30 +1930,24 @@ pub fn send_message(channel_id: u64, map: &Value) -> Result<Message> { /// Pins a message in a channel. pub fn pin_message(channel_id: u64, message_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdPinsMessageId(channel_id), - put, - "/channels/{}/pins/{}", - channel_id, - message_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Put, + route: Route::ChannelsIdPinsMessageId(channel_id), + url: api!("/channels/{}/pins/{}", channel_id, message_id), + })?) } /// Unbans a user from a guild. pub fn remove_ban(guild_id: u64, user_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdBansUserId(guild_id), - delete, - "/guilds/{}/bans/{}", - guild_id, - user_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::GuildsIdBansUserId(guild_id), + url: api!("/guilds/{}/bans/{}", guild_id, user_id), + })?) } /// Deletes a single [`Role`] from a [`Member`] in a [`Guild`]. @@ -1867,28 +1960,24 @@ pub fn remove_ban(guild_id: u64, user_id: u64) -> Result<()> { /// [`Role`]: ../model/guild/struct.Role.html /// [Manage Roles]: ../model/permissions/constant.MANAGE_ROLES.html pub fn remove_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdMembersIdRolesId(guild_id), - delete, - "/guilds/{}/members/{}/roles/{}", - guild_id, - user_id, - role_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::GuildsIdMembersIdRolesId(guild_id), + url: api!("/guilds/{}/members/{}/roles/{}", 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<GuildPrune> { - let body = map.to_string(); - let response = request!( - Route::GuildsIdPrune(guild_id), - post(body), - "/guilds/{}/prune", - guild_id - ); + let response = request(Request { + body: Some(map.to_string().into_bytes()), + headers: None, + method: Method::Post, + route: Route::GuildsIdPrune(guild_id), + url: api!("/guilds/{}/prune", guild_id), + })?; serde_json::from_reader::<HyperResponse, GuildPrune>(response) .map_err(From::from) @@ -1896,38 +1985,28 @@ pub fn start_guild_prune(guild_id: u64, map: &Value) -> Result<GuildPrune> { /// Starts syncing an integration with a guild. pub fn start_integration_sync(guild_id: u64, integration_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdIntegrationsIdSync(guild_id), - post, - "/guilds/{}/integrations/{}/sync", - guild_id, - integration_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Post, + route: Route::GuildsIdIntegrationsIdSync(guild_id), + url: api!("/guilds/{}/integrations/{}/sync", guild_id, integration_id), + })?) } /// Unpins a message from a channel. pub fn unpin_message(channel_id: u64, message_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdPinsMessageId(channel_id), - delete, - "/channels/{}/pins/{}", - channel_id, - message_id - ), - ) + verify(204, request(Request { + body: None, + headers: None, + method: Method::Delete, + route: Route::ChannelsIdPinsMessageId(channel_id), + url: api!("/channels/{}/pins/{}", channel_id, message_id), + })?) } -fn request<'a, F>(route: Route, f: F) -> Result<HyperResponse> - where F: Fn() -> RequestBuilder<'a> { - let response = ratelimiting::perform(route, || { - f().header(header::Authorization(TOKEN.lock().clone())) - .header(header::ContentType::json()) - })?; +fn request(req: Request) -> Result<HyperResponse> { + let response = ratelimiting::perform(req)?; if response.status.class() == StatusClass::Success { Ok(response) @@ -1936,17 +2015,33 @@ fn request<'a, F>(route: Route, f: F) -> Result<HyperResponse> } } -pub(crate) fn retry<'a, F>(f: F) -> HyperResult<HyperResponse> - where F: Fn() -> RequestBuilder<'a> { - let req = || { - f().header(header::UserAgent(constants::USER_AGENT.to_string())) - .send() - }; +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())); + } - match req() { - Err(HyperError::Io(ref io)) if io.kind() == IoErrorKind::ConnectionAborted => req(), - other => other, + if let Some(headers) = req.headers.clone() { + builder = builder.headers(headers); } + + builder +} + +fn retry<'a>(req: &Request) -> HyperResult<HyperResponse> { + // 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() { + Err(HyperError::Io(ref io)) + if io.kind() == IoErrorKind::ConnectionAborted => continue, + other => return other, + } + } + + build_req(req).send() } fn verify(expected: u16, response: HyperResponse) -> Result<()> { diff --git a/src/http/ratelimiting.rs b/src/http/ratelimiting.rs index b8152d1..1c31c9f 100644 --- a/src/http/ratelimiting.rs +++ b/src/http/ratelimiting.rs @@ -41,7 +41,7 @@ #![allow(zero_ptr)] use chrono::{DateTime, Utc}; -use hyper::client::{RequestBuilder, Response}; +use hyper::client::Response; use hyper::header::Headers; use hyper::status::StatusCode; use internal::prelude::*; @@ -54,7 +54,7 @@ use std::{ thread, i64 }; -use super::{HttpError, LightMethod}; +use super::{HttpError, LightMethod, Request}; /// Refer to [`offset`]. /// @@ -361,8 +361,7 @@ pub enum Route { None, } -pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> - where F: Fn() -> RequestBuilder<'a> { +pub(super) fn perform(req: Request) -> Result<Response> { loop { // This will block if another thread already has the global // unlocked already (due to receiving an x-ratelimit-global). @@ -378,7 +377,7 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> // - then, perform the request let bucket = Arc::clone(ROUTES .lock() - .entry(route) + .entry(req.route) .or_insert_with(|| { Arc::new(Mutex::new(RateLimit { limit: i64::MAX, @@ -388,9 +387,9 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> })); let mut lock = bucket.lock(); - lock.pre_hook(&route); + lock.pre_hook(&req.route); - let response = super::retry(&f)?; + let response = super::retry(&req)?; // Check if an offset has been calculated yet to determine the time // difference from Discord can the client. @@ -416,7 +415,7 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> // 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 route == Route::None { + if req.route == Route::None { return Ok(response); } else { let redo = if response.headers.get_raw("x-ratelimit-global").is_some() { @@ -424,7 +423,7 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> Ok( if let Some(retry_after) = parse_header(&response.headers, "retry-after")? { - debug!("Ratelimited on route {:?} for {:?}ms", route, retry_after); + debug!("Ratelimited on route {:?} for {:?}ms", req.route, retry_after); thread::sleep(Duration::from_millis(retry_after as u64)); true @@ -433,7 +432,7 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> }, ) } else { - lock.post_hook(&response, &route) + lock.post_hook(&response, &req.route) }; if !redo.unwrap_or(true) { |