diff options
author | David Blajda <blajda@hotmail.com> | 2018-12-20 23:42:45 +0000 |
---|---|---|
committer | David Blajda <blajda@hotmail.com> | 2018-12-20 23:42:45 +0000 |
commit | 298806448db4a4e74306ec648bfc0e43a76c6bc3 (patch) | |
tree | 1cdd90df0d65a513ad94588dae7621b03a906b3e | |
parent | 2bae9a4e8d8b77f8a99df6829547a60f883632a3 (diff) |
Split auth and unauth client and created ClientTrait
-rw-r--r-- | src/bin/main.rs | 17 | ||||
-rw-r--r-- | src/helix/mod.rs | 394 | ||||
-rw-r--r-- | src/helix/namespaces/auth.rs | 14 | ||||
-rw-r--r-- | src/helix/namespaces/clips.rs | 10 | ||||
-rw-r--r-- | src/helix/namespaces/mod.rs | 17 | ||||
-rw-r--r-- | src/helix/namespaces/users.rs | 14 | ||||
-rw-r--r-- | src/helix/namespaces/videos.rs | 2 | ||||
-rw-r--r-- | src/lib.rs | 22 | ||||
-rw-r--r-- | src/namespace.rs | 0 |
9 files changed, 324 insertions, 166 deletions
diff --git a/src/bin/main.rs b/src/bin/main.rs index c5b7ea2..81f07ff 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -6,17 +6,16 @@ extern crate twitch_api; use futures::future::Future; use std::env; -use twitch_api::Client; use twitch_api::HelixClient; fn main() { dotenv::dotenv().unwrap(); let client_id = &env::var("TWITCH_API").unwrap(); - let client = Client::new(client_id); + let client = HelixClient::new(client_id); let authed_client = - client.helix.clone() + client .authenticate(&env::var("TWITCH_SECRET").unwrap()) .build(); @@ -50,13 +49,21 @@ fn main() { * to become idle but it will never becomes idle since we keep a reference * to a reqwest client which maintains a connection pool. */ - std::mem::drop(authed_client); - std::mem::drop(client); + //std::mem::drop(authed_client); tokio::run( clip.join(clip2) .and_then(|(c1, c2)| { println!("{:?} {:?}", c1, c2); Ok((c1, c2)) + }).and_then(move |_| { + authed_client + .clips() + .clip(&"EnergeticApatheticTarsierThisIsSparta") + .map(|_| ()) + .map_err(|err| { + println!("{:?}", err); + () + }) }) .map(|_| ()) .map_err(|_| ()) diff --git a/src/helix/mod.rs b/src/helix/mod.rs index 3ed4494..c6eb1f5 100644 --- a/src/helix/mod.rs +++ b/src/helix/mod.rs @@ -2,14 +2,14 @@ use futures::future::Future; use std::sync::{Arc, Mutex}; use reqwest::r#async::Client as ReqwestClient; -use std::collections::HashSet; +use std::collections::{HashSet, HashMap}; use super::error::Error; -use std::marker::PhantomData; use futures::future::Shared; use futures::Poll; use serde::de::DeserializeOwned; use futures::Async; use futures::try_ready; +use std::iter::FromIterator; use crate::error::ConditionError; @@ -19,19 +19,13 @@ pub use super::types; pub mod models; pub mod namespaces; -pub struct Namespace<T> { - client: Client, - _type: PhantomData<T> -} +const API_DOMAIN: &'static str = "api.twitch.tv"; -impl<T> Namespace<T> { - pub fn new(client: &Client) -> Self { - Namespace { - client: client.clone(), - _type: PhantomData, - } - } +#[derive(PartialEq, Eq, Hash, Clone)] +pub enum RatelimitKey { + Default, } +type RatelimitMap = HashMap<RatelimitKey, Ratelimit>; #[derive(PartialEq, Hash, Eq, Clone)] pub enum Scope { @@ -47,7 +41,152 @@ pub enum Scope { #[derive(Clone)] pub struct Client { - inner: Arc<ClientRef>, + inner: Arc<ClientType>, +} + +enum ClientType { + Unauth(UnauthClient), + Auth(AuthClient), +} + +/*TODO: Try to remove this boilerplate too*/ +impl ClientTrait for Client { + + fn id<'a>(&'a self) -> &'a str { + use self::ClientType::*; + match self.inner.as_ref() { + Unauth(inner) => inner.id(), + Auth(inner) => inner.id(), + } + } + + fn domain<'a>(&'a self) -> &'a str { + use self::ClientType::*; + match self.inner.as_ref() { + Unauth(inner) => inner.domain(), + Auth(inner) => inner.domain(), + } + } + + fn ratelimit<'a>(&self, key: RatelimitKey) -> Option<&'a Ratelimit> { + use self::ClientType::*; + match self.inner.as_ref() { + Unauth(inner) => inner.ratelimit(key), + Auth(inner) => inner.ratelimit(key), + } + } + + fn authenticated(&self) -> bool { + use self::ClientType::*; + match self.inner.as_ref() { + Unauth(inner) => inner.authenticated(), + Auth(inner) => inner.authenticated(), + } + } + + fn scopes(&self) -> Vec<Scope> { + use self::ClientType::*; + match self.inner.as_ref() { + Unauth(inner) => inner.scopes(), + Auth(inner) => inner.scopes(), + } + } +} + +pub struct UnauthClient { + id: String, + reqwest: ReqwestClient, + domain: String, + ratelimits: RatelimitMap, +} + +impl Client { + + pub fn authenticate(self, secret: &str) -> AuthClientBuilder { + AuthClientBuilder::new(self, secret) + } + + pub fn deauthenticate(self) -> Client { + use self::ClientType::*; + match self.inner.as_ref() { + Unauth(_inner) => self, + Auth(inner) => inner.previous.clone(), + } + } +} + + +pub trait ClientTrait { + + fn id<'a>(&'a self) -> &'a str; + fn domain<'a>(&'a self) -> &'a str; + fn ratelimit<'a>(&self, key: RatelimitKey) -> Option<&'a Ratelimit>; + + fn authenticated(&self) -> bool; + fn scopes(&self) -> Vec<Scope>; +} + +impl ClientTrait for UnauthClient { + fn id<'a>(&'a self) -> &'a str { + &self.id + } + + fn domain<'a>(&'a self) -> &'a str { + &self.domain + } + + fn ratelimit<'a>(&self, key: RatelimitKey) -> Option<&'a Ratelimit> { + None + } + + fn authenticated(&self) -> bool { + false + } + + fn scopes(&self) -> Vec<Scope> { + Vec::with_capacity(0) + } +} + +pub struct AuthClient { + secret: String, + auth_state: Mutex<AuthStateRef>, + auth_barrier: Barrier, + previous: Client, +} + +/*TODO I'd be nice to remove this boiler plate */ +impl ClientTrait for AuthClient { + fn id<'a>(&'a self) -> &'a str { + match self.previous.inner.as_ref() { + ClientType::Auth(auth) => auth.id(), + ClientType::Unauth(unauth) => unauth.id(), + } + } + + fn domain<'a>(&'a self) -> &'a str { + match self.previous.inner.as_ref() { + ClientType::Auth(auth) => auth.domain(), + ClientType::Unauth(unauth) => unauth.domain(), + } + } + + fn ratelimit<'a>(&self, key: RatelimitKey) -> Option<&'a Ratelimit> { + match self.previous.inner.as_ref() { + ClientType::Auth(auth) => auth.ratelimit(key), + ClientType::Unauth(unauth) => unauth.ratelimit(key), + } + } + + fn authenticated(&self) -> bool { + let auth = self.auth_state.lock().expect("Auth Lock is poisoned"); + auth.state == AuthState::Auth + } + + fn scopes(&self) -> Vec<Scope> { + let auth = self.auth_state.lock().expect("Auth Lock is poisoned"); + Vec::with_capacity(0) + } } #[derive(Clone, PartialEq)] @@ -56,22 +195,21 @@ enum AuthState { Auth, } - -struct MutClientRef { +struct AuthStateRef { token: Option<String>, scopes: Vec<Scope>, - previous: Option<Client>, - auth_state: AuthState, - auth_future: Option<Shared<Box<Future<Item=(), Error=ConditionError> + Send>>> + state: AuthState, } struct ClientRef { id: String, secret: Option<String>, - client: ReqwestClient, + reqwest: ReqwestClient, + domain: &'static str, + ratelimits: RatelimitMap, + auth_state: Mutex<AuthStateRef>, auth_barrier: Barrier, - ratelimit_default: Ratelimit, - inner: Mutex<MutClientRef>, + previous: Option<Client>, } impl Client { @@ -80,50 +218,41 @@ impl Client { Client::new_with_client(id, client) } - pub fn default_ratelimit(&self) -> Ratelimit { - self.inner.ratelimit_default.clone() + fn default_ratelimits() -> RatelimitMap { + let mut limits = RatelimitMap::new(); + limits.insert(RatelimitKey::Default, Ratelimit::new(30, "Ratelimit-Limit", "Ratelimit-Remaining", "Ratelimit-Reset")); + + limits } - pub fn new_with_client(id: &str, client: ReqwestClient) -> Client { + pub fn new_with_client(id: &str, reqwest: ReqwestClient) -> Client { Client { - inner: Arc::new(ClientRef { - id: id.to_owned(), - client: client, - secret: None, - auth_barrier: Barrier::new(), - ratelimit_default: Ratelimit::new(30, "Ratelimit-Limit", "Ratelimit-Remaining", "Ratelimit-Reset"), - inner: Mutex::new( - MutClientRef { - token: None, - scopes: Vec::new(), - previous: None, - auth_state: AuthState::Auth, - auth_future: None, - }) - }) + inner: Arc::new( + ClientType::Unauth(UnauthClient { + id: id.to_owned(), + reqwest: reqwest, + domain: API_DOMAIN.to_owned(), + ratelimits: Self::default_ratelimits(), + })) } } - pub fn id(&self) -> &str { - &self.inner.id - } - - pub fn client(&self) -> &ReqwestClient { - &self.inner.client - } - - pub fn authenticated(&self) -> bool { - let mut_data = self.inner.inner.lock().unwrap(); - mut_data.token.is_some() + fn secret<'a>(&'a self) -> Option<&'a str> { + use self::ClientType::*; + match self.inner.as_ref() { + Unauth(_) => None, + Auth(inner) => Some(&inner.secret), + } } - /* - pub fn scopes(&self) -> Vec<Scope> { - let mut_data = self.inner.inner.lock().unwrap(); - (&mut_data.scopes).into_iter().to_owned().collect() + fn reqwest(&self) -> ReqwestClient { + use self::ClientType::*; + match self.inner.as_ref() { + Unauth(inner) => inner.reqwest.clone(), + Auth(inner) => inner.previous.reqwest(), + } } - */ /* The 'bottom' client must always be a client that is not authorized. * This which allows for calls to Auth endpoints using the same control flow @@ -133,38 +262,28 @@ impl Client { * to authenticate stack a authed client on top */ fn get_bottom_client(&self) -> Client { - let mut_client = self.inner.inner.lock().unwrap(); - match &mut_client.previous { - Some(client) => { - client.get_bottom_client() - }, - None => { - self.clone() - } - } - } - - pub fn authenticate(self, secret: &str) -> AuthClientBuilder { - AuthClientBuilder::new(self, secret) - } - - pub fn deauthenticate(self) -> Client { - let mut_data = self.inner.inner.lock().unwrap(); - match &mut_data.previous { - Some(old_client) => old_client.clone(), - None => self.clone() + match self.inner.as_ref() { + ClientType::Auth(inner) => inner.previous.get_bottom_client(), + ClientType::Unauth(_) => self.clone(), } } - pub fn apply_standard_headers(&self, request: RequestBuilder) + fn apply_standard_headers(&self, request: RequestBuilder) -> RequestBuilder { - let mut_client = self.inner.inner.lock().unwrap(); + let token = match self.inner.as_ref() { + ClientType::Auth(inner) => { + let auth = inner.auth_state.lock().expect("Authlock is poisoned"); + auth.token.as_ref().map(|s| s.to_owned()) + } + ClientType::Unauth(_) => None, + }; + let client_header = header::HeaderValue::from_str(self.id()).unwrap(); let request = - if let Some(token) = &mut_client.token { - let value = "Bearer ".to_owned() + token; + if let Some(token) = token { + let value = "Bearer ".to_owned() + &token; let token_header = header::HeaderValue::from_str(&value).unwrap(); request.header("Authorization", token_header) } else { request }; @@ -202,22 +321,18 @@ impl AuthClientBuilder { let auth_state = if self.token.is_some() { AuthState::Auth } else { AuthState::Unauth }; let old_client = self.client; Client { - inner: Arc::new(ClientRef { - id: old_client.inner.id.clone(), - client: old_client.inner.client.clone(), - secret: Some(self.secret), + inner: Arc::new(ClientType::Auth( + AuthClient { + secret: self.secret, auth_barrier: Barrier::new(), - ratelimit_default: old_client.default_ratelimit(), - inner: Mutex::new ( - MutClientRef { + auth_state: Mutex::new ( + AuthStateRef { token: self.token, scopes: Vec::new(), - previous: Some(old_client), - auth_state: auth_state, - auth_future: None, - }) - - }) + state: auth_state, + }), + previous: old_client, + })) } } @@ -252,13 +367,14 @@ struct RequestRef { url: String, params: BTreeMap<String, String>, client: Client, - ratelimit: Option<Ratelimit>, + ratelimit: Option<RatelimitKey>, method: Method, } enum RequestState<T> { Uninitalized, WaitAuth(WaiterState<AuthWaiter>), + SetupRatelimit, WaitLimit(WaiterState<RatelimitWaiter>), WaitRequest, PollParse(Box<dyn Future<Item=T, Error=reqwest::Error> + Send>), @@ -272,16 +388,21 @@ pub struct ApiRequest<T> { impl<T: DeserializeOwned + 'static + Send> ApiRequest<T> { pub fn new(url: String, - params: BTreeMap<String, String>, + params: BTreeMap<&str, &str>, client: Client, method: Method, - ratelimit: Option<Ratelimit>, + ratelimit: Option<RatelimitKey>, ) -> ApiRequest<T> { + let mut owned_params = BTreeMap::new(); + for (key, value) in params { + owned_params.insert(key.to_owned(), value.to_owned()); + } + ApiRequest { inner: Arc::new( RequestRef { url: url, - params: params, + params: owned_params, client: client, method: method, ratelimit: ratelimit, @@ -393,14 +514,21 @@ impl Waiter for AuthWaiter { type Error = ConditionError; fn blocked(&self) -> bool { - let mut_client = self.waiter.inner.inner.lock().unwrap(); - mut_client.auth_state == AuthState::Unauth + match self.waiter.inner.as_ref() { + ClientType::Unauth(_) => false, + ClientType::Auth(inner) => { + let auth = inner.auth_state.lock() + .expect("unable to lock auth state"); + auth.state == AuthState::Unauth + } + } } fn condition(&self) -> Shared<Box<Future<Item=(), Error=ConditionError> + Send>> { + /* If a secret is not provided than just immediately return */ + let secret = self.waiter.secret().unwrap(); let bottom_client = self.waiter.get_bottom_client(); - let secret = self.waiter.inner.secret.as_ref().unwrap(); let client = self.waiter.clone(); let auth_future = @@ -409,9 +537,11 @@ impl Waiter for AuthWaiter { .client_credentials(secret) .map(move |credentials| { println!("{:?}", credentials); - let mut mut_client = client.inner.inner.lock().unwrap(); - mut_client.auth_state = AuthState::Auth; - mut_client.token = Some(credentials.access_token.clone()); + if let ClientType::Auth(inner) = client.inner.as_ref() { + let mut auth = inner.auth_state.lock().unwrap(); + auth.state = AuthState::Auth; + auth.token = Some(credentials.access_token.clone()); + } () }) .map_err(|_| ConditionError{}); @@ -454,23 +584,35 @@ impl<T: DeserializeOwned + 'static + Send> Future for ApiRequest<T> { loop { match &mut self.state { RequestState::Uninitalized => { - let mut_client = self.inner.client.inner.inner.lock().unwrap(); - - let waiter = AuthWaiter { - waiter: self.inner.client.clone(), - }; + match self.inner.client.inner.as_ref() { + ClientType::Auth(inner) => { + let waiter = AuthWaiter { + waiter: self.inner.client.clone(), + }; - let f = WaiterState::new(waiter, - &self.inner.client.inner.auth_barrier); - self.state = RequestState::WaitAuth(f); + let f = WaiterState::new(waiter, + &inner.auth_barrier); + self.state = RequestState::WaitAuth(f); + }, + ClientType::Unauth(_) => { + self.state = RequestState::SetupRatelimit; + } + } }, RequestState::WaitAuth(auth) => { let _waiter = try_ready!(auth.poll()); - match self.inner.ratelimit { - Some(ref limit) => { - let barrier = limit.barrier.clone(); + self.state = RequestState::SetupRatelimit; + }, + RequestState::SetupRatelimit => { + let limits = + self.inner.ratelimit.as_ref().and_then(|key| { + self.inner.client.ratelimit(key.clone()) + }); + match limits { + Some(ratelimit) => { + let barrier = ratelimit.barrier.clone(); let waiter = RatelimitWaiter { - limit: limit.clone(), + limit: ratelimit.clone(), }; let f = WaiterState::new(waiter, &barrier); @@ -487,9 +629,14 @@ impl<T: DeserializeOwned + 'static + Send> Future for ApiRequest<T> { }, RequestState::WaitRequest => { let client = &self.inner.client; - let reqwest = client.client(); + let reqwest = client.reqwest(); + + let limits = + self.inner.ratelimit.as_ref().and_then(|key| { + client.ratelimit(key.clone()) + }); - if let Some(limits) = &self.inner.ratelimit { + if let Some(limits) = limits { let mut mut_limits = limits.inner.lock().unwrap(); mut_limits.inflight = mut_limits.inflight + 1; } @@ -497,24 +644,23 @@ impl<T: DeserializeOwned + 'static + Send> Future for ApiRequest<T> { let builder = reqwest.request(self.inner.method.clone(), &self.inner.url); let builder = client.apply_standard_headers(builder); let r = builder.query(&self.inner.params); - /*TODO add 1 to inflight*/ - let ratelimit_err = self.inner.ratelimit.clone(); - let ratelimit_ok = self.inner.ratelimit.clone(); + let limits_err = limits.clone(); + let limits_ok = limits.clone(); let f = r.send() - .map_err(|err| { + .map_err(move |err| { - if let Some(limits) = ratelimit_err { + if let Some(limits) = limits_err { let mut mut_limits = limits.inner.lock().unwrap(); mut_limits.inflight = mut_limits.inflight - 1; } err }) - .map(|mut response| { + .map(move |mut response| { println!("{:?}", response); - if let Some(limits) = ratelimit_ok { + if let Some(limits) = limits_ok { let mut mut_limits = limits.inner.lock().unwrap(); mut_limits.inflight = mut_limits.inflight - 1; diff --git a/src/helix/namespaces/auth.rs b/src/helix/namespaces/auth.rs index 5efc0fe..478c1af 100644 --- a/src/helix/namespaces/auth.rs +++ b/src/helix/namespaces/auth.rs @@ -1,9 +1,9 @@ -use futures::future::Future; use std::collections::BTreeMap; use super::super::models::Credentials; use super::super::Client; const ID_DOMAIN: &'static str = "id.twitch.tv"; -use super::super::Namespace; +use super::Namespace; +use super::super::ClientTrait; pub struct Auth {} type AuthNamespace = Namespace<Auth>; @@ -34,10 +34,10 @@ pub fn client_credentials(client: Client, secret: &str) ID_DOMAIN + "/oauth2/token"; let mut params = BTreeMap::new(); - params.insert("client_id".to_owned(), client.id().to_owned()); - params.insert("client_secret".to_owned(), secret.to_owned()); - params.insert("grant_type".to_owned(), "client_credentials".to_owned()); - params.insert("scope".to_owned(), "".to_owned()); + params.insert("client_id", client.id()); + params.insert("client_secret", secret); + params.insert("grant_type", "client_credentials"); + params.insert("scope", ""); - ApiRequest::new(url, params, client, Method::POST, None) + ApiRequest::new(url, params, client.clone(), Method::POST, None) } diff --git a/src/helix/namespaces/clips.rs b/src/helix/namespaces/clips.rs index 083e5c4..19293cc 100644 --- a/src/helix/namespaces/clips.rs +++ b/src/helix/namespaces/clips.rs @@ -1,9 +1,10 @@ -use futures::future::Future; use std::collections::BTreeMap; use super::super::models::{DataContainer, PaginationContainer, User, Video, Clip}; use super::super::Client; +use super::super::ClientTrait; +use super::super::RatelimitKey; const API_DOMAIN: &'static str = "api.twitch.tv"; -use super::super::Namespace; +use super::Namespace; pub struct Clips {} type ClipsNamespace = Namespace<Clips>; @@ -30,10 +31,9 @@ pub fn clip(client: Client, id: &str) { let url = String::from("https://") + - API_DOMAIN + "/helix/clips" + "?id=" + id; + client.domain() + "/helix/clips" + "?id=" + id; let params = BTreeMap::new(); - let limit = client.default_ratelimit(); - ApiRequest::new(url, params, client, Method::GET, Some(limit)) + ApiRequest::new(url, params, client, Method::GET, Some(RatelimitKey::Default)) } diff --git a/src/helix/namespaces/mod.rs b/src/helix/namespaces/mod.rs index d1c44bd..1c0d08e 100644 --- a/src/helix/namespaces/mod.rs +++ b/src/helix/namespaces/mod.rs @@ -1,4 +1,21 @@ +use std::marker::PhantomData; +use super::Client; + pub mod clips; pub mod users; pub mod videos; pub mod auth; + +pub struct Namespace<T> { + client: Client, + _type: PhantomData<T> +} + +impl<T> Namespace<T> { + pub fn new(client: &Client) -> Self { + Namespace { + client: client.clone(), + _type: PhantomData, + } + } +} diff --git a/src/helix/namespaces/users.rs b/src/helix/namespaces/users.rs index c809b95..3e4f1dd 100644 --- a/src/helix/namespaces/users.rs +++ b/src/helix/namespaces/users.rs @@ -3,25 +3,28 @@ use super::super::models::{DataContainer, PaginationContainer, User, Video, Clip use super::super::Client; use std::collections::BTreeMap; const API_DOMAIN: &'static str = "api.twitch.tv"; -use super::super::Namespace; +use super::Namespace; pub struct Users {} type UsersNamespace = Namespace<Users>; impl UsersNamespace { + /* pub fn users(self, id: Vec<&str>, login: Vec<&str>) -> impl Future<Item=DataContainer<User>, Error=reqwest::Error> { - use self::users; - users(self.client, id, login) + //use self::users; + //users(self.client, id, login) } + */ } - +/* impl Client { pub fn users(&self) -> UsersNamespace { UsersNamespace::new(self) } } - +*/ +/* pub fn users( client: Client, id: Vec<&str>, @@ -50,3 +53,4 @@ pub fn users( }) .and_then(|json| json) } +*/ diff --git a/src/helix/namespaces/videos.rs b/src/helix/namespaces/videos.rs index ad5ca28..7b8839b 100644 --- a/src/helix/namespaces/videos.rs +++ b/src/helix/namespaces/videos.rs @@ -3,7 +3,7 @@ use super::super::models::{DataContainer, PaginationContainer, User, Video, Clip use super::super::Client; use std::collections::BTreeMap; const API_DOMAIN: &'static str = "api.twitch.tv"; -use super::super::Namespace; +use super::Namespace; pub struct Videos {} type VideosNamespace = Namespace<Videos>; @@ -1,35 +1,19 @@ #![recursion_limit="128"] #![feature(option_replace)] -#![feature(associated_type_defaults)] extern crate futures; extern crate reqwest; extern crate serde; extern crate chrono; #[macro_use] extern crate serde_derive; +use reqwest::r#async::Client as ReqwestClient; + pub mod helix; pub mod kraken; pub mod types; pub mod error; pub mod sync; - +pub mod namespace; pub use self::helix::Client as HelixClient; pub use self::kraken::Client as KrakenClient; - -use reqwest::r#async::Client as ReqwestClient; - -pub struct Client { - pub helix: HelixClient, - pub kraken: KrakenClient, -} - -impl Client { - pub fn new(client_id: &str) -> Client { - let client = ReqwestClient::new(); - Client { - helix: HelixClient::new_with_client(client_id, client.clone()), - kraken: KrakenClient::new_with_client(client_id, client.clone()), - } - } -} diff --git a/src/namespace.rs b/src/namespace.rs new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/namespace.rs |