From ec7b0fbcd2f3273d51e15c5c1b34b45cb5a4f46f Mon Sep 17 00:00:00 2001 From: DankDumpster Date: Thu, 14 Jan 2021 08:19:30 +0100 Subject: added reqwest support --- src/bridge/hyper.rs | 8 +-- src/bridge/mod.rs | 1 + src/bridge/reqwest.rs | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/constants.rs | 6 +-- src/error.rs | 12 ++++- src/lib.rs | 12 ++--- src/model.rs | 26 +++++----- src/utils.rs | 8 ++- 8 files changed, 178 insertions(+), 34 deletions(-) create mode 100644 src/bridge/reqwest.rs (limited to 'src') diff --git a/src/bridge/hyper.rs b/src/bridge/hyper.rs index 4a7eba2..093b234 100644 --- a/src/bridge/hyper.rs +++ b/src/bridge/hyper.rs @@ -1,16 +1,16 @@ //! Bridged support for the `hyper` HTTP client. -use hyper::client::{Body, Client as HyperClient}; +use hyper::client::{Client as HyperClient, Body}; use hyper::header::ContentType; use serde_json; use serde_urlencoded; -use ::constants::BASE_TOKEN_URI; -use ::model::{ +use crate::constants::BASE_TOKEN_URI; +use crate::model::{ AccessTokenExchangeRequest, AccessTokenResponse, RefreshTokenRequest, }; -use ::Result; +use crate::Result; /// A trait used that implements methods for interacting with Discord's OAuth2 /// API on Hyper's client. diff --git a/src/bridge/mod.rs b/src/bridge/mod.rs index 92be3da..d635011 100644 --- a/src/bridge/mod.rs +++ b/src/bridge/mod.rs @@ -4,3 +4,4 @@ //! functions that create one-off clients for ease of use. pub mod hyper; +pub mod reqwest; \ No newline at end of file diff --git a/src/bridge/reqwest.rs b/src/bridge/reqwest.rs new file mode 100644 index 0000000..30943b5 --- /dev/null +++ b/src/bridge/reqwest.rs @@ -0,0 +1,139 @@ +//! Bridged support for the `reqwest` HTTP client. +use crate::constants::BASE_TOKEN_URI; +use crate::model::{AccessTokenExchangeRequest, AccessTokenResponse, RefreshTokenRequest}; +use crate::{Error, Result}; +use reqwest::blocking::Client as ReqwestClient; +use reqwest::header::CONTENT_TYPE; +use serde_json; +use serde_json::Error as JsonError; +use serde_urlencoded; + +// TODO Update this +/// A trait used that implements methods for interacting with Discord's OAuth2 +/// API on Reqwest's client. +/// +/// # Examples +/// +/// Bringing in the trait and creating a client. Since the trait is in scope, +/// the instance of hyper's Client will have those methods available: +/// +/// ```rust,no_run +/// extern crate hyper; +/// extern crate serenity_oauth; +/// +/// # fn main() { +/// use hyper::Client; +/// +/// let client = Client::new(); +/// +/// // At this point, the methods defined by the trait are not in scope. By +/// // using the trait, they will be. +/// use serenity_oauth::DiscordOAuthHyperRequester; +/// +/// // The methods defined by `DiscordOAuthHyperRequester` are now in scope and +/// // implemented on the instance of hyper's `Client`. +/// # } +/// ``` +/// +/// For examples of how to use the trait with the Client, refer to the trait's +/// methods. +pub trait DiscordOAuthReqwestRequester { + /// Exchanges a code for the user's access token. + /// + /// # Examples + /// + /// Exchange a code for an access token: + /// + /// ```rust,no_run + /// extern crate hyper; + /// extern crate serenity_oauth; + /// + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// use hyper::Client; + /// use serenity_oauth::model::AccessTokenExchangeRequest; + /// use serenity_oauth::DiscordOAuthHyperRequester; + /// + /// let request_data = AccessTokenExchangeRequest::new( + /// 249608697955745802, + /// "dd99opUAgs7SQEtk2kdRrTMU5zagR2a4", + /// "user code here", + /// "https://myapplication.website", + /// ); + /// + /// let client = Client::new(); + /// let response = client.exchange_code(&request_data)?; + /// + /// println!("Access token: {}", response.access_token); + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + fn exchange_code(&self, request: &AccessTokenExchangeRequest) -> Result; + + /// Exchanges a refresh token, returning a new refresh token and fresh + /// access token. + /// + /// # Examples + /// + /// Exchange a refresh token: + /// + /// ```rust,no_run + /// extern crate hyper; + /// extern crate serenity_oauth; + /// + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box> { + /// use hyper::Client; + /// use serenity_oauth::model::RefreshTokenRequest; + /// use serenity_oauth::DiscordOAuthHyperRequester; + /// + /// let request_data = RefreshTokenRequest::new( + /// 249608697955745802, + /// "dd99opUAgs7SQEtk2kdRrTMU5zagR2a4", + /// "user code here", + /// "https://myapplication.website", + /// ); + /// + /// let client = Client::new(); + /// let response = client.exchange_refresh_token(&request_data)?; + /// + /// println!("Fresh access token: {}", response.access_token); + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + fn exchange_refresh_token(&self, request: &RefreshTokenRequest) -> Result; +} + +impl DiscordOAuthReqwestRequester for ReqwestClient { + fn exchange_code(&self, request: &AccessTokenExchangeRequest) -> Result { + let body = serde_urlencoded::to_string(request)?; + + let response = sel + .post(BASE_TOKEN_URI) + .header(CONTENT_TYPE, "application/x-www-form-urlencoded") + .body(body) + .send()?; + let body = response.text().unwrap(); + + serde_json::from_str(&*body).map_err(From::from) + } + + fn exchange_refresh_token(&self, request: &RefreshTokenRequest) -> Result { + let body = serde_json::to_string(request)?; + + let response = self.post(BASE_TOKEN_URI).body(body).send()?; + let body = response.text().unwrap(); + + serde_json::from_str(&*body).map_err(From::from) + } +} diff --git a/src/constants.rs b/src/constants.rs index 76e4e0c..5078120 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,9 +1,9 @@ //! A set of constants around the OAuth2 API. /// The base authorization URI, used for authorizing an application. -pub const BASE_AUTHORIZE_URI: &str = "https://discordapp.com/api/oauth2/authorize"; +pub const BASE_AUTHORIZE_URI: &str = "https://discord.com/api/oauth2/authorize"; /// The revocation URL, used to revoke an access token. -pub const BASE_REVOKE_URI: &str = "https://discordapp.com/api/oauth2/revoke"; +pub const BASE_REVOKE_URI: &str = "https://discord.com/api/oauth2/revoke"; /// The token URI, used for exchanging a refresh token for a fresh access token /// and new refresh token. -pub const BASE_TOKEN_URI: &str = "https://discordapp.com/api/oauth2/token"; +pub const BASE_TOKEN_URI: &str = "https://discord.com/api/oauth2/token"; diff --git a/src/error.rs b/src/error.rs index 3e6a66e..a1fc9f1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,5 @@ use hyper::Error as HyperError; +use reqwest::Error as ReqwestError; use serde_json::Error as JsonError; use serde_urlencoded::ser::Error as UrlEncodeError; use std::error::Error as StdError; @@ -13,6 +14,8 @@ pub type Result = StdResult; pub enum Error { /// An error from the `hyper` crate. Hyper(HyperError), + /// An error from the `reqwest` crate. + Reqwest(ReqwestError), /// An error from the `serde_json` crate. Json(JsonError), /// An error from the `serde_urlencoded` crate. @@ -25,6 +28,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: ReqwestError) -> Self { + Error::Reqwest(err) + } +} + impl From for Error { fn from(err: JsonError) -> Self { Error::Json(err) @@ -39,7 +48,7 @@ impl From for Error { impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { - f.write_str(self.description()) + f.write_str(self.to_string().as_str()) } } @@ -47,6 +56,7 @@ impl StdError for Error { fn description(&self) -> &str { match *self { Error::Hyper(ref inner) => inner.description(), + Error::Reqwest(ref inner) => inner.description(), Error::Json(ref inner) => inner.description(), Error::UrlEncode(ref inner) => inner.description(), } diff --git a/src/lib.rs b/src/lib.rs index 9fd0556..9d96bd3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![warn(missing_docs)] //! # serenity-oauth //! //! `serenity-oauth` is a collection of HTTP library support bridges for @@ -23,14 +24,8 @@ #![deny(missing_docs)] -#[macro_use] extern crate serde_derive; - -extern crate hyper; -extern crate percent_encoding; -extern crate serde; -extern crate serde_json; -extern crate serde_urlencoded; -extern crate serenity_model; +#[macro_use] +extern crate serde_derive; pub mod bridge; pub mod constants; @@ -41,5 +36,6 @@ mod error; mod scope; pub use bridge::hyper::DiscordOAuthHyperRequester; +pub use bridge::reqwest::DiscordOAuthReqwestRequester; pub use error::{Error, Result}; pub use scope::Scope; diff --git a/src/model.rs b/src/model.rs index 86e3005..91c5fbe 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,7 +1,7 @@ //! A collection of models that can be deserialized from response bodies and //! serialized into request bodies. -use serenity_model::{PartialGuild, Webhook}; +use serenity::model::prelude::{PartialGuild, Webhook}; /// Structure of data used as the body of a request to exchange the [`code`] for /// an access token. @@ -45,12 +45,12 @@ impl AccessTokenExchangeRequest { /// /// assert_eq!(request.grant_type, "authorization_code"); /// ``` - pub fn new( - client_id: u64, - client_secret: S, - code: T, - redirect_uri: U, - ) -> Self where S: Into, T: Into, U: Into { + pub fn new(client_id: u64, client_secret: S, code: T, redirect_uri: U) -> Self + where + S: Into, + T: Into, + U: Into, + { Self { client_secret: client_secret.into(), code: code.into(), @@ -156,12 +156,12 @@ impl RefreshTokenRequest { /// /// assert_eq!(request.grant_type, "refresh_token"); /// ``` - pub fn new( - client_id: u64, - client_secret: S, - redirect_uri: T, - refresh_token: U, - ) -> Self where S: Into, T: Into, U: Into { + pub fn new(client_id: u64, client_secret: S, redirect_uri: T, refresh_token: U) -> Self + where + S: Into, + T: Into, + U: Into, + { Self { client_secret: client_secret.into(), grant_type: "refresh_token".to_owned(), diff --git a/src/utils.rs b/src/utils.rs index e11a313..dca680f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,9 +3,9 @@ //! This includes functions for easily generating URLs to redirect users to for //! authorization. -pub use serenity_model::Permissions; +pub use serenity::model::prelude::permissions::Permissions; -use constants::BASE_AUTHORIZE_URI; +use crate::constants::BASE_AUTHORIZE_URI; use percent_encoding; use super::Scope; use std::fmt::Write; @@ -22,11 +22,9 @@ use std::fmt::Write; /// "Send Messages" permissions: /// /// ```rust -/// extern crate serenity_model; -/// extern crate serenity_oauth; /// /// # fn main() { -/// use serenity_model::Permissions; +/// use serenity_oauth::utils::Permissions; /// /// let client_id = 249608697955745802; /// let required = Permissions::ADD_REACTIONS | Permissions::SEND_MESSAGES; -- cgit v1.2.3