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(¶ms).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}