From 98fb79b85e3cfbb547e5340b30623511daf09ef5 Mon Sep 17 00:00:00 2001 From: David Blajda Date: Wed, 8 May 2019 15:38:37 +0000 Subject: :WIP: Move types to a different crate --- Cargo.toml | 27 +- src/bin/main.rs | 124 --- src/client.rs | 1187 -------------------- src/error.rs | 94 -- src/helix/mod.rs | 83 -- src/helix/models.rs | 118 -- src/helix/namespaces/auth.rs | 19 - src/helix/namespaces/clips.rs | 32 - src/helix/namespaces/mod.rs | 26 - src/helix/namespaces/users.rs | 44 - src/helix/namespaces/videos.rs | 75 -- src/kraken/endpoints.rs | 0 src/kraken/mod.rs | 83 -- src/kraken/models.rs | 79 -- src/kraken/namespaces/clips.rs | 33 - src/kraken/namespaces/mod.rs | 20 - src/kraken/namespaces/users.rs | 32 - src/lib.rs | 22 - src/models.rs | 28 - src/namespace/auth.rs | 55 - src/namespace/mod.rs | 1 - src/sync/barrier.rs | 47 - src/sync/mod.rs | 2 - src/sync/waiter.rs | 12 - src/types.rs | 134 --- tests/common/mod.rs | 32 - tests/helix.rs | 253 ----- tests/kraken.rs | 76 -- twitch_api/Cargo.lock | 1728 +++++++++++++++++++++++++++++ twitch_api/Cargo.toml | 23 + twitch_api/src/bin/main.rs | 124 +++ twitch_api/src/client.rs | 1187 ++++++++++++++++++++ twitch_api/src/error.rs | 94 ++ twitch_api/src/helix/mod.rs | 83 ++ twitch_api/src/helix/models.rs | 118 ++ twitch_api/src/helix/namespaces/auth.rs | 19 + twitch_api/src/helix/namespaces/clips.rs | 32 + twitch_api/src/helix/namespaces/mod.rs | 26 + twitch_api/src/helix/namespaces/users.rs | 44 + twitch_api/src/helix/namespaces/videos.rs | 75 ++ twitch_api/src/kraken/endpoints.rs | 0 twitch_api/src/kraken/mod.rs | 83 ++ twitch_api/src/kraken/models.rs | 79 ++ twitch_api/src/kraken/namespaces/clips.rs | 33 + twitch_api/src/kraken/namespaces/mod.rs | 20 + twitch_api/src/kraken/namespaces/users.rs | 32 + twitch_api/src/lib.rs | 23 + twitch_api/src/models.rs | 28 + twitch_api/src/namespace/auth.rs | 55 + twitch_api/src/namespace/mod.rs | 1 + twitch_api/src/sync/barrier.rs | 47 + twitch_api/src/sync/mod.rs | 2 + twitch_api/src/sync/waiter.rs | 12 + twitch_api/src/types.rs | 134 +++ twitch_api/tests/common/mod.rs | 32 + twitch_api/tests/helix.rs | 253 +++++ twitch_api/tests/kraken.rs | 76 ++ twitch_types/Cargo.toml | 8 + twitch_types/src/lib.rs | 137 +++ 59 files changed, 4613 insertions(+), 2733 deletions(-) delete mode 100644 src/bin/main.rs delete mode 100644 src/client.rs delete mode 100644 src/error.rs delete mode 100644 src/helix/mod.rs delete mode 100644 src/helix/models.rs delete mode 100644 src/helix/namespaces/auth.rs delete mode 100644 src/helix/namespaces/clips.rs delete mode 100644 src/helix/namespaces/mod.rs delete mode 100644 src/helix/namespaces/users.rs delete mode 100644 src/helix/namespaces/videos.rs delete mode 100644 src/kraken/endpoints.rs delete mode 100644 src/kraken/mod.rs delete mode 100644 src/kraken/models.rs delete mode 100644 src/kraken/namespaces/clips.rs delete mode 100644 src/kraken/namespaces/mod.rs delete mode 100644 src/kraken/namespaces/users.rs delete mode 100644 src/lib.rs delete mode 100644 src/models.rs delete mode 100644 src/namespace/auth.rs delete mode 100644 src/namespace/mod.rs delete mode 100644 src/sync/barrier.rs delete mode 100644 src/sync/mod.rs delete mode 100644 src/sync/waiter.rs delete mode 100644 src/types.rs delete mode 100644 tests/common/mod.rs delete mode 100644 tests/helix.rs delete mode 100644 tests/kraken.rs create mode 100644 twitch_api/Cargo.lock create mode 100644 twitch_api/Cargo.toml create mode 100644 twitch_api/src/bin/main.rs create mode 100644 twitch_api/src/client.rs create mode 100644 twitch_api/src/error.rs create mode 100644 twitch_api/src/helix/mod.rs create mode 100644 twitch_api/src/helix/models.rs create mode 100644 twitch_api/src/helix/namespaces/auth.rs create mode 100644 twitch_api/src/helix/namespaces/clips.rs create mode 100644 twitch_api/src/helix/namespaces/mod.rs create mode 100644 twitch_api/src/helix/namespaces/users.rs create mode 100644 twitch_api/src/helix/namespaces/videos.rs create mode 100644 twitch_api/src/kraken/endpoints.rs create mode 100644 twitch_api/src/kraken/mod.rs create mode 100644 twitch_api/src/kraken/models.rs create mode 100644 twitch_api/src/kraken/namespaces/clips.rs create mode 100644 twitch_api/src/kraken/namespaces/mod.rs create mode 100644 twitch_api/src/kraken/namespaces/users.rs create mode 100644 twitch_api/src/lib.rs create mode 100644 twitch_api/src/models.rs create mode 100644 twitch_api/src/namespace/auth.rs create mode 100644 twitch_api/src/namespace/mod.rs create mode 100644 twitch_api/src/sync/barrier.rs create mode 100644 twitch_api/src/sync/mod.rs create mode 100644 twitch_api/src/sync/waiter.rs create mode 100644 twitch_api/src/types.rs create mode 100644 twitch_api/tests/common/mod.rs create mode 100644 twitch_api/tests/helix.rs create mode 100644 twitch_api/tests/kraken.rs create mode 100644 twitch_types/Cargo.toml create mode 100644 twitch_types/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 96cf8e9..67af503 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,5 @@ -[package] -name = "twitch_api" -version = "0.0.321" -authors = ["David Blajda "] -edition = "2018" - -[dependencies] -reqwest = ">=0.9.5" -hyper = "0.12.20" -http = "0.1.15" -futures = "0.1.25" -tokio = "0.1.13" -dotenv = "0.13.0" -log = "0.4.5" -env_logger = "0.6.0" -serde = "1.0.81" -serde_json = "1.0.33" -serde_derive = "1.0.81" -chrono = { version = "0.4.6", features = ["serde"]} -url = "1.7.2" -url_serde = "0.2.0" -futures-timer = "0.1.1" +[workspace] +members = [ + "twitch_api", + "twitch_types", +] diff --git a/src/bin/main.rs b/src/bin/main.rs deleted file mode 100644 index a545ec1..0000000 --- a/src/bin/main.rs +++ /dev/null @@ -1,124 +0,0 @@ -extern crate dotenv; -extern crate futures; -extern crate serde; -extern crate tokio; -extern crate twitch_api; -extern crate env_logger; - -use futures::future::Future; -use std::env; -use twitch_api::HelixClient; -use twitch_api::KrakenClient; -use twitch_api::ClientConfig; -use twitch_api::client::RatelimitMap; - - -fn main() { - dotenv::dotenv().unwrap(); - env_logger::init(); - - let config = ClientConfig { - max_retrys: 0, - ratelimits: RatelimitMap::empty(), - ..ClientConfig::default() - }; - - let client_id = &env::var("TWITCH_API").unwrap(); - let helix_client = HelixClient::new_with_config(client_id, config); - let kraken_client = KrakenClient::new(client_id); - - /* - .authenticate(&env::var("TWITCH_SECRET").unwrap()) - .build(); - */ - -/* - let clip = helix_client - .clips() - .clip(&"EnergeticApatheticTarsierThisIsSparta") - .map_err(|err| { - println!("{:?}", err); - () - }); - */ - /* - - let clip2 = authed_client - .clips() - .clip(&"EnergeticApatheticTarsierThisIsSparta") - .map_err(|err| { - println!("{:?}", err); - () - }); - */ - - //use twitch_api::types::VideoId; - - /* - let videos = authed_client - .videos() - .by_user(&UserId::from_str("19571641").unwrap()) - .take(1) - .for_each(|collection| { - println!("{:?}", collection); - Ok(()) - }) - .map(|_| ()) - .map_err(|err| {println!("{:?}", err); ()}); - */ - - -/* - let clip2 = kraken_client - .clips() - .clip(&"EnergeticApatheticTarsierThisIsSparta") - .map_err(|err| { - println!("{:?}", err); - () - }); -*/ - - - let f = futures::future::ok(1).and_then(move |_| { - for i in 0..80 { - let u = helix_client - .users() - .users(&vec!(), &vec!("freakey")) - .map(|res| {println!("{:?}", res); ()}) - .map_err(|res| {println!("{:?}", res); ()}); - tokio::spawn(u); - } - Ok(()) - }); - - /* Prevents tokio from **hanging** - * since tokio::run blocks the current thread and waits for the entire runtime - * 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); - tokio::run( - f - /* - clip.join(clip2) - .and_then(|(c1, c2)| { - println!("{:?}", c1); - println!("__"); - println!("{:?}", c2); - Ok((c1, c2)) - }).and_then(move |_| { - helix_client - .clips() - .clip(&ClipId::new("EnergeticApatheticTarsierThisIsSparta")) - .map(|_| ()) - .map_err(|err| { - println!("{:?}", err); - () - }) - }) - .map(|_| ()) - .map_err(|_| ()) - */ - /*videos*/ - ); -} diff --git a/src/client.rs b/src/client.rs deleted file mode 100644 index 0307a05..0000000 --- a/src/client.rs +++ /dev/null @@ -1,1187 +0,0 @@ -use crate::models::Message; -use std::convert::TryFrom; -use futures::future::Future; -use std::sync::{Arc, Mutex}; -use reqwest::r#async::Client as ReqwestClient; -use reqwest::Error as ReqwestError; -use reqwest::r#async::{Request, Response}; - -use std::collections::{HashSet, HashMap}; -use super::error::Error; -use futures::future::Shared; -use futures::Poll; -use serde::de::DeserializeOwned; -use futures::Async; -use futures::try_ready; -use serde_json::Value; -use futures::future::Either; - -use crate::error::ConditionError; - -pub use super::types; - -#[derive(PartialEq, Eq, Hash, Clone)] -pub enum RatelimitKey { - Default, -} - -pub struct RatelimitMap { - pub inner: HashMap -} - -const API_DOMAIN: &'static str = "api.twitch.tv"; -const AUTH_DOMAIN: &'static str = "id.twitch.tv"; -const KRAKEN_ACCEPT: &'static str = "application/vnd.twitchtv.v5+json"; - -pub trait PaginationTrait { - fn cursor<'a>(&'a self) -> Option<&'a str>; -} - -pub type ParamList<'a> = BTreeMap<&'a str, &'a dyn ToString>; - -#[derive(Clone)] -pub struct Client { - inner: Arc, -} - -#[derive(Debug)] -pub struct ScopeParseError {} -use std::fmt; -impl fmt::Display for ScopeParseError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Scope Parse Error") - } -} - -/*TODO*/ -#[derive(PartialEq, Hash, Eq, Clone, Debug)] -pub enum Scope { - Helix(HelixScope), - Kraken(KrakenScope), -} - -impl TryFrom<&str> for Scope { - type Error = ScopeParseError; - fn try_from(s: &str) -> Result { - if let Ok(scope) = HelixScope::try_from(s) { - return Ok(Scope::Helix(scope)); - } - if let Ok(scope) = KrakenScope::try_from(s) { - return Ok(Scope::Kraken(scope)); - } - Err(ScopeParseError {}) - } -} -use serde::{Deserialize, Deserializer}; -impl<'de> Deserialize<'de> for Scope { - - fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de> - { - let id = String::deserialize(deserializer)?; - Scope::try_from(&id[0..]).map_err(serde::de::Error::custom) - } -} - -#[derive(PartialEq, Hash, Eq, Clone, Debug)] -pub enum HelixScope { - AnalyticsReadExtensions, - AnalyticsReadGames, - BitsRead, - ChannelReadSubscriptions, - ClipsEdit, - UserEdit, - UserEditBroadcast, - UserReadBroadcast, - UserReadEmail, -} - -impl HelixScope { - pub fn to_str(&self) -> &'static str { - use self::HelixScope::*; - match self { - AnalyticsReadExtensions => "analytics:read:extensions", - AnalyticsReadGames => "analytics:read:games", - BitsRead => "bits:read", - ChannelReadSubscriptions => "channel:read:subscriptions", - ClipsEdit => "clips:edit", - UserEdit => "user:edit", - UserEditBroadcast => "user:edit:broadcast", - UserReadBroadcast => "user:read:broadcast", - UserReadEmail => "user:read:email", - } - } -} - -impl TryFrom<&str> for HelixScope { - type Error = ScopeParseError; - fn try_from(s: &str) -> Result { - use self::HelixScope::*; - Ok( match s { - "analytics:read:extensions" => AnalyticsReadExtensions, - "analytics:read:games" => AnalyticsReadGames, - "bits:read" => BitsRead, - "channel:read:subscriptions" => ChannelReadSubscriptions, - "clips:edit" => ClipsEdit, - "user:edit" => UserEdit, - "user:edit:broadcast" => UserEditBroadcast, - "user:read:broadcast" => UserReadBroadcast, - "user:read:email" => UserReadEmail, - _ => return Err(ScopeParseError{}) - }) - } -} - -#[derive(PartialEq, Hash, Eq, Clone, Debug)] -pub enum KrakenScope { - ChannelCheckSubscription, - ChannelCommercial, - ChannelEditor, - ChannelFeedEdit, - ChannelFeedRead, - ChannelRead, - ChannelStream, - ChannelSubscriptions, - CollectionsEdit, - CommunitiesEdit, - CommunitiesModerate, - Openid, - UserBlocksEdit, - UserBlocksRead, - UserFollowsEdit, - UserRead, - UserSubscriptions, - ViewingActivityRead, -} - -impl KrakenScope { - pub fn to_str(&self) -> &'static str { - use self::KrakenScope::*; - match self { - ChannelCheckSubscription => "channel_check_subscription", - ChannelCommercial => "channel_commercial", - ChannelEditor => "channel_editor", - ChannelFeedEdit => "channel_feed_edit", - ChannelFeedRead => "channel_feed_read", - ChannelRead => "channel_read", - ChannelStream => "channel_stream", - ChannelSubscriptions => "channel_subscriptions", - CollectionsEdit => "collections_edit", - CommunitiesEdit => "communities_edit", - CommunitiesModerate => "communities_moderate", - Openid => "openid", - UserBlocksEdit => "user_blocks_edit", - UserBlocksRead => "user_blocks_read", - UserFollowsEdit => "user_follows_edit", - UserRead => "user_read", - UserSubscriptions => "user_subscriptions", - ViewingActivityRead => "viewing_activity_read", - } - } -} - -impl TryFrom<&str> for KrakenScope { - type Error = ScopeParseError; - fn try_from(s: &str) -> Result { - use self::KrakenScope::*; - Ok( match s { - "channel_check_subscription" => ChannelCheckSubscription, - "channel_commercial" => ChannelCommercial, - "channel_editor" => ChannelEditor, - "channel_feed_edit" => ChannelFeedEdit, - "channel_feed_read" => ChannelFeedRead, - "channel_read" => ChannelRead, - "channel_stream" => ChannelStream, - "channel_subscriptions" => ChannelSubscriptions, - "collections_edit" => CollectionsEdit, - "communities_edit" => CommunitiesEdit, - "communities_moderate" => CommunitiesModerate, - "openid" => Openid, - "user_blocks_edit" => UserBlocksEdit, - "user_blocks_read" => UserBlocksRead, - "user_follows_edit" => UserFollowsEdit, - "user_read" => UserRead, - "user_subscriptions" => UserSubscriptions, - "viewing_activity_read" => ViewingActivityRead, - _ => return Err(ScopeParseError {}) - }) - } -} - -#[derive(Clone)] -pub enum Version { - Helix, - Kraken, -} - -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 struct TestConfigRef { - pub requests: Vec>, - pub responses: Vec, -} - -#[derive(Clone)] -pub struct TestConfig { - pub inner: Arc> -} - -impl TestConfig { - - pub fn push_response(&self, response: Response) { - let inner = &mut self.inner.lock().unwrap(); - inner.responses.push(response); - } -} - -impl Default for TestConfig { - - fn default() -> Self { - TestConfig { - inner: Arc::new( - Mutex::new( - TestConfigRef { - requests: Vec::new(), - responses: Vec::new(), - } - ) - ) - } - } -} - -enum ClientType { - Unauth(UnauthClient), - Auth(AuthClient), -} - - -pub struct ClientConfig { - pub reqwest: ReqwestClient, - pub domain: String, - pub auth_domain: String, - pub ratelimits: RatelimitMap, - pub max_retrys: u32, - pub test_config: Option, -} - -impl Default for RatelimitMap { - - fn default() -> Self { - let mut limits = HashMap::new(); - limits.insert(RatelimitKey::Default, Ratelimit::new(30, "Ratelimit-Limit", "Ratelimit-Remaining", "Ratelimit-Reset")); - RatelimitMap { - inner: limits - } - } -} - -impl RatelimitMap { - pub fn empty() -> RatelimitMap { - RatelimitMap { - inner: HashMap::new() - } - } -} - -impl Default for ClientConfig { - - fn default() -> Self { - let reqwest = ReqwestClient::new(); - let ratelimits = RatelimitMap::default(); - - ClientConfig { - reqwest, - domain: API_DOMAIN.to_owned(), - auth_domain: AUTH_DOMAIN.to_owned(), - ratelimits, - max_retrys: 1, - test_config: None, - } - } -} - -pub struct UnauthClient { - id: String, - config: ClientConfig, - version: Version, -} - -pub struct AuthClient { - secret: String, - auth_state: Mutex, - auth_barrier: Barrier, - previous: Client, -} - -pub trait ClientTrait { - - fn id<'a>(&'a self) -> &'a str; - fn config<'a>(&'a self) -> &'a ClientConfig; - fn domain<'a>(&'a self) -> &'a str; - fn auth_domain<'a>(&'a self) -> &'a str; - fn ratelimit<'a>(&'a self, key: RatelimitKey) -> Option<&'a Ratelimit>; - - fn authenticated(&self) -> bool; - fn scopes(&self) -> Vec; -} - -impl ClientTrait for UnauthClient { - fn id<'a>(&'a self) -> &'a str { - &self.id - } - - fn domain<'a>(&'a self) -> &'a str { - &self.config.domain - } - - fn auth_domain<'a>(&'a self) -> &'a str { - &self.config.auth_domain - } - - fn ratelimit<'a>(&'a self, key: RatelimitKey) -> Option<&'a Ratelimit> { - self.config.ratelimits.inner.get(&key) - } - - fn authenticated(&self) -> bool { - false - } - - fn config<'a>(&'a self) -> &'a ClientConfig { - &self.config - } - - fn scopes(&self) -> Vec { - Vec::with_capacity(0) - } -} - -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 auth_domain<'a>(&'a self) -> &'a str { - use self::ClientType::*; - match self.inner.as_ref() { - Unauth(inner) => inner.auth_domain(), - Auth(inner) => inner.auth_domain(), - } - } - - fn config<'a>(&'a self) -> &'a ClientConfig { - use self::ClientType::*; - match self.inner.as_ref() { - Unauth(inner) => inner.config(), - Auth(inner) => inner.config(), - } - } - - fn ratelimit<'a>(&'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 { - use self::ClientType::*; - match self.inner.as_ref() { - Unauth(inner) => inner.scopes(), - Auth(inner) => inner.scopes(), - } - } -} - -/*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 auth_domain<'a>(&'a self) -> &'a str { - match self.previous.inner.as_ref() { - ClientType::Auth(auth) => auth.auth_domain(), - ClientType::Unauth(unauth) => unauth.auth_domain(), - } - } - - fn config<'a>(&'a self) -> &'a ClientConfig { - match self.previous.inner.as_ref() { - ClientType::Auth(auth) => auth.config(), - ClientType::Unauth(unauth) => unauth.config(), - } - } - - fn ratelimit<'a>(&'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 { - let auth = self.auth_state.lock().expect("Auth Lock is poisoned"); - auth.scopes.clone() - } -} - -#[derive(Clone, PartialEq)] -enum AuthState { - Unauth, - Auth, -} - -struct AuthStateRef { - token: Option, - scopes: Vec, - state: AuthState, -} - -impl Client { - pub fn new(id: &str, config: ClientConfig, version: Version) -> Client { - let client = ReqwestClient::new(); - Client { - inner: Arc::new( - ClientType::Unauth(UnauthClient { - id: id.to_owned(), - config: config, - version: version, - })) - } - } - - fn secret<'a>(&'a self) -> Option<&'a str> { - use self::ClientType::*; - match self.inner.as_ref() { - Unauth(_) => None, - Auth(inner) => Some(&inner.secret), - } - } - - fn version(&self) -> Version { - use self::ClientType::*; - match self.inner.as_ref() { - Unauth(inner) => inner.version.clone(), - Auth(inner) => inner.previous.version(), - } - } - - fn reqwest(&self) -> ReqwestClient { - use self::ClientType::*; - match self.inner.as_ref() { - Unauth(inner) => inner.config.reqwest.clone(), - Auth(inner) => inner.previous.reqwest(), - } - } - - fn send(&self, builder: RequestBuilder) -> Box + Send> { - if let Some(test_config) = &self.config().test_config { - let config: &mut TestConfigRef = &mut test_config.inner.lock().expect("Test Config poisoned"); - println!("{}", config.responses.len()); - config.requests.push(builder.build()); - let res = config.responses.pop().expect("Ran out of test responses!"); - Box::new(futures::future::ok(res)) - } else { - Box::new(builder.send()) - } - } - - /* 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 - * as other requests. - * - * Clients created with 'new' are bottom clients and calls - * to authenticate stack an authed client on top - */ - fn get_bottom_client(&self) -> Client { - match self.inner.as_ref() { - ClientType::Auth(inner) => inner.previous.get_bottom_client(), - ClientType::Unauth(_) => self.clone(), - } - } - - fn apply_standard_headers(&self, request: RequestBuilder) - -> RequestBuilder - { - 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, - }; - match self.version() { - Version::Helix => { - - let client_header = header::HeaderValue::from_str(self.id()).unwrap(); - - let request = - 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 }; - - request.header("Client-ID", client_header) - }, - Version::Kraken => { - let client_header = header::HeaderValue::from_str(self.id()).unwrap(); - let accept_header = header::HeaderValue::from_str(KRAKEN_ACCEPT).unwrap(); - - let request = request.header("Client-ID", client_header); - let request = request.header("Accept", accept_header); - let request = if let Some(token) = token { - let value = "OAuth ".to_owned() + &token; - let token_header = header::HeaderValue::from_str(&value).unwrap(); - request.header("Authorization", token_header) - } else {request}; - - request - } - } - } -} - - -use reqwest::r#async::{RequestBuilder}; -use reqwest::header; - - -pub struct AuthClientBuilder { - scopes: HashSet, - secret: String, - token: Option, - client: Client, - /*If the user supplies a token, - * then we can skip fetching it from the server and are authenticated - */ -} - -impl AuthClientBuilder { - pub fn new(client: Client, secret: &str) -> AuthClientBuilder { - AuthClientBuilder { - scopes: HashSet::new(), - client: client, - secret: secret.to_owned(), - token: None, - } - } - - pub fn build(self) -> Client { - let auth_state = if self.token.is_some() { AuthState::Auth } else { AuthState::Unauth }; - let old_client = self.client; - Client { - inner: Arc::new(ClientType::Auth( - AuthClient { - secret: self.secret, - auth_barrier: Barrier::new(), - auth_state: Mutex::new ( - AuthStateRef { - token: self.token, - scopes: Vec::new(), - state: auth_state, - }), - previous: old_client, - })) - } - } - - pub fn scope(mut self, scope: Scope) -> AuthClientBuilder { - let scopes = &mut self.scopes; - scopes.insert(scope); - self - } - - pub fn scopes(mut self, scopes: Vec) -> AuthClientBuilder { - let _scopes = &mut self.scopes; - for scope in scopes { - _scopes.insert(scope); - } - self - } - - pub fn token(mut self, token: &str) -> AuthClientBuilder { - self.token.replace(token.to_owned()); - self - } -} - -use std::collections::BTreeMap; -use reqwest::Method; - -struct RequestRef { - url: String, - params: BTreeMap, - client: Client, - ratelimit: Option, - method: Method, -} - -impl RequestRef { - pub fn new(url: String, - params: BTreeMap<&str, &dyn ToString>, - client: Client, - method: Method, - ratelimit: Option, - ) -> RequestRef - { - let mut owned_params = BTreeMap::new(); - for (key, value) in params { - owned_params.insert(key.to_string(), value.to_string()); - } - - RequestRef { - url: url, - params: owned_params, - client: client, - method: method, - ratelimit: ratelimit, - } - } -} - -enum RequestState { - SetupRequest, - SetupBarriers, - WaitAuth(WaiterState), - SetupRatelimit, - WaitLimit(WaiterState), - WaitRequest, - PollParse(Box + Send>), -} - -pub struct ApiRequest { - inner: Arc, - state: RequestState, - attempt: u32, - max_attempts: u32, - pagination: Option, -} - -enum IterableApiRequestState { - Start, - PollInner(ApiRequest), - Finished, -} - -pub struct IterableApiRequest { - inner: Arc, - state: IterableApiRequestState, -} - -impl ApiRequest { - - pub fn new(url: String, - params: BTreeMap<&str, &dyn ToString>, - client: Client, - method: Method, - ratelimit: Option, - ) -> ApiRequest - { - let max_attempts = client.config().max_retrys; - ApiRequest { - inner: Arc::new(RequestRef::new(url, params, client, method, ratelimit)), - state: RequestState::SetupRequest, - attempt: 0, - max_attempts, - pagination: None, - } - } -} - -impl IterableApiRequest { - - pub fn new(url: String, - params: BTreeMap<&str, &dyn ToString>, - client: Client, - method: Method, - ratelimit: Option - ) -> IterableApiRequest - { - let request_ref = - Arc::new(RequestRef::new(url, params, client, method, ratelimit)); - - IterableApiRequest { - inner: request_ref, - state: IterableApiRequestState::Start, - } - } -} - - -pub struct RatelimitWaiter { - limit: Ratelimit, -} - -#[derive(Clone)] -pub struct Ratelimit { - inner: Arc>, - barrier: Barrier, -} - -impl Ratelimit { - pub fn new(limit: i32, - header_limit: &str, - header_remaining: &str, - header_reset: &str) - -> Ratelimit - { - Ratelimit { - inner: Arc::new( - Mutex::new( - RatelimitRef { - limit: limit, - remaining: limit, - inflight: 0, - reset: None, - header_limit: header_limit.to_owned(), - header_remaining: header_remaining.to_owned(), - header_reset: header_reset.to_owned(), - } - ) - ), - barrier: Barrier::new(), - } - } -} - - - -#[derive(Debug, Clone)] -pub struct RatelimitRef { - limit: i32, - remaining: i32, - inflight: i32, - reset: Option, - header_limit: String, - header_remaining: String, - header_reset: String, -} - - -impl RatelimitRef { - pub fn update_from_headers(&mut self, headers: &reqwest::header::HeaderMap) { - let maybe_limit = - headers - .get(&self.header_limit) - .and_then(|x| x.to_str().ok()) - .and_then(|x| x.parse::().ok()); - - if let Some(limit) = maybe_limit { - self.limit = limit; - } - - let maybe_remaining = - headers - .get(&self.header_remaining) - .and_then(|x| x.to_str().ok()) - .and_then(|x| x.parse::().ok()); - - if let Some(limit) = maybe_remaining { - self.remaining = limit; - } - - let maybe_reset = - headers - .get(&self.header_reset) - .and_then(|x| x.to_str().ok()) - .and_then(|x| x.parse::().ok()); - - if let Some(reset) = maybe_reset { - self.reset = Some(reset); - } - } -} - -use crate::sync::barrier::Barrier; -use crate::sync::waiter::Waiter; - -struct WaiterState { - polling: bool, - shared_future: Option<(Shared + Send>>)>, - waiter: W, - barrier: Barrier, -} - -impl WaiterState { - fn new(waiter: W, barrier: &Barrier) -> WaiterState { - WaiterState { - polling: false, - shared_future: None, - waiter: waiter, - barrier: barrier.clone(), - } - } -} - -impl Future for WaiterState { - type Item = ::Item; - type Error = ::Error; - - fn poll(&mut self) -> Poll { - loop { - let blocked = self.waiter.blocked(); - if blocked && !self.polling { - let fut = self.barrier.condition(&self.waiter); - self.shared_future = Some(fut); - self.polling = true; - } else if blocked || self.polling { - let f = self.shared_future.as_mut().unwrap(); - try_ready!(f.poll()); - self.polling = false; - } else { - return Ok(Async::Ready(::Item::default())); - } - } - } -} - - -struct AuthWaiter { - waiter: Client, -} - -impl Waiter for AuthWaiter { - type Item = (); - type Error = ConditionError; - - fn blocked(&self) -> bool { - 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 + Send>> { - /* If a secret is not provided then just immediately return */ - let secret = self.waiter.secret().unwrap(); - let bottom_client = self.waiter.get_bottom_client(); - let client = self.waiter.clone(); - - let auth_future = - bottom_client - .auth() - .client_credentials(secret) - .map(move |credentials| { - 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()); - if let Some(scopes) = credentials.scope { - for scope in scopes { auth.scopes.push(scope) } - } - } - () - }) - .map_err(|err| err.into()); - - Future::shared(Box::new(auth_future)) - } -} - -impl Waiter for RatelimitWaiter { - type Item = (); - type Error = ConditionError; - - fn blocked(&self) -> bool { - let limits = self.limit.inner.lock().unwrap(); - limits.remaining - limits.inflight <= 0 - } - - fn condition(&self) - -> Shared + Send>> - { - /*TODO: Really basic for now*/ - use futures_timer::Delay; - use std::time::Duration; - let limits = self.limit.clone(); - Future::shared(Box::new( - Delay::new(Duration::from_secs(60)) - .map(move |_res| { - let mut limits = limits.inner.lock().unwrap(); - limits.remaining = limits.limit; - () - }) - .map_err(|err| Error::from(err).into()) - )) - } -} - -/* Macro ripped directly from try_ready and simplies retries if any error occurs - * and there are remaning retry attempt - */ -#[macro_export] -macro_rules! retry_ready { - ($s:expr, $e:expr) => (match $e { - Ok(futures::prelude::Async::Ready(t)) => t, - Ok(futures::prelude::Async::NotReady) => return Ok(futures::prelude::Async::NotReady), - Err(e) => { - if $s.attempt < $s.max_attempts { - $s.attempt += 1; - $s.state = RequestState::SetupBarriers; - continue; - } else { - return Err(e.into()); - } - } - }) -} - -use futures::Stream; - -impl Stream for IterableApiRequest { - type Item = T; - type Error = Error; - - fn poll(&mut self) -> Poll, Self::Error> { - loop { - match &mut self.state { - IterableApiRequestState::Start => { - self.state = - IterableApiRequestState::PollInner( - ApiRequest { - inner: self.inner.clone(), - state: RequestState::SetupRequest, - attempt: 0, - max_attempts: self.inner.client.config().max_retrys, - pagination: None - }); - }, - IterableApiRequestState::PollInner(request) => { - let f = request as &mut Future; - match f.poll() { - Err(err) => { - self.state = IterableApiRequestState::Finished; - return Err(err); - }, - Ok(state) => { - match state { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(res) => { - let cursor = res.cursor(); - match cursor { - Some(cursor) => { - self.state = IterableApiRequestState::PollInner( - ApiRequest { - inner: self.inner.clone(), - state: RequestState::SetupRequest, - attempt: 0, - max_attempts: self.inner.client.config().max_retrys, - pagination: Some(cursor.to_owned()), - }); - }, - None => { - self.state = IterableApiRequestState::Finished; - } - } - return Ok(Async::Ready(Some(res))); - } - } - } - } - }, - IterableApiRequestState::Finished => { - return Ok(Async::Ready(None)); - } - } - } - } -} - -impl Future for ApiRequest { - type Item = T; - type Error = Error; - - fn poll(&mut self) -> Poll { - loop { - match &mut self.state { - RequestState::SetupRequest => { - self.attempt = 0; - self.state = RequestState::SetupBarriers; - } - RequestState::SetupBarriers => { - match self.inner.client.inner.as_ref() { - ClientType::Auth(inner) => { - let waiter = AuthWaiter { - waiter: self.inner.client.clone(), - }; - - let f = WaiterState::new(waiter, - &inner.auth_barrier); - self.state = RequestState::WaitAuth(f); - }, - ClientType::Unauth(_) => { - self.state = RequestState::SetupRatelimit; - } - } - }, - RequestState::WaitAuth(auth) => { - let _waiter = retry_ready!(self, auth.poll()); - 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: ratelimit.clone(), - }; - let f = WaiterState::new(waiter, - &barrier); - self.state = RequestState::WaitLimit(f); - }, - None => { - self.state = RequestState::WaitRequest; - } - } - }, - RequestState::WaitLimit(limit) => { - let _waiter = retry_ready!(self, limit.poll()); - self.state = RequestState::WaitRequest; - }, - RequestState::WaitRequest => { - let client = self.inner.client.clone(); - let c_ref = &client; - let reqwest = client.reqwest(); - - let limits = - self.inner.ratelimit.as_ref().and_then(|key| { - c_ref.ratelimit(key.clone()) - }); - - if let Some(limits) = limits { - let mut mut_limits = limits.inner.lock().unwrap(); - mut_limits.inflight = mut_limits.inflight + 1; - } - - let mut builder = reqwest.request(self.inner.method.clone(), &self.inner.url); - builder = client.apply_standard_headers(builder); - builder = builder.query(&self.inner.params); - builder = - if let Some(cursor) = &self.pagination { - builder.query(&[("after", cursor)]) - } else { - builder - }; - - - let ratelimit_key = self.inner.ratelimit.clone(); - let client_cloned = client.clone(); - /* - Allow testing by capturing the request and returning a predetermined response - If testing is set in the client config then `Pending` is captured and saved and a future::ok(Resposne) is returned. - */ - let f = - client.send(builder) - .then(move |result| { - trace!("[TWITCH_API] {:?}", result); - if let Some(ratelimit_key) = ratelimit_key { - if let Some(limits) = client_cloned.ratelimit(ratelimit_key) { - let mut mut_limits = limits.inner.lock().unwrap(); - mut_limits.inflight = mut_limits.inflight - 1; - } - } - result - }) - .map_err(|err| err.into()) - .and_then(|mut response| { - let status = response.status(); - if status.is_success() { - Either::A( - response.json().map_err(|err| Error::from(err)).and_then(|json| { - trace!("[TWITCH_API] {}", json); - serde_json::from_value(json).map_err(|err| err.into()) - }) - ) - } else { - Either::B( - response.json::() - .then(|res| { - match res { - Ok(message) => futures::future::err(Some(message)), - Err(_err) => futures::future::err(None) - } - }) - .map_err(move |maybe_message| { - let status = response.status(); - if status == 401 || status == 403 { - Error::auth_error(maybe_message) - } else if status == 429 { - Error::ratelimit_error(maybe_message) - } else { - Error::auth_error(maybe_message) - } - }) - ) - } - }); - self.state = RequestState::PollParse(Box::new(f)); - }, - RequestState::PollParse(future) => { - let res = retry_ready!(self, future.poll()); - return Ok(Async::Ready(res)); - }, - } - } - } -} \ No newline at end of file diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index c291b5d..0000000 --- a/src/error.rs +++ /dev/null @@ -1,94 +0,0 @@ -use reqwest::Error as ReqwestError; -use futures::future::SharedError; -use std::convert::From; -use std::sync::Arc; -use serde_json::Error as JsonError; -use crate::models::Message; - -#[derive(Clone, Debug)] -pub struct ConditionError { - inner: Arc, -} - -impl From> for ConditionError { - fn from(other: SharedError) -> Self { - (*other).clone() - } -} - -impl From for ConditionError { - fn from(other: Error) -> Self { - ConditionError{ inner: Arc::new(other) } - } -} - -#[derive(Debug)] -enum Kind { - Reqwest(ReqwestError), - ConditionError(ConditionError), - Io(std::io::Error), - Json(JsonError), - AuthError(Option), - RatelimitError(Option), -} - -#[derive(Debug)] -pub struct Error { - inner: Kind -} - -impl Error { - pub fn auth_error(message: Option) -> Error { - Error { inner: Kind::AuthError(message) } - } - - pub fn ratelimit_error(message: Option) -> Error { - Error { inner: Kind::RatelimitError(message) } - } - - pub fn is_auth_error(&self) -> bool { - match &self.inner { - Kind::AuthError(_) => true, - Kind::ConditionError(condition) => condition.inner.is_auth_error(), - _ => false, - } - } - - pub fn is_ratelimit_error(&self) -> bool { - match &self.inner { - Kind::RatelimitError(_) => true, - Kind::ConditionError(condition) => condition.inner.is_ratelimit_error(), - _ => false, - } - } -} - - -impl From for Error { - - fn from(err: ReqwestError) -> Error { - Error { - inner: Kind::Reqwest(err) - } - } -} - -impl From for Error { - fn from(err: std::io::Error) -> Self { - Error { inner: Kind::Io(err) } - } -} - -impl From for Error { - - fn from(err: JsonError) -> Error { - Error { inner: Kind::Json(err) } - } -} - -impl From for Error { - - fn from(err: ConditionError) -> Error { - Error { inner: Kind::ConditionError(err) } - } -} diff --git a/src/helix/mod.rs b/src/helix/mod.rs deleted file mode 100644 index da24aa4..0000000 --- a/src/helix/mod.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::client::Client as GenericClient; -use crate::client::{Version, ClientConfig}; -use crate::client::ClientTrait; - -use crate::client::{HelixScope, Scope}; - -pub mod models; -pub mod namespaces; - - -#[derive(Clone)] -pub struct Client { - inner: GenericClient -} - -impl Client { - pub fn new(id: &str) -> Client { - let config = ClientConfig::default(); - Client { - inner: GenericClient::new(id, config, Version::Helix) - } - } - - pub fn new_with_config(id: &str, config: ClientConfig) -> Client { - Client { - inner: GenericClient::new(id, config, Version::Helix) - } - } - - pub fn authenticate(self, secret: &str) -> AuthClientBuilder { - AuthClientBuilder::new(self, secret) - } - - pub fn id<'a>(&'a self) -> &'a str { &self.inner.id() } - pub fn domain<'a>(&'a self) -> &'a str { &self.inner.domain() } - pub fn auth_domain<'a>(&'a self) -> &'a str { &self.inner.auth_domain() } - pub fn authenticated(&self) -> bool { self.inner.authenticated() } - - pub fn scopes(&self) -> Vec { - self.inner.scopes().into_iter().filter_map(|item| { - if let Scope::Helix(scope) = item { Some(scope) } else { None } - }).collect() - } -} - -use crate::client::AuthClientBuilder as GenericAuthClientBuilder; - -pub struct AuthClientBuilder { - inner: GenericAuthClientBuilder, -} - -impl AuthClientBuilder { - pub fn new(client: Client, secret: &str) -> AuthClientBuilder { - AuthClientBuilder { - inner: GenericAuthClientBuilder::new(client.inner, secret), - } - } - - pub fn build(self) -> Client { - let client = self.inner.build(); - Client { - inner: client - } - } - - pub fn scope(self, scope: HelixScope) -> AuthClientBuilder { - AuthClientBuilder { - inner: self.inner.scope(Scope::Helix(scope)) - } - } - - pub fn scopes(self, scopes: Vec) -> AuthClientBuilder { - AuthClientBuilder { - inner: self.inner.scopes(scopes.into_iter().map(|e| Scope::Helix(e)).collect()) - } - } - - pub fn token(self, token: &str) -> AuthClientBuilder { - AuthClientBuilder { - inner: self.inner.token(token) - } - } -} diff --git a/src/helix/models.rs b/src/helix/models.rs deleted file mode 100644 index 1e9f0ec..0000000 --- a/src/helix/models.rs +++ /dev/null @@ -1,118 +0,0 @@ -extern crate serde_json; -extern crate chrono; - -use url::Url; -use chrono::{DateTime, Utc}; -use crate::types::{UserId, VideoId, ChannelId}; - -use crate::client::PaginationTrait; - -#[derive(Debug, Deserialize, Serialize)] -pub struct DataContainer { - pub data: Vec -} - -impl PaginationTrait for DataContainer { - fn cursor<'a>(&'a self) -> Option<&'a str> { None } -} - -impl PaginationTrait for PaginationContainer { - fn cursor<'a>(&'a self) -> Option<&'a str> { - match self.pagination.as_ref() { - Some(cursor) => { - match cursor.cursor.as_ref() { - Some(cursor) => Some(cursor), - None => None, - } - }, - None => None - } - } -} - -impl PaginationTrait for Credentials { - fn cursor<'a>(&'a self) -> Option<&'a str> { None } -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct PaginationContainer { - pub data: Vec, - pub pagination: Option -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct Cursor { - pub cursor: Option -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct Video { - pub id: VideoId, - pub user_id: UserId, - pub user_name: String, - pub title: String, - pub description: String, - pub created_at: DateTime, - pub published_at: DateTime, - #[serde(with = "url_serde")] - pub url: Url, - /*FIXME: Serde will attempt to parse an empty string. - * In this case this should be None when thumbnail_url is an empty string - */ - //#[serde(with = "url_serde")] - pub thumbnail_url: String, //Option, - pub viewable: String, - pub view_count: i32, - pub language: String, - #[serde(rename = "type")] - pub video_type: String, - //Should be converted to a Duration - pub duration: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct User { - pub id: UserId, - pub login: String, - pub display_name: String, - #[serde(rename = "type")] - pub user_type: String, - pub broadcaster_type: String, - pub description: String, - #[serde(with = "url_serde")] - pub profile_image_url: Url, - //#[serde(with = "url_serde")] - pub offline_image_url: String, // Option, - pub view_count: u32, - pub email: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct Clip { - pub id: String, - #[serde(with = "url_serde")] - pub url: Url, - #[serde(with = "url_serde")] - pub embed_url: Url, - pub broadcaster_id: ChannelId, - pub broadcaster_name: String, - pub creator_id: UserId, - pub creator_name: String, - pub video_id: VideoId, - pub game_id: String, - pub language: String, - pub title: String, - pub created_at: DateTime, - #[serde(with = "url_serde")] - pub thumbnail_url: Url, - pub view_count: i32, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct Credentials { - pub access_token: String, - pub refresh_token: Option, - pub expires_in: u32, - pub scope: Option>, - pub token_type: String, -} diff --git a/src/helix/namespaces/auth.rs b/src/helix/namespaces/auth.rs deleted file mode 100644 index d006e2f..0000000 --- a/src/helix/namespaces/auth.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::*; -use crate::models::Credentials; -use crate::namespace::auth; - -pub struct Auth {} -type AuthNamespace = Namespace; - -impl AuthNamespace { - pub fn client_credentials(self, secret: &str) - -> ApiRequest { - auth::client_credentials(self.client.inner, &secret) - } -} - -impl Client { - pub fn auth(&self) -> AuthNamespace { - AuthNamespace::new(self) - } -} diff --git a/src/helix/namespaces/clips.rs b/src/helix/namespaces/clips.rs deleted file mode 100644 index c4595ce..0000000 --- a/src/helix/namespaces/clips.rs +++ /dev/null @@ -1,32 +0,0 @@ -use super::*; -use super::models::{DataContainer, Clip}; - -pub struct Clips {} -type ClipsNamespace = Namespace; - -impl ClipsNamespace { - pub fn clip(self, id: &S) -> ApiRequest> { - use self::clip; - clip(self.client, id) - } -} - -impl Client { - pub fn clips(&self) -> ClipsNamespace { - ClipsNamespace::new(self) - } -} - - -pub fn clip(client: Client, id: &S) - -> ApiRequest> -{ - let client = client.inner; - let url = - String::from("https://") + - client.domain() + "/helix/clips" + "?id=" + &id.to_string(); - - let params : ParamList = BTreeMap::new(); - - ApiRequest::new(url, params, client, Method::GET, Some(RatelimitKey::Default)) -} diff --git a/src/helix/namespaces/mod.rs b/src/helix/namespaces/mod.rs deleted file mode 100644 index 1d4239a..0000000 --- a/src/helix/namespaces/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::marker::PhantomData; - -pub use super::Client; -pub use crate::client::{RatelimitKey, ClientTrait, ApiRequest, IterableApiRequest, ParamList}; -pub use std::collections::BTreeMap; -pub use reqwest::Method; -pub use super::models; - -pub mod clips; -pub mod users; -pub mod videos; -pub mod auth; - -pub struct Namespace { - client: Client, - _type: PhantomData -} - -impl Namespace { - 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 deleted file mode 100644 index 2e922d6..0000000 --- a/src/helix/namespaces/users.rs +++ /dev/null @@ -1,44 +0,0 @@ -use super::*; -use super::models::{DataContainer, User}; -use std::string::ToString; - -pub struct Users {} -type UsersNamespace = Namespace; - -impl UsersNamespace { - pub fn users(self, ids: &[S], logins: &[S]) -> ApiRequest> { - use self::users; - users(self.client, ids, logins) - } -} - -impl Client { - pub fn users(&self) -> UsersNamespace { - UsersNamespace::new(self) - } -} - -pub fn users( - client: Client, - ids: &[S], - logins: &[S], - ) -> ApiRequest> { - let client = client.inner; - let url = - String::from("https://") + client.domain() + &String::from("/helix/users"); - - let mut params: BTreeMap<&str, &dyn ToString> = BTreeMap::new(); - for id in ids { - params.insert("id", id); - } - - for login in logins { - params.insert("login", login); - } - - ApiRequest::new(url, params, client, Method::GET, Some(RatelimitKey::Default)) -} - -pub fn authed_as(client: Client) -> ApiRequest> { - users(client, &[""], &[""]) -} diff --git a/src/helix/namespaces/videos.rs b/src/helix/namespaces/videos.rs deleted file mode 100644 index b603b8f..0000000 --- a/src/helix/namespaces/videos.rs +++ /dev/null @@ -1,75 +0,0 @@ -use super::*; -use super::models::{PaginationContainer, Video}; -use crate::types::{UserId, GameId, VideoId}; - - -pub struct Videos {} -type VideosNamespace = Namespace; - -impl VideosNamespace { - pub fn by_id(self, ids: &[S]) - -> IterableApiRequest> { - use self::by_id; - by_id(self.client, ids) - } - - pub fn by_user(self, user_id: &S) - -> IterableApiRequest> { - use self::by_user; - by_user(self.client, user_id) - } - - pub fn for_game(self, game_id: &S) - -> IterableApiRequest> { - use self::for_game; - for_game(self.client, game_id) - } -} - -impl Client { - - pub fn videos(&self) -> VideosNamespace { - VideosNamespace::new(self) - } -} - -pub fn by_id(client: Client, ids: &[S]) - -> IterableApiRequest> { - let client = client.inner; - let url = - String::from("https://") + client.domain() + &String::from("/helix/videos"); - - let mut params: ParamList = BTreeMap::new(); - for id in ids { - params.insert("id", id); - } - - IterableApiRequest::new(url, params, client, - Method::GET, Some(RatelimitKey::Default)) -} - -pub fn by_user(client: Client, user_id: &S) - -> IterableApiRequest> { - let client = client.inner; - let url = - String::from("https://") + client.domain() + &String::from("/helix/videos"); - - let mut params: ParamList = BTreeMap::new(); - params.insert("user_id", user_id); - - IterableApiRequest::new(url, params, client, - Method::GET, Some(RatelimitKey::Default)) -} - -pub fn for_game(client: Client, game_id: &S) - -> IterableApiRequest> { - let client = client.inner; - let url = - String::from("https://") + client.domain() + &String::from("/helix/videos"); - - let mut params: ParamList = BTreeMap::new(); - params.insert("game_id", game_id); - - IterableApiRequest::new(url, params, client, - Method::GET, Some(RatelimitKey::Default)) -} diff --git a/src/kraken/endpoints.rs b/src/kraken/endpoints.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/kraken/mod.rs b/src/kraken/mod.rs deleted file mode 100644 index 4046377..0000000 --- a/src/kraken/mod.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::client::Client as GenericClient; -use crate::client::{Version, ClientConfig}; -use crate::client::ClientTrait; -pub use super::types; - -use crate::client::{KrakenScope, Scope}; - -mod namespaces; -pub mod models; - -#[derive(Clone)] -pub struct Client { - inner: GenericClient -} - -impl Client { - pub fn new(id: &str) -> Client { - let config = ClientConfig::default(); - Client { - inner: GenericClient::new(id, config, Version::Kraken) - } - } - - pub fn new_with_config(id: &str, config: ClientConfig) -> Client { - Client { - inner: GenericClient::new(id, config, Version::Kraken) - } - } - - pub fn authenticate(self, secret: &str) -> AuthClientBuilder { - AuthClientBuilder::new(self, secret) - } - - pub fn id<'a>(&'a self) -> &'a str { &self.inner.id() } - pub fn domain<'a>(&'a self) -> &'a str { &self.inner.domain() } - pub fn auth_domain<'a>(&'a self) -> &'a str { &self.inner.auth_domain() } - pub fn authenticated(&self) -> bool { self.inner.authenticated() } - - pub fn scopes(&self) -> Vec { - self.inner.scopes().into_iter().filter_map(|item| { - if let Scope::Kraken(scope) = item { Some(scope) } else { None } - }).collect() - } -} - -use crate::client::AuthClientBuilder as GenericAuthClientBuilder; - -pub struct AuthClientBuilder { - inner: GenericAuthClientBuilder, -} - -impl AuthClientBuilder { - pub fn new(client: Client, secret: &str) -> AuthClientBuilder { - AuthClientBuilder { - inner: GenericAuthClientBuilder::new(client.inner, secret), - } - } - - pub fn build(self) -> Client { - let client = self.inner.build(); - Client { - inner: client - } - } - - pub fn scope(self, scope: Scope) -> AuthClientBuilder { - AuthClientBuilder { - inner: self.inner.scope(scope) - } - } - - pub fn scopes(self, scopes: Vec) -> AuthClientBuilder { - AuthClientBuilder { - inner: self.inner.scopes(scopes) - } - } - - pub fn token(self, token: &str) -> AuthClientBuilder { - AuthClientBuilder { - inner: self.inner.token(token) - } - } -} diff --git a/src/kraken/models.rs b/src/kraken/models.rs deleted file mode 100644 index 931f849..0000000 --- a/src/kraken/models.rs +++ /dev/null @@ -1,79 +0,0 @@ -extern crate serde_json; -extern crate chrono; -extern crate url; - -use url::Url; -use chrono::{DateTime, Utc}; -use super::types::{UserId, VideoId}; -use crate::client::PaginationTrait; - -#[derive(Debug, Deserialize, Serialize)] -pub struct User { - pub _id: String, - pub bio: String, - pub created_at: DateTime, - pub display_name: String, - #[serde(with = "url_serde")] - pub logo: Url, - pub name: String, - #[serde(rename = "type")] - pub user_type: String, - pub updated_at: DateTime, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct Clip { - pub slug: String, - pub tracking_id: String, - #[serde(with = "url_serde")] - pub url: Url, - #[serde(with = "url_serde")] - pub embed_url: Url, - pub embed_html: String, - pub broadcaster: UserData, - pub curator: UserData, - pub vod: Vod, - pub game: String, - pub language: String, - pub title: String, - pub views: i32, - pub duration: f32, - pub created_at: DateTime, - pub thumbnails: Thumbnails, -} - -impl PaginationTrait for Clip { - fn cursor<'a>(&'a self) -> Option<&'a str> { None } -} - -impl PaginationTrait for User { - fn cursor<'a>(&'a self) -> Option<&'a str> { None } -} - - -#[derive(Debug, Deserialize, Serialize)] -pub struct Thumbnails { - #[serde(with = "url_serde")] - pub medium: Url, - #[serde(with = "url_serde")] - pub small: Url, - #[serde(with = "url_serde")] - pub tiny: Url, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct UserData { - pub id: UserId, - pub name: String, - pub display_name: String, - #[serde(with = "url_serde")] - pub channel_url: Url, - pub logo: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct Vod { - pub id: VideoId, - #[serde(with = "url_serde")] - pub url: Url, -} diff --git a/src/kraken/namespaces/clips.rs b/src/kraken/namespaces/clips.rs deleted file mode 100644 index edde25b..0000000 --- a/src/kraken/namespaces/clips.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::collections::BTreeMap; -use super::super::models::{Clip}; -use super::super::Client; -use crate::client::{RatelimitKey, ClientTrait, ApiRequest}; -use reqwest::Method; -use super::Namespace; - -pub struct Clips {} -type ClipsNamespace = Namespace; - -impl ClipsNamespace { - pub fn clip(self, id: &str) -> ApiRequest { - use self::clip; - clip(self.client, id) - } -} - -impl Client { - - pub fn clips(&self) -> ClipsNamespace { - ClipsNamespace::new(self) - } -} - -pub fn clip(client: Client, id: &str) - -> ApiRequest -{ - let client = client.inner; - let url = String::from("https://") + client.domain() + "/kraken/clips/" + id; - let params = BTreeMap::new(); - - ApiRequest::new(url, params, client, Method::GET, Some(RatelimitKey::Default)) -} diff --git a/src/kraken/namespaces/mod.rs b/src/kraken/namespaces/mod.rs deleted file mode 100644 index d8a065f..0000000 --- a/src/kraken/namespaces/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::marker::PhantomData; -pub use super::models; -pub use super::Client; - -pub mod clips; -pub mod users; - -pub struct Namespace { - client: Client, - _type: PhantomData -} - -impl Namespace { - pub fn new(client: &Client) -> Self { - Namespace { - client: client.clone(), - _type: PhantomData, - } - } -} diff --git a/src/kraken/namespaces/users.rs b/src/kraken/namespaces/users.rs deleted file mode 100644 index d4adb8e..0000000 --- a/src/kraken/namespaces/users.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::collections::BTreeMap; -use super::super::models::{User}; -use super::super::Client; -use crate::client::{RatelimitKey, ClientTrait, ApiRequest}; -use reqwest::Method; -use super::Namespace; - -pub struct Users {} -type UsersNamespace = Namespace; - -impl UsersNamespace { - pub fn by_id(self, id: &str) -> ApiRequest { - use self::by_id; - by_id(self.client, id) - } -} - -impl Client { - pub fn users(&self) -> UsersNamespace { - UsersNamespace::new(self) - } -} - -pub fn by_id(client: Client, id: &str) - -> ApiRequest -{ - let client = client.inner; - let url = String::from("https://") + client.domain() + "/kraken/users/" + id; - let params = BTreeMap::new(); - - ApiRequest::new(url, params, client, Method::GET, Some(RatelimitKey::Default)) -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 35361ed..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![recursion_limit="128"] -#![feature(never_type)] -extern crate futures; -extern crate reqwest; -extern crate serde; -extern crate chrono; -extern crate serde_json; -#[macro_use] extern crate serde_derive; -#[macro_use] extern crate log; - -pub mod helix; -pub mod kraken; -pub mod types; -pub mod error; -mod sync; -pub mod namespace; -pub mod client; -pub mod models; - -pub use self::helix::Client as HelixClient; -pub use self::kraken::Client as KrakenClient; -pub use self::client::{ClientConfig, TestConfig}; diff --git a/src/models.rs b/src/models.rs deleted file mode 100644 index cc5442c..0000000 --- a/src/models.rs +++ /dev/null @@ -1,28 +0,0 @@ -extern crate serde_json; - -use crate::client::PaginationTrait; -use crate::client::Scope; - -impl PaginationTrait for Credentials { - fn cursor<'a>(&'a self) -> Option<&'a str> { None } -} - -impl PaginationTrait for Message { - fn cursor<'a>(&'a self) -> Option<&'a str> { None } -} - -#[derive(Debug, Deserialize)] -pub struct Credentials { - pub access_token: String, - pub refresh_token: Option, - pub expires_in: u32, - pub scope: Option>, - pub token_type: String, -} - -#[derive(Debug, Deserialize)] -pub struct Message { - pub error: Option, - pub message: String, - pub status: u32, -} \ No newline at end of file diff --git a/src/namespace/auth.rs b/src/namespace/auth.rs deleted file mode 100644 index ff835bf..0000000 --- a/src/namespace/auth.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::collections::BTreeMap; -use crate::models::Credentials; -use crate::client::Client; -use crate::client::{ClientTrait, ApiRequest}; -use reqwest::Method; -use std::marker::PhantomData; - -pub struct Namespace { - client: Client, - _type: PhantomData -} - -impl Namespace { - pub fn new(client: &Client) -> Self { - Namespace { - client: client.clone(), - _type: PhantomData, - } - } -} - -pub struct Auth {} -type AuthNamespace = Namespace; - -impl AuthNamespace { - pub fn client_credentials(self, secret: &str) - -> ApiRequest { - use self::client_credentials; - client_credentials(self.client, &secret.to_owned()) - } -} - -impl Client { - pub fn auth(&self) -> AuthNamespace { - AuthNamespace::new(self) - } -} - -//TODO: Implement scopes -pub fn client_credentials(client: Client, secret: &S) - -> ApiRequest { - - let url = - String::from("https://") + - client.auth_domain() + "/oauth2/token"; - - let mut params : BTreeMap<&str, &dyn ToString> = BTreeMap::new(); - let client_id = &client.id(); - 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.clone(), Method::POST, None) -} diff --git a/src/namespace/mod.rs b/src/namespace/mod.rs deleted file mode 100644 index 0e4a05d..0000000 --- a/src/namespace/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod auth; diff --git a/src/sync/barrier.rs b/src/sync/barrier.rs deleted file mode 100644 index c0f21b8..0000000 --- a/src/sync/barrier.rs +++ /dev/null @@ -1,47 +0,0 @@ -use super::waiter::Waiter; -use futures::prelude::*; -use std::sync::{Arc, Mutex}; -use futures::future::{Shared, SharedError}; - -use crate::error::ConditionError; - -#[derive(Clone)] -pub struct Barrier { - inner: Arc>, -} - -struct BarrierRef { - condition: Option + Send>>> -} - -impl Barrier { - - pub fn new() -> Barrier { - Barrier { - inner: Arc::new(Mutex::new( - BarrierRef { - condition: None, - })) - } - } - - pub fn condition(&self, waiter: &impl Waiter) - -> Shared + Send>> - { - let mut mut_barrier = self.inner.lock().unwrap(); - let maybe_condition = &mut mut_barrier.condition; - - let f = maybe_condition.get_or_insert_with(|| { - waiter.condition() - }); - - let f = - if let Some(_) = f.peek() { - let condition = waiter.condition(); - maybe_condition.replace(condition); - maybe_condition.as_ref().unwrap() - } else { f }; - f.clone() - } -} - diff --git a/src/sync/mod.rs b/src/sync/mod.rs deleted file mode 100644 index 74e4235..0000000 --- a/src/sync/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod barrier; -pub mod waiter; diff --git a/src/sync/waiter.rs b/src/sync/waiter.rs deleted file mode 100644 index 1005e3d..0000000 --- a/src/sync/waiter.rs +++ /dev/null @@ -1,12 +0,0 @@ -use futures::Future; -use futures::future::{Shared, SharedError}; -use crate::error::ConditionError; - -pub trait Waiter { - type Item: Default; - type Error: From>; - - fn blocked(&self) -> bool; - fn condition(&self) - -> Shared + Send>>; -} diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index e8ae2a3..0000000 --- a/src/types.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::convert::AsRef; -use std::cmp::{Eq, PartialEq}; -use std::marker::PhantomData; -use std::convert::Into; -use std::fmt; -use std::fmt::{Debug, Formatter}; - -/* Used for Id's that can be interpreted as integers but aren't returned as - * an int by Twitch's API. (Maybe to allow a quick switch to a different representation - * without breaking the json schema?) - * - * Don't Implement Display for StringID since it would allow comparisions between - * different StringId types - */ - -pub struct User {} -pub struct Video {} -pub struct Game {} -pub struct Clip {} - -pub type UserId = StringId; -pub type ChannelId = UserId; -pub type VideoId = StringId