desmos_bindings/iter/page_iterator.rs
1//! Contains an iterator that lazily loads paginated data from the chain.
2
3use cosmwasm_std::StdResult;
4
5/// Type alias of a function that fetch a page given as first argument an optional key
6/// that references the next page to fetch and as second argument how many items to fetch.
7/// If the first argument is None means that this function should fetch the first page.
8pub type Fetcher<'a, T, K> = Box<dyn Fn(Option<K>, u64) -> StdResult<Page<T, K>> + 'a>;
9
10/// A page of elements.
11pub struct Page<T, K> {
12 /// List of elements present in the page.
13 pub items: Vec<T>,
14 /// Optional key to the next page to fetch.
15 /// If this is None means that there aren't other pages to fetch.
16 pub next_page_key: Option<K>,
17}
18
19/// Iterator that fetch paginated elements and allow to iterate over
20/// them as a continuous sequence of elements.
21pub struct PageIterator<'a, T: Clone, K: Clone> {
22 /// Function to fetch a page.
23 fetch_page: Fetcher<'a, T, K>,
24 /// Optional cached page.
25 current_page: Option<Page<T, K>>,
26 /// Position to the current element of the current page.
27 page_item_index: usize,
28 /// Size of each page.
29 page_size: u64,
30 /// Tells if the iterator has iterated over all the items.
31 consumed: bool,
32}
33
34impl<'a, T: Clone, K: Clone> PageIterator<'a, T, K> {
35 /// Creates a new iterator that fetch paginated items and allow to iterate over them
36 /// as a continuous sequence of elements.
37 ///
38 /// * `fetch_page` - Function that fetch the pages.
39 /// * `page_size` - Size of each page.
40 ///
41 /// # Examples
42 ///
43 /// ```
44 /// use desmos_bindings::iter::page_iterator::{Page, PageIterator};
45 ///
46 /// // Creates an iterator that return the numbers from 0 to 19.
47 /// let it: PageIterator<u64, u64> = PageIterator::new(
48 /// Box::new(|key, limit| {
49 /// let start = key.unwrap_or(0);
50 /// Ok(Page {
51 /// items: (start..start + limit).collect(),
52 /// next_page_key: if start + limit >= 20 {
53 /// None
54 /// } else {
55 /// Some(start + limit)
56 /// }
57 /// })
58 /// }),
59 /// 10,
60 /// );
61 /// ```
62 pub fn new(fetch_page: Fetcher<'a, T, K>, page_size: u64) -> PageIterator<'a, T, K> {
63 PageIterator {
64 fetch_page,
65 current_page: None,
66 page_item_index: 0,
67 page_size,
68 consumed: false,
69 }
70 }
71}
72
73impl<'a, T: Clone, K: Clone> Iterator for PageIterator<'a, T, K> {
74 type Item = StdResult<T>;
75
76 fn next(&mut self) -> Option<Self::Item> {
77 // If the iterator is consumed just return None
78 if self.consumed {
79 return None;
80 }
81
82 if self.current_page.is_none()
83 || self.current_page.as_ref().unwrap().items.len() == self.page_item_index
84 {
85 // Get the next page key
86 let next_key = self
87 .current_page
88 .as_ref()
89 .and_then(|page| page.next_page_key.as_ref().cloned());
90
91 if next_key.is_none() && self.current_page.is_some() {
92 // We have fetched at least on page but there isn't a new page to fetch,
93 // to prevent an empty fetch just mark this iterator as consumed and return None
94 self.consumed = true;
95 self.current_page = None;
96 return None;
97 }
98
99 // Fetch a new page
100 let fetch_result: StdResult<Page<T, K>> = (self.fetch_page)(next_key, self.page_size);
101
102 match fetch_result {
103 Ok(page) => {
104 // No items on the new page so no more items, flag the iterator as consumed
105 if page.items.is_empty() {
106 self.consumed = true;
107 None
108 } else {
109 // Clone the first item
110 let first_item = page.items[0].clone();
111 // Save the fetched page
112 self.current_page = Some(page);
113 // Update the index for the next iteration
114 self.page_item_index = 1;
115 // Return the first item of the new fetched page
116 Some(Ok(first_item))
117 }
118 }
119
120 // An error occurred, propagate it to the caller
121 Err(e) => {
122 // Set the iterator as consumed to prevent other invocations
123 self.consumed = true;
124 Some(Err(e))
125 }
126 }
127 } else {
128 // A page is available and we don't have iterated over all the items
129 let page = self.current_page.as_ref().unwrap();
130 let result = Some(Ok(page.items[self.page_item_index].clone()));
131
132 // Update the iterator index
133 self.page_item_index += 1;
134
135 result
136 }
137 }
138}
139
140#[cfg(test)]
141mod test {
142 use crate::iter::page_iterator::{Page, PageIterator};
143 use cosmwasm_std::StdError;
144
145 #[test]
146 fn test_iterations_without_errors() {
147 let it: PageIterator<u64, u64> = PageIterator::new(
148 Box::new(|key, limit| {
149 let start = key.unwrap_or(0);
150 Ok(Page {
151 items: (start..start + limit).collect(),
152 next_page_key: if start + limit >= 20 {
153 None
154 } else {
155 Some(start + limit)
156 },
157 })
158 }),
159 10,
160 );
161
162 let mut it_counter = 0;
163 for element in it {
164 element.unwrap();
165 it_counter += 1;
166 }
167
168 assert_eq!(20, it_counter);
169 }
170
171 #[test]
172 fn test_iterations_with_error() {
173 let mut it = PageIterator::<i32, i32>::new(
174 Box::new(|_, _| Err(StdError::generic_err("ERROR :("))),
175 10,
176 );
177
178 let item = it.next();
179
180 // First item should be some with the error that happen fetching the page
181 assert!(item.is_some());
182 assert!(item.unwrap().is_err());
183
184 // Since an error occurred when fetching the page the second item should be None
185 // to signal that the iterator don't have more items.
186 let item = it.next();
187 assert!(item.is_none());
188 }
189
190 #[test]
191 fn test_iterations_with_empty_page() {
192 // Create an that just return an empty page
193 let mut it: PageIterator<i32, i32> = PageIterator::new(
194 Box::new(|_, _| {
195 Ok(Page {
196 items: Vec::new(),
197 next_page_key: None,
198 })
199 }),
200 10,
201 );
202
203 // First items should be None since we returned an empty page
204 let first = it.next();
205 assert!(first.is_none());
206
207 // Second items should also be None since the page don't contains any element
208 let second = it.next();
209 assert!(second.is_none());
210 }
211
212 #[test]
213 fn test_iterations_with_partial_page() {
214 // Create an iterator that just return a page of 2 elements
215 // even if we requested a page of 10 elements.
216 let mut it: PageIterator<i32, i32> = PageIterator::new(
217 Box::new(|_, _| {
218 Ok(Page {
219 items: vec![1, 2],
220 next_page_key: None,
221 })
222 }),
223 10,
224 );
225
226 // First items should be 1
227 let first = it.next();
228 assert_eq!(1, first.unwrap().unwrap());
229
230 // Second items should be 1
231 let second = it.next();
232 assert_eq!(2, second.unwrap().unwrap());
233
234 // Third items should be None since we returned only 2 elements from the iterator.
235 let third = it.next();
236 assert_eq!(None, third)
237 }
238
239 #[test]
240 fn test_iterations_with_second_partial_page() {
241 // Create an iterator that just return a page of 2 elements
242 // even if we requested a page of 10 elements.
243 let it: PageIterator<u64, u64> = PageIterator::new(
244 Box::new(|key, limit| {
245 let range_start = key.unwrap_or(0);
246 let range_end = if range_start == 0 {
247 range_start + limit
248 } else {
249 range_start + 2
250 };
251 Ok(Page {
252 items: (range_start..range_end).collect(),
253 next_page_key: if range_start == 0 {
254 Some(range_end)
255 } else {
256 None
257 },
258 })
259 }),
260 10,
261 );
262
263 let mut total_count = 0;
264 for element in it {
265 element.unwrap();
266 total_count += 1;
267 }
268 // We should have 12 elements
269 assert_eq!(12, total_count);
270 }
271}