Skip to main content

fyuz/
pagination.rs

1//! Auto-pagination for `GET /tokens`.
2
3use std::collections::VecDeque;
4
5use crate::client::FyuzClient;
6use crate::error::Result;
7use crate::models::Token;
8use crate::params::ListTokensParams;
9
10/// Walks every page of `GET /tokens`, one request at a time.
11///
12/// Built with [`FyuzClient::token_pages`]. The pager starts at the page in the
13/// [`ListTokensParams`] it was given (or page 1) and stops as soon as the
14/// server's `tokenCount` is covered or a page comes back empty — so it
15/// terminates even if the total shifts while you are walking it.
16///
17/// This is a plain async iterator rather than a `futures::Stream`: the crate
18/// has no `futures` dependency, and `while let Some(..) = pager.next_*().await?`
19/// reads the same.
20///
21/// ```no_run
22/// # async fn demo() -> Result<(), fyuz::Error> {
23/// use fyuz::{FyuzClient, ListTokensParams, SortDirection};
24///
25/// let client = FyuzClient::new()?;
26/// let mut pager = client.token_pages(
27///     &ListTokensParams::new()
28///         .order_type("marketcap")
29///         .order_flag(SortDirection::Desc)
30///         .page_size(100),
31/// );
32///
33/// // Page at a time…
34/// while let Some(page) = pager.next_page().await? {
35///     println!("{} tokens (of {:?} total)", page.len(), pager.total_count());
36/// }
37/// # Ok(())
38/// # }
39/// ```
40///
41/// ```no_run
42/// # async fn demo() -> Result<(), fyuz::Error> {
43/// # use fyuz::{FyuzClient, ListTokensParams};
44/// # let client = FyuzClient::new()?;
45/// // …or token at a time, with paging handled underneath.
46/// let mut pager = client.token_pages(&ListTokensParams::new());
47/// while let Some(token) = pager.next_token().await? {
48///     println!("{}", token.token_symbol);
49/// }
50/// # Ok(())
51/// # }
52/// ```
53#[derive(Debug)]
54pub struct TokenPager {
55    client: FyuzClient,
56    params: ListTokensParams,
57    next_page_number: i64,
58    fetched: i64,
59    total: Option<i64>,
60    buffer: VecDeque<Token>,
61    exhausted: bool,
62}
63
64impl TokenPager {
65    pub(crate) fn new(client: FyuzClient, params: ListTokensParams) -> Self {
66        let next_page_number = params.page_number.unwrap_or(1).max(1);
67        Self {
68            client,
69            params,
70            next_page_number,
71            fetched: 0,
72            total: None,
73            buffer: VecDeque::new(),
74            exhausted: false,
75        }
76    }
77
78    /// Total matching rows, as reported by the last page fetched. `None` before
79    /// the first request.
80    pub fn total_count(&self) -> Option<i64> {
81        self.total
82    }
83
84    /// Rows yielded so far.
85    pub fn fetched_count(&self) -> i64 {
86        self.fetched
87    }
88
89    /// Fetch the next page. Returns `None` once every page has been walked.
90    pub async fn next_page(&mut self) -> Result<Option<Vec<Token>>> {
91        if self.exhausted {
92            return Ok(None);
93        }
94
95        let params = self.params.clone().page_number(self.next_page_number);
96        let page = self.client.list_tokens(&params).await?;
97
98        self.total = Some(page.token_count);
99        self.next_page_number += 1;
100        self.fetched += page.token_list.len() as i64;
101
102        if page.token_list.is_empty() {
103            self.exhausted = true;
104            return Ok(None);
105        }
106
107        if self.fetched >= page.token_count {
108            self.exhausted = true;
109        }
110
111        Ok(Some(page.token_list))
112    }
113
114    /// Yield the next token, fetching a page whenever the buffer runs dry.
115    pub async fn next_token(&mut self) -> Result<Option<Token>> {
116        loop {
117            if let Some(token) = self.buffer.pop_front() {
118                return Ok(Some(token));
119            }
120            match self.next_page().await? {
121                Some(page) => self.buffer.extend(page),
122                None => return Ok(None),
123            }
124        }
125    }
126
127    /// Walk every remaining page and collect the lot.
128    ///
129    /// Bounded by the server's `tokenCount`; on a busy deployment that is still
130    /// thousands of rows and dozens of requests, so prefer
131    /// [`next_page`](Self::next_page) if you can stream instead.
132    pub async fn collect_all(&mut self) -> Result<Vec<Token>> {
133        let mut all = Vec::new();
134        all.extend(self.buffer.drain(..));
135        while let Some(page) = self.next_page().await? {
136            all.extend(page);
137        }
138        Ok(all)
139    }
140}