diff options
author | David Blajda <blajda@hotmail.com> | 2018-12-27 22:03:23 +0000 |
---|---|---|
committer | David Blajda <blajda@hotmail.com> | 2018-12-27 22:03:23 +0000 |
commit | 678e3d3f28cb8594204dc5e2b7597ae66a4582c7 (patch) | |
tree | 55b563cfdc09dab8f18c1f4d688ebf0c2187985c | |
parent | cb1b144e48ee357a76f551433d4886f092d259c8 (diff) |
Use Id types for endpoints and implement kraken headers
-rw-r--r-- | src/bin/main.rs | 42 | ||||
-rw-r--r-- | src/client.rs | 49 | ||||
-rw-r--r-- | src/helix/models.rs | 7 | ||||
-rw-r--r-- | src/helix/namespaces/auth.rs | 31 | ||||
-rw-r--r-- | src/helix/namespaces/clips.rs | 17 | ||||
-rw-r--r-- | src/helix/namespaces/mod.rs | 7 | ||||
-rw-r--r-- | src/helix/namespaces/users.rs | 55 | ||||
-rw-r--r-- | src/helix/namespaces/videos.rs | 28 | ||||
-rw-r--r-- | src/kraken/endpoints.rs | 20 | ||||
-rw-r--r-- | src/kraken/mod.rs | 46 | ||||
-rw-r--r-- | src/kraken/models.rs | 5 | ||||
-rw-r--r-- | src/kraken/namespaces/clips.rs | 33 | ||||
-rw-r--r-- | src/kraken/namespaces/mod.rs | 19 | ||||
-rw-r--r-- | src/lib.rs | 4 | ||||
-rw-r--r-- | src/namespace/auth.rs | 3 | ||||
-rw-r--r-- | src/types.rs | 10 |
16 files changed, 199 insertions, 177 deletions
diff --git a/src/bin/main.rs b/src/bin/main.rs index ba1482a..15b7753 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -8,26 +8,32 @@ use futures::future::Future; use futures::Stream; use std::env; use twitch_api::HelixClient; +use twitch_api::KrakenClient; +use std::str::FromStr; + +use twitch_api::types::UserId; +use twitch_api::types::ClipId; + fn main() { dotenv::dotenv().unwrap(); let client_id = &env::var("TWITCH_API").unwrap(); - let client = HelixClient::new(client_id); - + let helix_client = HelixClient::new(client_id); + let kraken_client = KrakenClient::new(client_id); - let authed_client = client; /* .authenticate(&env::var("TWITCH_SECRET").unwrap()) .build(); */ - let clip = authed_client + let clip = helix_client .clips() - .clip(&"EnergeticApatheticTarsierThisIsSparta") + .clip(&ClipId::new("EnergeticApatheticTarsierThisIsSparta")) .map_err(|err| { println!("{:?}", err); () }); + /* let clip2 = authed_client .clips() @@ -36,27 +42,31 @@ fn main() { println!("{:?}", err); () }); + */ + + //use twitch_api::types::VideoId; + /* let videos = authed_client .videos() - .by_user("67955580") - .take(10) + .by_user(&UserId::from_str("19571641").unwrap()) + .take(1) .for_each(|collection| { println!("{:?}", collection); Ok(()) }) .map(|_| ()) .map_err(|err| {println!("{:?}", err); ()}); + */ - /* - let clip2 = client.kraken + let clip2 = kraken_client + .clips() .clip(&"EnergeticApatheticTarsierThisIsSparta") .map_err(|err| { println!("{:?}", err); () }); - */ /* Prevents tokio from **hanging** * since tokio::run blocks the current thread and waits for the entire runtime @@ -65,15 +75,16 @@ fn main() { */ //std::mem::drop(authed_client); tokio::run( - /* clip.join(clip2) .and_then(|(c1, c2)| { - println!("{:?} {:?}", c1, c2); + println!("{:?}", c1); + println!("__"); + println!("{:?}", c2); Ok((c1, c2)) }).and_then(move |_| { - authed_client + helix_client .clips() - .clip(&"EnergeticApatheticTarsierThisIsSparta") + .clip(&ClipId::new("EnergeticApatheticTarsierThisIsSparta")) .map(|_| ()) .map_err(|err| { println!("{:?}", err); @@ -82,7 +93,6 @@ fn main() { }) .map(|_| ()) .map_err(|_| ()) - */ - videos + /*videos*/ ); } diff --git a/src/client.rs b/src/client.rs index fd3af32..8f93e1e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -21,6 +21,8 @@ pub enum RatelimitKey { type RatelimitMap = HashMap<RatelimitKey, Ratelimit>; 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>; @@ -68,6 +70,7 @@ pub struct UnauthClient { id: String, reqwest: ReqwestClient, domain: String, + auth_domain: String, ratelimits: RatelimitMap, version: Version, } @@ -83,6 +86,7 @@ pub trait ClientTrait { fn id<'a>(&'a self) -> &'a str; 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; @@ -98,6 +102,10 @@ impl ClientTrait for UnauthClient { &self.domain } + fn auth_domain<'a>(&'a self) -> &'a str { + &self.auth_domain + } + fn ratelimit<'a>(&'a self, key: RatelimitKey) -> Option<&'a Ratelimit> { self.ratelimits.get(&key) } @@ -129,6 +137,14 @@ impl ClientTrait for Client { } } + 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 ratelimit<'a>(&'a self, key: RatelimitKey) -> Option<&'a Ratelimit> { use self::ClientType::*; match self.inner.as_ref() { @@ -170,6 +186,13 @@ impl ClientTrait for AuthClient { } } + 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 ratelimit<'a>(&'a self, key: RatelimitKey) -> Option<&'a Ratelimit> { match self.previous.inner.as_ref() { ClientType::Auth(auth) => auth.ratelimit(key), @@ -221,6 +244,7 @@ impl Client { id: id.to_owned(), reqwest: reqwest, domain: API_DOMAIN.to_owned(), + auth_domain: AUTH_DOMAIN.to_owned(), ratelimits: Self::default_ratelimits(), version: version, })) @@ -268,15 +292,15 @@ impl Client { 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 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(); @@ -290,6 +314,17 @@ impl Client { 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 } } diff --git a/src/helix/models.rs b/src/helix/models.rs index 47a1288..4c1566c 100644 --- a/src/helix/models.rs +++ b/src/helix/models.rs @@ -56,8 +56,11 @@ pub struct Video { pub published_at: DateTime<Utc>, #[serde(with = "url_serde")] pub url: Url, - #[serde(with = "url_serde")] - pub thumbnail_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<Url>, pub viewable: String, pub view_count: i32, pub language: String, diff --git a/src/helix/namespaces/auth.rs b/src/helix/namespaces/auth.rs index 1ad5c57..1900fbf 100644 --- a/src/helix/namespaces/auth.rs +++ b/src/helix/namespaces/auth.rs @@ -1,10 +1,6 @@ -use std::collections::BTreeMap; -use crate::helix::models::Credentials; -use super::super::Client; -const ID_DOMAIN: &'static str = "id.twitch.tv"; -use super::Namespace; -use crate::client::{ClientTrait, ApiRequest}; -use reqwest::Method; +use super::*; +use crate::models::Credentials; +use crate::namespace::auth; pub struct Auth {} type AuthNamespace = Namespace<Auth>; @@ -12,8 +8,7 @@ type AuthNamespace = Namespace<Auth>; impl AuthNamespace { pub fn client_credentials(self, secret: &str) -> ApiRequest<Credentials> { - use self::client_credentials; - client_credentials(self.client, secret) + auth::client_credentials(self.client.inner, secret) } } @@ -22,21 +17,3 @@ impl Client { AuthNamespace::new(self) } } - -//TODO: Implement scopes -pub fn client_credentials(client: Client, secret: &str) - -> ApiRequest<Credentials> { - - let client = client.inner; - let url = - String::from("https://") + - ID_DOMAIN + "/oauth2/token"; - - let mut params = BTreeMap::new(); - 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/helix/namespaces/clips.rs b/src/helix/namespaces/clips.rs index 28b66f7..1de2e33 100644 --- a/src/helix/namespaces/clips.rs +++ b/src/helix/namespaces/clips.rs @@ -1,36 +1,31 @@ -use std::collections::BTreeMap; -use super::super::models::{DataContainer, Clip}; -use super::super::Client; -use super::Namespace; - -use crate::client::{RatelimitKey, ClientTrait, ApiRequest}; +use super::*; +use super::models::{DataContainer, Clip}; +use crate::types::ClipId; pub struct Clips {} type ClipsNamespace = Namespace<Clips>; impl ClipsNamespace { - pub fn clip(self, id: &str) -> ApiRequest<DataContainer<Clip>> { + pub fn clip(self, id: &ClipId) -> ApiRequest<DataContainer<Clip>> { use self::clip; clip(self.client, id) } } impl Client { - pub fn clips(&self) -> ClipsNamespace { ClipsNamespace::new(self) } } -use reqwest::Method; -pub fn clip(client: Client, id: &str) +pub fn clip(client: Client, id: &ClipId) -> ApiRequest<DataContainer<Clip>> { let client = client.inner; let url = String::from("https://") + - client.domain() + "/helix/clips" + "?id=" + id; + client.domain() + "/helix/clips" + "?id=" + id.as_ref(); let params = BTreeMap::new(); diff --git a/src/helix/namespaces/mod.rs b/src/helix/namespaces/mod.rs index 1c0d08e..8adc1e3 100644 --- a/src/helix/namespaces/mod.rs +++ b/src/helix/namespaces/mod.rs @@ -1,5 +1,10 @@ use std::marker::PhantomData; -use super::Client; + +pub use super::Client; +pub use crate::client::{RatelimitKey, ClientTrait, ApiRequest, IterableApiRequest}; +pub use std::collections::BTreeMap; +pub use reqwest::Method; +pub use super::models; pub mod clips; pub mod users; diff --git a/src/helix/namespaces/users.rs b/src/helix/namespaces/users.rs index 3e4f1dd..20f2914 100644 --- a/src/helix/namespaces/users.rs +++ b/src/helix/namespaces/users.rs @@ -1,56 +1,45 @@ -use futures::future::Future; -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::Namespace; +use super::*; +use super::models::{DataContainer, User}; +use crate::types::UserId; 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) + pub fn users(self, ids: Vec<&UserId>, logins: Vec<&str>) + -> ApiRequest<DataContainer<User>> { + use self::users; + users(self.client, ids, logins) } - */ } -/* -impl Client { +impl Client { pub fn users(&self) -> UsersNamespace { UsersNamespace::new(self) } } -*/ -/* + pub fn users( client: Client, - id: Vec<&str>, - login: Vec<&str>, - ) -> impl Future<Item = DataContainer<User>, Error = reqwest::Error> { + ids: Vec<&UserId>, + logins: Vec<&str>, + ) -> ApiRequest<DataContainer<User>> { + let client = client.inner; let url = - String::from("https://") + &String::from(API_DOMAIN) + &String::from("/helix/users"); + String::from("https://") + client.domain() + &String::from("/helix/users"); let mut params = BTreeMap::new(); - for i in id { - params.insert("id", i); + for id in ids { + params.insert("id", id.as_ref()); } - for log in login { - params.insert("login", log); + for login in logins { + params.insert("login", login); } - let request = client.client().get(&url); - let request = client.apply_standard_headers(request); - let request = request.query(¶ms); + ApiRequest::new(url, params, client, Method::GET, Some(RatelimitKey::Default)) +} - request - .send() - .map(|mut res| { - res.json::<DataContainer<User>>() - }) - .and_then(|json| json) +pub fn authed_as(client: Client) -> ApiRequest<DataContainer<User>> { + users(client, Vec::with_capacity(0), Vec::with_capacity(0)) } -*/ diff --git a/src/helix/namespaces/videos.rs b/src/helix/namespaces/videos.rs index a8021f3..75b702e 100644 --- a/src/helix/namespaces/videos.rs +++ b/src/helix/namespaces/videos.rs @@ -1,29 +1,25 @@ -use super::super::models::{PaginationContainer, Video}; -use super::super::Client; -use super::Namespace; - -use crate::client::{ClientTrait, RatelimitKey, IterableApiRequest}; -use std::collections::BTreeMap; -use reqwest::Method; +use super::*; +use super::models::{PaginationContainer, Video}; +use crate::types::{UserId, GameId, VideoId}; pub struct Videos {} type VideosNamespace = Namespace<Videos>; impl VideosNamespace { - pub fn by_id(self, ids: Vec<&str>) + pub fn by_id(self, ids: Vec<&VideoId>) -> IterableApiRequest<PaginationContainer<Video>> { use self::by_id; by_id(self.client, ids) } - pub fn by_user(self, user_id: &str) + pub fn by_user(self, user_id: &UserId) -> IterableApiRequest<PaginationContainer<Video>> { use self::by_user; by_user(self.client, user_id) } - pub fn for_game(self, game_id: &str) + pub fn for_game(self, game_id: &GameId) -> IterableApiRequest<PaginationContainer<Video>> { use self::for_game; for_game(self.client, game_id) @@ -37,7 +33,7 @@ impl Client { } } -pub fn by_id(client: Client, ids: Vec<&str>) +pub fn by_id(client: Client, ids: Vec<&VideoId>) -> IterableApiRequest<PaginationContainer<Video>> { let client = client.inner; let url = @@ -45,34 +41,34 @@ pub fn by_id(client: Client, ids: Vec<&str>) let mut params = BTreeMap::new(); for id in ids { - params.insert("id", id); + params.insert("id", id.as_ref()); } IterableApiRequest::new(url, params, client, Method::GET, Some(RatelimitKey::Default)) } -pub fn by_user(client: Client, user_id: &str) +pub fn by_user(client: Client, user_id: &UserId) -> IterableApiRequest<PaginationContainer<Video>> { let client = client.inner; let url = String::from("https://") + client.domain() + &String::from("/helix/videos"); let mut params = BTreeMap::new(); - params.insert("user_id", user_id); + params.insert("user_id", user_id.as_ref()); IterableApiRequest::new(url, params, client, Method::GET, Some(RatelimitKey::Default)) } -pub fn for_game(client: Client, game_id: &str) +pub fn for_game(client: Client, game_id: &GameId) -> IterableApiRequest<PaginationContainer<Video>> { let client = client.inner; let url = String::from("https://") + client.domain() + &String::from("/helix/videos"); let mut params = BTreeMap::new(); - params.insert("game_id", game_id); + params.insert("game_id", game_id.as_ref()); IterableApiRequest::new(url, params, client, Method::GET, Some(RatelimitKey::Default)) diff --git a/src/kraken/endpoints.rs b/src/kraken/endpoints.rs index 7ae273c..e69de29 100644 --- a/src/kraken/endpoints.rs +++ b/src/kraken/endpoints.rs @@ -1,20 +0,0 @@ -use futures::Future; -use super::models::Clip; -use super::Client; - -use super::API_DOMAIN; - -impl Client { - pub fn clip(&self, id: &str) - -> impl Future<Item=Clip, Error=reqwest::Error> - { - let url = String::from("https://") + API_DOMAIN + "/kraken/clips/" + id; - let request = self.inner.client.get(&url); - let request = self.apply_standard_headers(request); - - request - .send() - .map(|mut res| res.json::<Clip>()) - .and_then(|json| json) - } -} diff --git a/src/kraken/mod.rs b/src/kraken/mod.rs index 59d00c0..0201295 100644 --- a/src/kraken/mod.rs +++ b/src/kraken/mod.rs @@ -1,53 +1,19 @@ -use reqwest::header; -use std::sync::Arc; -use reqwest::r#async::RequestBuilder; -use reqwest::r#async::Client as ReqwestClient; +use crate::client::Client as GenericClient; +use crate::client::Version; pub use super::types; -mod endpoints; -mod models; - - -const ACCEPT: &str = "application/vnd.twitchtv.v5+json"; -pub const API_DOMAIN: &str = "api.twitch.tv"; +mod namespaces; +pub mod models; #[derive(Clone)] pub struct Client { - inner: Arc<ClientRef>, -} - -struct ClientRef { - id: String, - client: ReqwestClient + inner: GenericClient } impl Client { pub fn new(id: &str) -> Client { Client { - inner: Arc::new(ClientRef { - id: id.to_owned(), - client: ReqwestClient::new(), - }) + inner: GenericClient::new(id, Version::Kraken) } } - - pub fn new_with_client(id: &str, client: ReqwestClient) -> Client { - Client { - inner: Arc::new(ClientRef { - id: id.to_owned(), - client: client, - }) - } - } - - fn apply_standard_headers(&self, request: RequestBuilder) - -> RequestBuilder - { - let client_header = header::HeaderValue::from_str(&self.inner.id).unwrap(); - let accept_header = header::HeaderValue::from_str(ACCEPT).unwrap(); - - request - .header("Accept", accept_header) - .header("Client-ID", client_header) - } } diff --git a/src/kraken/models.rs b/src/kraken/models.rs index 4863641..8b5b6d6 100644 --- a/src/kraken/models.rs +++ b/src/kraken/models.rs @@ -5,6 +5,7 @@ extern crate url; use url::Url; use chrono::{DateTime, Utc}; use super::types::{UserId, VideoId}; +use crate::client::PaginationTrait; #[derive(Debug, Deserialize)] pub struct Clip { @@ -27,6 +28,10 @@ pub struct Clip { pub thumbnails: Thumbnails, } +impl PaginationTrait for Clip { + fn cursor<'a>(&'a self) -> Option<&'a str> { None } +} + #[derive(Debug, Deserialize)] pub struct Thumbnails { diff --git a/src/kraken/namespaces/clips.rs b/src/kraken/namespaces/clips.rs new file mode 100644 index 0000000..edde25b --- /dev/null +++ b/src/kraken/namespaces/clips.rs @@ -0,0 +1,33 @@ +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<Clips>; + +impl ClipsNamespace { + pub fn clip(self, id: &str) -> ApiRequest<Clip> { + 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<Clip> +{ + 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 new file mode 100644 index 0000000..5f4b421 --- /dev/null +++ b/src/kraken/namespaces/mod.rs @@ -0,0 +1,19 @@ +use std::marker::PhantomData; +pub use super::models; +pub use super::Client; + +pub mod clips; + +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, + } + } +} @@ -10,10 +10,10 @@ pub mod helix; pub mod kraken; pub mod types; pub mod error; -pub mod sync; +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::kraken::Client as KrakenClient; diff --git a/src/namespace/auth.rs b/src/namespace/auth.rs index 577aa92..c7a0c67 100644 --- a/src/namespace/auth.rs +++ b/src/namespace/auth.rs @@ -1,7 +1,6 @@ use std::collections::BTreeMap; use crate::models::Credentials; use crate::client::Client; -const ID_DOMAIN: &'static str = "id.twitch.tv"; use crate::client::{ClientTrait, ApiRequest}; use reqwest::Method; use std::marker::PhantomData; @@ -43,7 +42,7 @@ pub fn client_credentials(client: Client, secret: &str) let url = String::from("https://") + - ID_DOMAIN + "/oauth2/token"; + client.auth_domain() + "/oauth2/token"; let mut params = BTreeMap::new(); params.insert("client_id", client.id()); diff --git a/src/types.rs b/src/types.rs index 09f2063..db8f0cb 100644 --- a/src/types.rs +++ b/src/types.rs @@ -11,11 +11,13 @@ use std::marker::PhantomData; pub struct User {} pub struct Video {} +pub struct Game {} pub type UserId = IntegerId<User>; pub type ChannelId = UserId; pub type VideoId = IntegerId<Video>; pub type ClipId = Id; +pub type GameId = IntegerId<Game>; #[derive(Clone)] pub struct IntegerId<T> { @@ -115,6 +117,14 @@ pub struct Id { inner: String } +impl Id { + pub fn new(id: &str) -> Id { + Id { + inner: id.to_owned(), + } + } +} + impl AsRef<str> for Id { fn as_ref(&self) -> &str { |