twitch-rs / twitch_api

Rust library for talking with the Twitch API aka. "Helix", TMI and more! Use Twitch endpoints fearlessly!
Apache License 2.0
150 stars 33 forks source link

Improve helix::client_ext helpers & types in collections #359

Closed Emilgardis closed 7 months ago

Nerixyz commented 7 months ago

It's good to include more helpers, esp. for easy pagination.

Seems like we only need to adjust the example:

diff --git a/examples/followed_streams.rs b/examples/followed_streams.rs
index d702260109..b423dfa66c 100644
--- a/examples/followed_streams.rs
+++ b/examples/followed_streams.rs
@@ -1,6 +1,9 @@
+use std::collections::HashMap;
+
 use futures::TryStreamExt;
-use twitch_api::helix::HelixClient;
+use twitch_api::helix::{games::get_games::Game, HelixClient};
 use twitch_oauth2::{AccessToken, UserToken};
+use twitch_types::CategoryId;

 fn main() {
     use std::error::Error;
@@ -36,7 +39,7 @@ async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>>
         .get_followed_streams(&token)
         .try_collect::<Vec<_>>()
         .await?;
-    let games = client
+    let games: HashMap<CategoryId, Game> = client
         .get_games_by_id(
             &streams
                 .iter()
@@ -44,6 +47,8 @@ async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>>
                 .collect::<Vec<_>>(),
             &token,
         )
+        .map_ok(|game| (game.id.clone(), game))
+        .try_collect()
         .await?;

     println!(
Nerixyz commented 7 months ago

...hmm I wonder if we can remove the boxing and drop the Unpin bound (or at least drop it where stuff isn't boxed):

diff --git a/src/helix/client/client_ext.rs b/src/helix/client/client_ext.rs
index 896454aceb..493bc55589 100644
--- a/src/helix/client/client_ext.rs
+++ b/src/helix/client/client_ext.rs
@@ -154,16 +154,13 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
         &'client self,
         ids: &'client [&'client types::UserIdRef],
         token: &'client T,
-    ) -> impl futures::Stream<Item = Result<helix::streams::Stream, ClientError<C>>>
-           + Send
-           + Unpin
-           + 'client
+    ) -> impl futures::Stream<Item = Result<helix::streams::Stream, ClientError<C>>> + Send + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
     {
         futures::stream::iter(ids.chunks(100).map(move |c| {
             let req = helix::streams::GetStreamsRequest::user_ids(c).first(100);
-            make_stream(req, token, self, std::collections::VecDeque::from)
+            make_stream(req, token, self, std::collections::VecDeque::from).boxed()
         }))
         .flatten_unordered(None)
     }
@@ -190,16 +187,13 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
         &'client self,
         logins: &'client [&'client types::UserNameRef],
         token: &'client T,
-    ) -> impl futures::Stream<Item = Result<helix::streams::Stream, ClientError<C>>>
-           + Send
-           + Unpin
-           + 'client
+    ) -> impl futures::Stream<Item = Result<helix::streams::Stream, ClientError<C>>> + Send + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
     {
         futures::stream::iter(logins.chunks(100).map(move |c| {
             let req = helix::streams::GetStreamsRequest::user_logins(c).first(100);
-            make_stream(req, token, self, std::collections::VecDeque::from)
+            make_stream(req, token, self, std::collections::VecDeque::from).boxed()
         }))
         .flatten_unordered(None)
     }
@@ -231,7 +225,7 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
         moderator_id: impl Into<&'client types::UserIdRef>,
         batch_size: impl Into<Option<usize>>,
         token: &'client T,
-    ) -> impl futures::Stream<Item = Result<helix::chat::Chatter, ClientError<C>>> + Send + Unpin + 'client
+    ) -> impl futures::Stream<Item = Result<helix::chat::Chatter, ClientError<C>>> + Send + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
     {
@@ -266,10 +260,7 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
         &'client self,
         query: impl Into<&'client str>,
         token: &'client T,
-    ) -> impl futures::Stream<Item = Result<helix::search::Category, ClientError<C>>>
-           + Send
-           + Unpin
-           + 'client
+    ) -> impl futures::Stream<Item = Result<helix::search::Category, ClientError<C>>> + Send + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
     {
@@ -296,18 +287,14 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
     ///
     /// # Ok(()) }
     /// ```
-    pub fn search_channels<'b, T>(
+    pub fn search_channels<T>(
         &'client self,
-        query: impl Into<&'b str>,
+        query: impl Into<&'client str>,
         live_only: bool,
         token: &'client T,
-    ) -> impl futures::Stream<Item = Result<helix::search::Channel, ClientError<C>>>
-           + Send
-           + Unpin
-           + 'client
+    ) -> impl futures::Stream<Item = Result<helix::search::Channel, ClientError<C>>> + Send + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
-        'b: 'client,
     {
         let req = helix::search::SearchChannelsRequest::query(query.into()).live_only(live_only);
         make_stream(req, token, self, std::collections::VecDeque::from)
@@ -342,16 +329,14 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
     #[doc(hidden)]
     pub fn get_follow_relationships<'b, T>(
         &'client self,
-        to_id: impl Into<Option<&'b types::UserIdRef>>,
-        from_id: impl Into<Option<&'b types::UserIdRef>>,
+        to_id: impl Into<Option<&'client types::UserIdRef>>,
+        from_id: impl Into<Option<&'client types::UserIdRef>>,
         token: &'client T,
     ) -> impl futures::Stream<Item = Result<helix::users::FollowRelationship, ClientError<C>>>
            + Send
-           + Unpin
            + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
-        'b: 'client,
     {
         let mut req = helix::users::GetUsersFollowsRequest::empty();
         req.to_id = to_id.into().map(Cow::Borrowed);
@@ -386,10 +371,7 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
     pub fn get_followed_streams<T>(
         &'client self,
         token: &'client T,
-    ) -> impl futures::Stream<Item = Result<helix::streams::Stream, ClientError<C>>>
-           + Send
-           + Unpin
-           + 'client
+    ) -> impl futures::Stream<Item = Result<helix::streams::Stream, ClientError<C>>> + Send + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
     {
@@ -401,7 +383,7 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
             Err(e) => return futures::stream::once(async { Err(e) }).boxed(),
         };
         let req = helix::streams::GetFollowedStreamsRequest::user_id(user_id);
-        make_stream(req, token, self, std::collections::VecDeque::from)
+        make_stream(req, token, self, std::collections::VecDeque::from).boxed()
     }

     /// Get authenticated broadcasters' [subscribers](helix::subscriptions::BroadcasterSubscription)
@@ -429,7 +411,6 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
     ) -> impl futures::Stream<
         Item = Result<helix::subscriptions::BroadcasterSubscription, ClientError<C>>,
     > + Send
-           + Unpin
            + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
@@ -443,7 +424,7 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
         };
         // If this fails to compile due to missing implementation, make sure this crate and `twitch_oauth2` use the same version of `twitch_types`
         let req = helix::subscriptions::GetBroadcasterSubscriptionsRequest::broadcaster_id(user_id);
-        make_stream(req, token, self, std::collections::VecDeque::from)
+        make_stream(req, token, self, std::collections::VecDeque::from).boxed()
     }

     /// Get all moderators in a channel [Get Moderators](helix::moderation::GetModeratorsRequest)
@@ -465,14 +446,11 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
     ///
     /// # Ok(()) }
     /// ```
-    pub fn get_moderators_in_channel_from_id<'b: 'client, T>(
+    pub fn get_moderators_in_channel_from_id<T>(
         &'client self,
-        broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b,
+        broadcaster_id: impl types::IntoCow<'client, types::UserIdRef> + 'client,
         token: &'client T,
-    ) -> impl futures::Stream<Item = Result<helix::moderation::Moderator, ClientError<C>>>
-           + Send
-           + Unpin
-           + 'client
+    ) -> impl futures::Stream<Item = Result<helix::moderation::Moderator, ClientError<C>>> + Send + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
     {
@@ -498,13 +476,12 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
     ///
     /// # Ok(()) }
     /// ```
-    pub fn get_banned_users_in_channel_from_id<'b: 'client, T>(
+    pub fn get_banned_users_in_channel_from_id<T>(
         &'client self,
-        broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b,
+        broadcaster_id: impl types::IntoCow<'client, types::UserIdRef> + 'client,
         token: &'client T,
     ) -> impl futures::Stream<Item = Result<helix::moderation::BannedUser, ClientError<C>>>
            + Send
-           + Unpin
            + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
@@ -586,7 +563,6 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
         token: &'client T,
     ) -> impl futures::Stream<Item = Result<helix::channels::FollowedBroadcaster, ClientError<C>>>
            + Send
-           + Unpin
            + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
@@ -772,14 +748,11 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
     ///
     /// # Ok(()) }
     /// ```
-    pub fn get_channel_schedule<'b: 'client, T>(
+    pub fn get_channel_schedule<T>(
         &'client self,
-        broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b,
+        broadcaster_id: impl types::IntoCow<'client, types::UserIdRef> + 'client,
         token: &'client T,
-    ) -> impl futures::Stream<Item = Result<helix::schedule::Segment, ClientError<C>>>
-           + Send
-           + Unpin
-           + 'client
+    ) -> impl futures::Stream<Item = Result<helix::schedule::Segment, ClientError<C>>> + Send + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
     {
@@ -863,7 +836,7 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
     where
         T: TwitchToken + Send + Sync + ?Sized,
     {
-        futures::stream::iter(emote_sets.chunks(25))
+        futures::stream::iter(emote_sets.chunks(100))
             .map(move |c| {
                 let req = helix::chat::GetEmoteSetsRequest::emote_set_ids(c);
                 futures::stream::once(self.req_get(req, token)).boxed()
@@ -1080,11 +1053,11 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
     }

     /// Get channel VIPs
-    pub fn get_vips_in_channel<'b: 'client, T>(
+    pub fn get_vips_in_channel<T>(
         &'client self,
-        broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b,
+        broadcaster_id: impl types::IntoCow<'client, types::UserIdRef> + 'client,
         token: &'client T,
-    ) -> impl futures::Stream<Item = Result<helix::channels::Vip, ClientError<C>>> + Send + Unpin + 'client
+    ) -> impl futures::Stream<Item = Result<helix::channels::Vip, ClientError<C>>> + Send + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
     {
@@ -1314,7 +1287,6 @@ impl<'client, C: crate::HttpClient + Sync + 'client> HelixClient<'client, C> {
         token: &'client T,
     ) -> impl futures::Stream<Item = Result<helix::eventsub::EventSubSubscriptions, ClientError<C>>>
            + Send
-           + Unpin
            + 'client
     where
         T: TwitchToken + Send + Sync + ?Sized,
@@ -1465,7 +1437,7 @@ pub fn make_stream<
         + Sync
         + Copy
         + 'static,
-) -> std::pin::Pin<Box<dyn futures::Stream<Item = Result<Item, ClientError<C>>> + 'a + Send>>
+) -> impl futures::Stream<Item = Result<Item, ClientError<C>>> + 'a + Send
 where
     // FIXME: This clone is bad. I want to be able to return the data, but not in a way that limits the response to be Default
     // I also want to keep allocations low, so std::mem::take is perfect, but that makes get_next not work optimally.
@@ -1591,5 +1563,4 @@ where
             _ => todo!("failed to process request"),
         }
     })
-    .boxed()
 }

This does compile, but I'm not super sure if the removal of 'b is fine, although 'b: 'client and 'client: 'b are both required. Furthermore, we could remove boxing in the cases of methods like get_followed_streams, which could return a Result<impl Stream>.

Emilgardis commented 7 months ago

I have some unpushed changes to this branch incorporating https://github.com/twitch-rs/twitch_types/pull/55, I'll clean it up and push for a review

Emilgardis commented 7 months ago

i've pushed the changes

Emilgardis commented 7 months ago

final check wanted, think we can merge this