diff --git a/http/Cargo.toml b/http/Cargo.toml index b2a1e3fb9f1..0bde83f2e70 100644 --- a/http/Cargo.toml +++ b/http/Cargo.toml @@ -13,6 +13,7 @@ repository = "https://github.com/twilight-rs/twilight.git" version = "0.1.0" [dependencies] +bytes = { default-features = false, version = "0.5" } futures = "0.3" twilight-model = { path = "../model" } log = "0.4" diff --git a/http/src/client/mod.rs b/http/src/client/mod.rs index c7b68aeee2d..4f6f4d6ba5f 100644 --- a/http/src/client/mod.rs +++ b/http/src/client/mod.rs @@ -12,6 +12,7 @@ use crate::{ Request, }, }; +use bytes::Bytes; use log::{debug, warn}; use reqwest::{ header::HeaderValue, Body, Client as ReqwestClient, ClientBuilder as ReqwestClientBuilder, @@ -844,6 +845,14 @@ impl Client { }) } + pub(crate) async fn request_bytes(&self, request: Request) -> Result { + let resp = self.make_request(request).await?; + + resp.bytes() + .await + .map_err(|source| Error::ChunkingResponse { source }) + } + pub async fn verify(&self, request: Request) -> Result<()> { self.make_request(request).await?; diff --git a/http/src/request/channel/get_channel.rs b/http/src/request/channel/get_channel.rs index ba78c672b57..fa62c8ee9cb 100644 --- a/http/src/request/channel/get_channel.rs +++ b/http/src/request/channel/get_channel.rs @@ -3,7 +3,7 @@ use twilight_model::{channel::Channel, id::ChannelId}; pub struct GetChannel<'a> { channel_id: ChannelId, - fut: Option>>, + fut: Option>, http: &'a Client, } @@ -17,14 +17,15 @@ impl<'a> GetChannel<'a> { } fn start(&mut self) -> Result<()> { - self.fut.replace(Box::pin(self.http.request(Request::from( - Route::GetChannel { - channel_id: self.channel_id.0, - }, - )))); + self.fut + .replace(Box::pin(self.http.request_bytes(Request::from( + Route::GetChannel { + channel_id: self.channel_id.0, + }, + )))); Ok(()) } } -poll_req!(GetChannel<'_>, Option); +poll_req!(opt, GetChannel<'_>, Channel); diff --git a/http/src/request/channel/invite/get_invite.rs b/http/src/request/channel/invite/get_invite.rs index b18d025cfdb..9b06fa47fc1 100644 --- a/http/src/request/channel/invite/get_invite.rs +++ b/http/src/request/channel/invite/get_invite.rs @@ -9,7 +9,7 @@ struct GetInviteFields { pub struct GetInvite<'a> { code: String, fields: GetInviteFields, - fut: Option>>, + fut: Option>, http: &'a Client, } @@ -30,15 +30,16 @@ impl<'a> GetInvite<'a> { } fn start(&mut self) -> Result<()> { - self.fut.replace(Box::pin(self.http.request(Request::from( - Route::GetInvite { - code: self.code.clone(), - with_counts: self.fields.with_counts, - }, - )))); + self.fut + .replace(Box::pin(self.http.request_bytes(Request::from( + Route::GetInvite { + code: self.code.clone(), + with_counts: self.fields.with_counts, + }, + )))); Ok(()) } } -poll_req!(GetInvite<'_>, Option); +poll_req!(opt, GetInvite<'_>, Invite); diff --git a/http/src/request/channel/message/get_message.rs b/http/src/request/channel/message/get_message.rs index 37cd6b6e659..516de8d9c28 100644 --- a/http/src/request/channel/message/get_message.rs +++ b/http/src/request/channel/message/get_message.rs @@ -6,7 +6,7 @@ use twilight_model::{ pub struct GetMessage<'a> { channel_id: ChannelId, - fut: Option>>, + fut: Option>, http: &'a Client, message_id: MessageId, } @@ -22,15 +22,16 @@ impl<'a> GetMessage<'a> { } fn start(&mut self) -> Result<()> { - self.fut.replace(Box::pin(self.http.request(Request::from( - Route::GetMessage { - channel_id: self.channel_id.0, - message_id: self.message_id.0, - }, - )))); + self.fut + .replace(Box::pin(self.http.request_bytes(Request::from( + Route::GetMessage { + channel_id: self.channel_id.0, + message_id: self.message_id.0, + }, + )))); Ok(()) } } -poll_req!(GetMessage<'_>, Option); +poll_req!(opt, GetMessage<'_>, Message); diff --git a/http/src/request/channel/webhook/get_webhook.rs b/http/src/request/channel/webhook/get_webhook.rs index 967fe1eaeab..fbdd4df7cfe 100644 --- a/http/src/request/channel/webhook/get_webhook.rs +++ b/http/src/request/channel/webhook/get_webhook.rs @@ -8,7 +8,7 @@ struct GetWebhookFields { pub struct GetWebhook<'a> { fields: GetWebhookFields, - fut: Option>>, + fut: Option>, http: &'a Client, id: WebhookId, } @@ -30,15 +30,16 @@ impl<'a> GetWebhook<'a> { } fn start(&mut self) -> Result<()> { - self.fut.replace(Box::pin(self.http.request(Request::from( - Route::GetWebhook { - token: self.fields.token.clone(), - webhook_id: self.id.0, - }, - )))); + self.fut + .replace(Box::pin(self.http.request_bytes(Request::from( + Route::GetWebhook { + token: self.fields.token.clone(), + webhook_id: self.id.0, + }, + )))); Ok(()) } } -poll_req!(GetWebhook<'_>, Option); +poll_req!(opt, GetWebhook<'_>, Webhook); diff --git a/http/src/request/guild/ban/get_ban.rs b/http/src/request/guild/ban/get_ban.rs index a639c28b181..a5a76aae08c 100644 --- a/http/src/request/guild/ban/get_ban.rs +++ b/http/src/request/guild/ban/get_ban.rs @@ -5,7 +5,7 @@ use twilight_model::{ }; pub struct GetBan<'a> { - fut: Option>>, + fut: Option>, guild_id: GuildId, http: &'a Client, user_id: UserId, @@ -23,13 +23,15 @@ impl<'a> GetBan<'a> { fn start(&mut self) -> Result<()> { self.fut - .replace(Box::pin(self.http.request(Request::from(Route::GetBan { - guild_id: self.guild_id.0, - user_id: self.user_id.0, - })))); + .replace(Box::pin(self.http.request_bytes(Request::from( + Route::GetBan { + guild_id: self.guild_id.0, + user_id: self.user_id.0, + }, + )))); Ok(()) } } -poll_req!(GetBan<'_>, Option); +poll_req!(opt, GetBan<'_>, Ban); diff --git a/http/src/request/guild/emoji/get_emoji.rs b/http/src/request/guild/emoji/get_emoji.rs index b21356a2dcf..1005ec12b98 100644 --- a/http/src/request/guild/emoji/get_emoji.rs +++ b/http/src/request/guild/emoji/get_emoji.rs @@ -6,7 +6,7 @@ use twilight_model::{ pub struct GetEmoji<'a> { emoji_id: EmojiId, - fut: Option>>, + fut: Option>, guild_id: GuildId, http: &'a Client, } @@ -22,15 +22,16 @@ impl<'a> GetEmoji<'a> { } fn start(&mut self) -> Result<()> { - self.fut.replace(Box::pin(self.http.request(Request::from( - Route::GetEmoji { - emoji_id: self.emoji_id.0, - guild_id: self.guild_id.0, - }, - )))); + self.fut + .replace(Box::pin(self.http.request_bytes(Request::from( + Route::GetEmoji { + emoji_id: self.emoji_id.0, + guild_id: self.guild_id.0, + }, + )))); Ok(()) } } -poll_req!(GetEmoji<'_>, Option); +poll_req!(opt, GetEmoji<'_>, Emoji); diff --git a/http/src/request/guild/get_guild_vanity_url.rs b/http/src/request/guild/get_guild_vanity_url.rs index 11ec25f81d4..df47eaba0a2 100644 --- a/http/src/request/guild/get_guild_vanity_url.rs +++ b/http/src/request/guild/get_guild_vanity_url.rs @@ -1,6 +1,10 @@ -use crate::request::prelude::*; -use futures::TryFutureExt; +use crate::{request::prelude::*, Error}; use serde::Deserialize; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; use twilight_model::id::GuildId; #[derive(Deserialize)] @@ -9,7 +13,7 @@ struct VanityUrl { } pub struct GetGuildVanityUrl<'a> { - fut: Option>, + fut: Option>, guild_id: GuildId, http: &'a Client, } @@ -26,14 +30,45 @@ impl<'a> GetGuildVanityUrl<'a> { fn start(&mut self) -> Result<()> { let fut = self .http - .request::(Request::from(Route::GetGuildVanityUrl { + .request_bytes(Request::from(Route::GetGuildVanityUrl { guild_id: self.guild_id.0, - })) - .map_ok(|url| url.code); + })); self.fut.replace(Box::pin(fut)); Ok(()) } } -poll_req!(GetGuildVanityUrl<'_>, String); +impl Future for GetGuildVanityUrl<'_> { + type Output = Result>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + if let Some(fut) = self.as_mut().fut.as_mut() { + let bytes = match fut.as_mut().poll(cx) { + Poll::Ready(Ok(bytes)) => bytes, + Poll::Ready(Err(crate::Error::Response { status, .. })) + if status == reqwest::StatusCode::NOT_FOUND => + { + return Poll::Ready(Ok(None)); + } + Poll::Ready(Err(why)) => return Poll::Ready(Err(why)), + Poll::Pending => return Poll::Pending, + }; + + let vanity_url = serde_json::from_slice::(&bytes).map_err(|source| { + Error::Parsing { + body: bytes.to_vec(), + source, + } + })?; + + return Poll::Ready(Ok(Some(vanity_url.code))); + } + + if let Err(why) = self.as_mut().start() { + return Poll::Ready(Err(why)); + } + } + } +} diff --git a/http/src/request/guild/get_guild_widget.rs b/http/src/request/guild/get_guild_widget.rs index f470ce9d6c2..f2f0d924ea1 100644 --- a/http/src/request/guild/get_guild_widget.rs +++ b/http/src/request/guild/get_guild_widget.rs @@ -2,7 +2,7 @@ use crate::request::prelude::*; use twilight_model::{guild::GuildWidget, id::GuildId}; pub struct GetGuildWidget<'a> { - fut: Option>>, + fut: Option>, guild_id: GuildId, http: &'a Client, } @@ -17,14 +17,15 @@ impl<'a> GetGuildWidget<'a> { } fn start(&mut self) -> Result<()> { - self.fut.replace(Box::pin(self.http.request(Request::from( - Route::GetGuildWidget { - guild_id: self.guild_id.0, - }, - )))); + self.fut + .replace(Box::pin(self.http.request_bytes(Request::from( + Route::GetGuildWidget { + guild_id: self.guild_id.0, + }, + )))); Ok(()) } } -poll_req!(GetGuildWidget<'_>, Option); +poll_req!(opt, GetGuildWidget<'_>, GuildWidget); diff --git a/http/src/request/guild/member/get_member.rs b/http/src/request/guild/member/get_member.rs index 8f0b9ba8c31..8250d4a4942 100644 --- a/http/src/request/guild/member/get_member.rs +++ b/http/src/request/guild/member/get_member.rs @@ -5,7 +5,7 @@ use twilight_model::{ }; pub struct GetMember<'a> { - fut: Option>>, + fut: Option>, guild_id: GuildId, http: &'a Client, user_id: UserId, @@ -22,15 +22,16 @@ impl<'a> GetMember<'a> { } fn start(&mut self) -> Result<()> { - self.fut.replace(Box::pin(self.http.request(Request::from( - Route::GetMember { - guild_id: self.guild_id.0, - user_id: self.user_id.0, - }, - )))); + self.fut + .replace(Box::pin(self.http.request_bytes(Request::from( + Route::GetMember { + guild_id: self.guild_id.0, + user_id: self.user_id.0, + }, + )))); Ok(()) } } -poll_req!(GetMember<'_>, Option); +poll_req!(opt, GetMember<'_>, Member); diff --git a/http/src/request/mod.rs b/http/src/request/mod.rs index 38dae121ac2..dd87611b47b 100644 --- a/http/src/request/mod.rs +++ b/http/src/request/mod.rs @@ -19,6 +19,45 @@ macro_rules! poll_req { } } }; + + (opt, $ty: ty, $ret: ty) => { + impl std::future::Future for $ty { + type Output = $crate::error::Result>; + + fn poll( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> ::std::task::Poll { + use std::task::Poll; + + loop { + if let Some(fut) = self.as_mut().fut.as_mut() { + let bytes = match fut.as_mut().poll(cx) { + Poll::Ready(Ok(bytes)) => bytes, + Poll::Ready(Err(crate::Error::Response { status, .. })) + if status == reqwest::StatusCode::NOT_FOUND => + { + return Poll::Ready(Ok(None)); + } + Poll::Ready(Err(why)) => return Poll::Ready(Err(why)), + Poll::Pending => return Poll::Pending, + }; + + return Poll::Ready(serde_json::from_slice(&bytes).map(Some).map_err( + |source| crate::Error::Parsing { + body: bytes.to_vec(), + source, + }, + )); + } + + if let Err(why) = self.as_mut().start() { + return Poll::Ready(Err(why)); + } + } + } + } + }; } pub mod channel; @@ -40,7 +79,7 @@ use crate::{ error::{Error, Result}, routing::{Path, Route}, }; - +use bytes::Bytes; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use reqwest::{ header::{HeaderMap, HeaderName, HeaderValue}, @@ -51,6 +90,7 @@ use reqwest::{ use std::{borrow::Cow, future::Future, pin::Pin}; type Pending<'a, T> = Pin> + Send + 'a>>; +type PendingOption<'a> = Pin> + Send + 'a>>; #[derive(Debug)] pub struct Request { diff --git a/http/src/request/prelude.rs b/http/src/request/prelude.rs index 82b8d9daab1..9a38dd3efc9 100644 --- a/http/src/request/prelude.rs +++ b/http/src/request/prelude.rs @@ -1,4 +1,4 @@ -pub(super) use super::{audit_header, validate, Pending, Request}; +pub(super) use super::{audit_header, validate, Pending, PendingOption, Request}; pub use super::{ channel::{invite::*, message::*, reaction::*, webhook::*, *}, get_gateway::GetGateway, diff --git a/http/src/request/user/get_user.rs b/http/src/request/user/get_user.rs index 2b13495360e..c8d36a17501 100644 --- a/http/src/request/user/get_user.rs +++ b/http/src/request/user/get_user.rs @@ -2,7 +2,7 @@ use crate::request::prelude::*; use twilight_model::user::User; pub struct GetUser<'a> { - fut: Option>>, + fut: Option>, http: &'a Client, target_user: String, } @@ -18,12 +18,14 @@ impl<'a> GetUser<'a> { fn start(&mut self) -> Result<()> { self.fut - .replace(Box::pin(self.http.request(Request::from(Route::GetUser { - target_user: self.target_user.clone(), - })))); + .replace(Box::pin(self.http.request_bytes(Request::from( + Route::GetUser { + target_user: self.target_user.clone(), + }, + )))); Ok(()) } } -poll_req!(GetUser<'_>, Option); +poll_req!(opt, GetUser<'_>, User);