use futures::Stream;
use serde::{Deserialize, Serialize};
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PageToken {
Page(u32),
Cursor(String),
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Page<T> {
pub page: u32,
pub results: Vec<T>,
pub has_next: bool,
pub next: Option<PageToken>,
pub total_pages: u32,
pub total_results: u32,
}
impl<T> Page<T> {
pub fn offset(page: u32, results: Vec<T>, total_pages: u32, total_results: u32) -> Self {
let has_next = page < total_pages;
Self {
page,
results,
has_next,
next: has_next.then(|| PageToken::Page(page.saturating_add(1))),
total_pages,
total_results,
}
}
pub fn with_has_next(
page: u32,
results: Vec<T>,
has_next: bool,
total_pages: u32,
total_results: u32,
) -> Self {
Self {
page,
results,
has_next,
next: has_next.then(|| PageToken::Page(page.saturating_add(1))),
total_pages,
total_results,
}
}
pub fn has_next_page(&self) -> bool {
self.has_next
}
pub fn next_page(&self) -> Option<u32> {
match &self.next {
Some(PageToken::Page(p)) => Some(*p),
Some(PageToken::Cursor(_)) => None,
None => None,
}
}
pub fn map<U, F: FnMut(T) -> U>(self, f: F) -> Page<U> {
Page {
page: self.page,
results: self.results.into_iter().map(f).collect(),
has_next: self.has_next,
next: self.next,
total_pages: self.total_pages,
total_results: self.total_results,
}
}
pub fn filter_map<U, F: FnMut(T) -> Option<U>>(self, f: F) -> Page<U> {
Page {
page: self.page,
results: self.results.into_iter().filter_map(f).collect(),
has_next: self.has_next,
next: self.next,
total_pages: self.total_pages,
total_results: self.total_results,
}
}
}
pub fn into_stream<T, E, F, Fut>(fetch_page: F) -> impl Stream<Item = Result<T, E>> + Send
where
T: Send + 'static,
E: Send + 'static,
F: Fn(u32) -> Fut + Send + 'static,
Fut: std::future::Future<Output = Result<Page<T>, E>> + Send + 'static,
{
async_stream::try_stream! {
let mut page = 1u32;
loop {
let response = fetch_page(page).await?;
let has_next = response.has_next_page();
let next = response.next_page();
for item in response.results {
yield item;
}
match next {
Some(n) if has_next => page = n,
_ => break,
}
}
}
}