paginate/
lib.rs

1//! A framework agnostic pagination crate, that is especially suited for databases, slices and collections. Paginate calculates the range of pages indexes, making it ideal for accessing slices, chunking data and querying databases.
2//!
3//! ### Example
4//!
5//! To iterate over each page:
6//! ```rust
7//! use paginate::Pages;
8//!
9//! fn print_pages() {
10//!     let total_items = 100usize;
11//!      let items_per_page = 5usize;
12//!      let pages = Pages::new(total_items, items_per_page);
13//!      println!("total pages: {}", pages.page_count());
14//!      for page in pages.into_iter() {
15//!          println!("offset: {}, total: {}, start: {}, end: {}", page.offset, page.length, page.start, page.end);
16//!      }
17//! }
18//! ```
19//!
20//! To get the pagination of a specific offset:
21//! ```rust
22//! use paginate::Pages;
23//!
24//! fn print_test() {
25//!     let total_items = 35;
26//!     let items_per_page = 5;
27//!     let pages = Pages::new(total_items, items_per_page);
28//!     let page = pages.with_offset(3);
29//!     println!("total pages: {}", pages.page_count());
30//!     println!("offset: {}, total: {}, start: {}, end: {}", page.offset, page.length, page.start, page.end);
31//! }
32//! ```
33
34use std::cmp::{max, min};
35
36///
37/// Defines the pages to facilitate pagination.
38#[derive(Clone, Debug, Eq, PartialEq)]
39pub struct Pages {
40    /// Current page offset.
41    offset: usize,
42    /// Total number of items.
43    length: usize,
44    /// Total number of pages.
45    limit: usize,
46}
47
48impl Pages {
49    /// Defines a new pagination.
50    ///
51    /// ### Arguments
52    ///
53    /// * `length` - the total number of items to paginate.
54    /// * `limit` - the maximum number of items that can exist on a single page.
55    pub fn new(length: usize, limit: usize) -> Pages {
56        Pages {
57            offset: 0,
58            length,
59            limit,
60        }
61    }
62
63    /// Gets a specific page.
64    ///
65    /// ### Arguments
66    ///
67    /// * `offset` - the offset of the page you want to retrieve.
68    pub fn with_offset(&self, offset: usize) -> Page {
69        let mut page = Page::default();
70        page.offset = offset;
71        page.start = min(page.offset * self.limit, self.length);
72        page.end = min(page.start + self.limit, self.length);
73        page.length = max(page.end - page.start, 0);
74        if page.length == 0 {
75            page.start = 0;
76            page.end = 0;
77        };
78        if page.length > 0 {
79            page.end -= 1;
80        };
81        page
82    }
83
84    /// Gets the current page offset.
85    pub fn offset(&self) -> usize {
86        self.offset
87    }
88
89    /// Gets the total number of items.
90    pub fn length(&self) -> usize {
91        self.length
92    }
93
94    /// Gets the maximum number of items per page.
95    pub fn limit(&self) -> usize {
96        self.limit
97    }
98
99    /// Gets the total number of pages.
100    pub fn page_count(&self) -> usize {
101        (self.length + self.limit - 1) / self.limit
102    }
103}
104
105impl Iterator for Pages {
106    type Item = Page;
107    fn next(&mut self) -> Option<Self::Item> {
108        let page: Page = self.with_offset(self.offset);
109        self.offset += 1;
110        if page.is_empty() {
111            None
112        } else {
113            Some(page)
114        }
115    }
116}
117
118impl IntoIterator for &Pages {
119    type Item = Page;
120    type IntoIter = Pages;
121
122    fn into_iter(self) -> Pages {
123        self.clone()
124    }
125}
126
127/// Defines the properties of a page.
128#[derive(Clone, Debug, Eq, PartialEq)]
129pub struct Page {
130    /// The current page offset.
131    pub offset: usize,
132    /// The total number of items that exist on this page offset.
133    pub length: usize,
134    /// The index of the first item on this page.
135    pub start: usize,
136    /// The index of the last item on this page.
137    pub end: usize,
138}
139
140impl Page {
141    /// When a page does not contain any items it is considered to be empty.
142    pub fn is_empty(&self) -> bool {
143        self.length == 0
144    }
145}
146
147impl Default for Page {
148    /// Creates an empty page with no items.
149    fn default() -> Self {
150        Page {
151            offset: 0usize,
152            length: 0usize,
153            start: 0usize,
154            end: 0usize,
155        }
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::{Page, Pages};
162
163    #[test]
164    fn default_page() {
165        let page = Page::default();
166        assert_eq!(
167            page,
168            Page {
169                offset: 0,
170                length: 0,
171                start: 0,
172                end: 0
173            }
174        );
175    }
176
177    #[test]
178    fn empty_page() {
179        let total_items = 0usize;
180        let items_per_page = 5usize;
181        let pages = Pages::new(total_items, items_per_page);
182        assert_eq!(
183            pages.with_offset(0),
184            Page {
185                offset: 0,
186                length: 0,
187                start: 0,
188                end: 0
189            }
190        );
191        assert_eq!(
192            pages.with_offset(1),
193            Page {
194                offset: 1,
195                length: 0,
196                start: 0,
197                end: 0
198            }
199        );
200    }
201
202    #[test]
203    fn limitless_page() {
204        let total_items = 5usize;
205        let items_per_page = 0usize;
206        let pages = Pages::new(total_items, items_per_page);
207        assert_eq!(
208            pages.with_offset(0),
209            Page {
210                offset: 0,
211                length: 0,
212                start: 0,
213                end: 0
214            }
215        );
216        assert_eq!(
217            pages.with_offset(1),
218            Page {
219                offset: 1,
220                length: 0,
221                start: 0,
222                end: 0
223            }
224        );
225    }
226
227    #[test]
228    fn single_page() {
229        let total_items = 5usize;
230        let items_per_page = 5usize;
231        let pages = Pages::new(total_items, items_per_page);
232        assert_eq!(
233            pages.with_offset(0),
234            Page {
235                offset: 0,
236                length: 5,
237                start: 0,
238                end: 4
239            }
240        );
241        assert_eq!(
242            pages.with_offset(1),
243            Page {
244                offset: 1,
245                length: 0,
246                start: 0,
247                end: 0
248            }
249        );
250        assert_eq!(
251            pages.with_offset(2),
252            Page {
253                offset: 2,
254                length: 0,
255                start: 0,
256                end: 0
257            }
258        );
259    }
260
261    #[test]
262    fn single_item() {
263        let total_items = 1usize;
264        let items_per_page = 5usize;
265        let pages = Pages::new(total_items, items_per_page);
266        assert_eq!(
267            pages.with_offset(0),
268            Page {
269                offset: 0,
270                length: 1,
271                start: 0,
272                end: 0
273            }
274        );
275        assert_eq!(
276            pages.with_offset(1),
277            Page {
278                offset: 1,
279                length: 0,
280                start: 0,
281                end: 0
282            }
283        );
284    }
285
286    #[test]
287    fn odd_items() {
288        let total_items = 5usize;
289        let items_per_page = 2usize;
290        let pages = Pages::new(total_items, items_per_page);
291        assert_eq!(
292            pages.with_offset(0),
293            Page {
294                offset: 0,
295                length: 2,
296                start: 0,
297                end: 1
298            }
299        );
300        assert_eq!(
301            pages.with_offset(1),
302            Page {
303                offset: 1,
304                length: 2,
305                start: 2,
306                end: 3
307            }
308        );
309        assert_eq!(
310            pages.with_offset(2),
311            Page {
312                offset: 2,
313                length: 1,
314                start: 4,
315                end: 4
316            }
317        );
318        assert_eq!(
319            pages.with_offset(3),
320            Page {
321                offset: 3,
322                length: 0,
323                start: 0,
324                end: 0
325            }
326        );
327    }
328
329    #[test]
330    fn even_items() {
331        let total_items = 6usize;
332        let items_per_page = 2usize;
333        let pages = Pages::new(total_items, items_per_page);
334        assert_eq!(
335            pages.with_offset(0),
336            Page {
337                offset: 0,
338                length: 2,
339                start: 0,
340                end: 1
341            }
342        );
343        assert_eq!(
344            pages.with_offset(1),
345            Page {
346                offset: 1,
347                length: 2,
348                start: 2,
349                end: 3
350            }
351        );
352        assert_eq!(
353            pages.with_offset(2),
354            Page {
355                offset: 2,
356                length: 2,
357                start: 4,
358                end: 5
359            }
360        );
361        assert_eq!(
362            pages.with_offset(3),
363            Page {
364                offset: 3,
365                length: 0,
366                start: 0,
367                end: 0
368            }
369        );
370    }
371
372    #[test]
373    fn odd_sizes() {
374        let total_items = 5usize;
375        let items_per_page = 3usize;
376        let pages = Pages::new(total_items, items_per_page);
377        assert_eq!(
378            pages.with_offset(0),
379            Page {
380                offset: 0,
381                length: 3,
382                start: 0,
383                end: 2
384            }
385        );
386        assert_eq!(
387            pages.with_offset(1),
388            Page {
389                offset: 1,
390                length: 2,
391                start: 3,
392                end: 4
393            }
394        );
395        assert_eq!(
396            pages.with_offset(2),
397            Page {
398                offset: 2,
399                length: 0,
400                start: 0,
401                end: 0
402            }
403        );
404    }
405
406    #[test]
407    fn iterator() {
408        let total_items = 1usize;
409        let items_per_page = 1usize;
410        let pages = Pages::new(total_items, items_per_page);
411        for p in pages {
412            assert_eq!(
413                p,
414                Page {
415                    offset: 0,
416                    length: 1,
417                    start: 0,
418                    end: 0
419                }
420            );
421        }
422    }
423
424    #[test]
425    fn iterator_ref() {
426        let total_items = 1usize;
427        let items_per_page = 1usize;
428        let pages = Pages::new(total_items, items_per_page);
429        for p in &pages {
430            assert_eq!(
431                p,
432                Page {
433                    offset: 0,
434                    length: 1,
435                    start: 0,
436                    end: 0
437                }
438            );
439        }
440    }
441
442    #[test]
443    fn is_empty() {
444        let empty_page = Page::default();
445        assert!(empty_page.is_empty());
446
447        let filled_page = Page {
448            length: 1,
449            ..Page::default()
450        };
451        assert!(!filled_page.is_empty());
452    }
453
454    #[test]
455    fn offset() {
456        let pages = Pages::new(100, 5);
457        assert_eq!(0, pages.offset());
458    }
459
460    #[test]
461    fn length() {
462        let pages = Pages::new(100, 5);
463        assert_eq!(100, pages.length());
464    }
465
466    #[test]
467    fn limit() {
468        let pages = Pages::new(100, 5);
469        assert_eq!(5, pages.limit());
470    }
471
472    #[test]
473    fn page_count() {
474        let pages = Pages::new(100, 5);
475        assert_eq!(20, pages.page_count());
476
477        let pages = Pages::new(101, 5);
478        assert_eq!(21, pages.page_count());
479
480        let pages = Pages::new(99, 5);
481        assert_eq!(20, pages.page_count());
482    }
483}