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}