feat: Adapt internal SlackApiScrollableResponse API for more flexible paging (#344)

* feat: Adapt internal SlackApiScrollableResponse API for more flexible paging

* chore: Examples update
This commit is contained in:
Abdulla Abdurakhmanov 2026-03-11 21:38:25 +01:00 committed by GitHub
parent af8708f2e7
commit a92ec21d4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 84 additions and 15 deletions

View file

@ -117,6 +117,26 @@ async fn test_file_upload() -> Result<(), Box<dyn std::error::Error + Send + Syn
&complete_file_upload_resp
);
let all_channels_scroller = SlackApiConversationsListRequest::new().scroller();
let channels: Vec<SlackChannelInfo> = all_channels_scroller
.collect_items_stream(&session, Duration::from_millis(1000))
.await?;
println!("Number of channels: {:#?}", channels.len());
if let Some(general_channel_info) = channels
.into_iter()
.find(|c| c.flags.is_general.unwrap_or(false))
{
println!("General channel info: {:#?}", general_channel_info);
let files_list_scroller = SlackApiFilesListRequest::new()
.with_channel(general_channel_info.id)
.scroller();
let files: Vec<SlackFile> = files_list_scroller
.collect_items_stream(&session, Duration::from_millis(1000))
.await?;
println!("Number of files in general: {:#?}", files.len());
}
Ok(())
}

View file

@ -453,10 +453,11 @@ impl SlackApiScrollableResponse for SlackApiChatScheduledMessagesListResponse {
type CursorType = SlackCursorId;
type ResponseItemType = SlackApiChatScheduledMessageInfo;
fn next_cursor(&self) -> Option<&Self::CursorType> {
fn next_cursor(&self) -> Option<Self::CursorType> {
self.response_metadata
.as_ref()
.and_then(|rm| rm.next_cursor.as_ref())
.cloned()
}
fn scrollable_items<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::ResponseItemType> + 'a> {

View file

@ -426,10 +426,11 @@ impl SlackApiScrollableResponse for SlackApiConversationsHistoryResponse {
type CursorType = SlackCursorId;
type ResponseItemType = SlackHistoryMessage;
fn next_cursor(&self) -> Option<&Self::CursorType> {
fn next_cursor(&self) -> Option<Self::CursorType> {
self.response_metadata
.as_ref()
.and_then(|rm| rm.next_cursor.as_ref())
.cloned()
}
fn scrollable_items<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::ResponseItemType> + 'a> {
@ -540,10 +541,11 @@ impl SlackApiScrollableResponse for SlackApiConversationsListResponse {
type CursorType = SlackCursorId;
type ResponseItemType = SlackChannelInfo;
fn next_cursor(&self) -> Option<&Self::CursorType> {
fn next_cursor(&self) -> Option<Self::CursorType> {
self.response_metadata
.as_ref()
.and_then(|rm| rm.next_cursor.as_ref())
.cloned()
}
fn scrollable_items<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::ResponseItemType> + 'a> {
@ -590,10 +592,11 @@ impl SlackApiScrollableResponse for SlackApiConversationsMembersResponse {
type CursorType = SlackCursorId;
type ResponseItemType = SlackUserId;
fn next_cursor(&self) -> Option<&Self::CursorType> {
fn next_cursor(&self) -> Option<Self::CursorType> {
self.response_metadata
.as_ref()
.and_then(|rm| rm.next_cursor.as_ref())
.cloned()
}
fn scrollable_items<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::ResponseItemType> + 'a> {
@ -677,10 +680,11 @@ impl SlackApiScrollableResponse for SlackApiConversationsRepliesResponse {
type CursorType = SlackCursorId;
type ResponseItemType = SlackHistoryMessage;
fn next_cursor(&self) -> Option<&Self::CursorType> {
fn next_cursor(&self) -> Option<Self::CursorType> {
self.response_metadata
.as_ref()
.and_then(|rm| rm.next_cursor.as_ref())
.cloned()
}
fn scrollable_items<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::ResponseItemType> + 'a> {

View file

@ -2,11 +2,6 @@
//! Support for Slack Files API methods
//!
use rsb_derive::Builder;
use rvstruct::ValueStruct;
use serde::{Deserialize, Serialize, Serializer};
use serde_with::skip_serializing_none;
use crate::api::{
SlackApiUsersConversationsRequest, SlackApiUsersConversationsResponse,
SlackApiUsersProfileSetRequest, SlackApiUsersProfileSetResponse,
@ -14,8 +9,15 @@ use crate::api::{
use crate::models::*;
use crate::multipart_form::FileMultipartData;
use crate::ratectl::*;
use crate::SlackClientSession;
use crate::{ClientResult, SlackClientHttpConnector};
use crate::{SlackApiScrollableRequest, SlackApiScrollableResponse, SlackClientSession};
use futures_util::future::BoxFuture;
use futures_util::FutureExt;
use rsb_derive::Builder;
use rvstruct::ValueStruct;
use serde::{Deserialize, Serialize, Serializer};
use serde_with::skip_serializing_none;
use tokio_stream::StreamExt;
impl<'a, SCHC> SlackClientSession<'a, SCHC>
where
@ -223,6 +225,46 @@ pub struct SlackApiFilesListPaging {
pub pages: Option<u32>,
}
impl<SCHC> SlackApiScrollableRequest<SCHC> for SlackApiFilesListRequest
where
SCHC: SlackClientHttpConnector + Send + Sync + Clone + 'static,
{
type ResponseType = SlackApiFilesListResponse;
type CursorType = u32;
type ResponseItemType = SlackFile;
fn with_new_cursor(&self, new_cursor: Option<&Self::CursorType>) -> Self {
self.clone().opt_page(new_cursor.cloned())
}
fn scroll<'a, 's>(
&'a self,
session: &'a SlackClientSession<'s, SCHC>,
) -> BoxFuture<'a, ClientResult<Self::ResponseType>> {
async move { session.files_list(self).await }.boxed()
}
}
impl SlackApiScrollableResponse for SlackApiFilesListResponse {
type CursorType = u32;
type ResponseItemType = SlackFile;
fn next_cursor(&self) -> Option<Self::CursorType> {
self.paging
.as_ref()
.into_iter()
.filter_map(|paging| match (paging.page, paging.pages) {
(Some(page), Some(pages)) if page < pages => Some(page + 1),
_ => None,
})
.next()
}
fn scrollable_items<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::ResponseItemType> + 'a> {
Box::new(self.files.iter())
}
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
pub struct SlackApiFilesUploadRequest {

View file

@ -233,10 +233,11 @@ impl SlackApiScrollableResponse for SlackApiUsersConversationsResponse {
type CursorType = SlackCursorId;
type ResponseItemType = SlackChannelInfo;
fn next_cursor(&self) -> Option<&Self::CursorType> {
fn next_cursor(&self) -> Option<Self::CursorType> {
self.response_metadata
.as_ref()
.and_then(|rm| rm.next_cursor.as_ref())
.cloned()
}
fn scrollable_items<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::ResponseItemType> + 'a> {
@ -284,10 +285,11 @@ impl SlackApiScrollableResponse for SlackApiUsersListResponse {
type CursorType = SlackCursorId;
type ResponseItemType = SlackUser;
fn next_cursor(&self) -> Option<&Self::CursorType> {
fn next_cursor(&self) -> Option<Self::CursorType> {
self.response_metadata
.as_ref()
.and_then(|rm| rm.next_cursor.as_ref())
.cloned()
}
fn scrollable_items<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::ResponseItemType> + 'a> {

View file

@ -80,7 +80,7 @@ pub trait SlackApiScrollableResponse {
type CursorType;
type ResponseItemType;
fn next_cursor(&self) -> Option<&Self::CursorType>;
fn next_cursor(&self) -> Option<Self::CursorType>;
fn scrollable_items<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::ResponseItemType> + 'a>;
}
@ -164,7 +164,7 @@ where
.scroll(session)
.map_ok(|res| {
self.last_response = Some(res.clone());
self.last_cursor = res.next_cursor().cloned();
self.last_cursor = res.next_cursor();
res
})
.await