Skip to main content

aro_core/
pagination.rs

1//! Pagination primitives for repository queries.
2
3use serde::{Deserialize, Serialize};
4
5/// A request for a specific page of results.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct PageRequest {
8    /// The 1-based page number.
9    pub page: u64,
10    /// The number of items per page.
11    pub per_page: u64,
12}
13
14/// A page of results returned from a repository query.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct Page<T> {
17    /// The items in this page.
18    pub items: Vec<T>,
19    /// The total number of items across all pages.
20    pub total: u64,
21    /// The 1-based page number of this page.
22    pub page: u64,
23    /// The number of items per page.
24    pub per_page: u64,
25}
26
27impl<T> Page<T> {
28    /// Returns the total number of pages.
29    pub fn total_pages(&self) -> u64 {
30        if self.per_page == 0 {
31            return 0;
32        }
33        self.total.div_ceil(self.per_page)
34    }
35
36    /// Returns `true` if there is a page after this one.
37    pub fn has_next_page(&self) -> bool {
38        self.page < self.total_pages()
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn page_total_pages_exact_division() {
48        let page: Page<i32> = Page {
49            items: vec![1, 2],
50            total: 10,
51            page: 1,
52            per_page: 5,
53        };
54        assert_eq!(page.total_pages(), 2);
55    }
56
57    #[test]
58    fn page_total_pages_with_remainder() {
59        let page: Page<i32> = Page {
60            items: vec![1, 2, 3],
61            total: 7,
62            page: 1,
63            per_page: 3,
64        };
65        assert_eq!(page.total_pages(), 3);
66    }
67
68    #[test]
69    fn page_total_pages_zero_per_page() {
70        let page: Page<i32> = Page {
71            items: vec![],
72            total: 10,
73            page: 1,
74            per_page: 0,
75        };
76        assert_eq!(page.total_pages(), 0);
77    }
78
79    #[test]
80    fn page_total_pages_zero_total() {
81        let page: Page<i32> = Page {
82            items: vec![],
83            total: 0,
84            page: 1,
85            per_page: 10,
86        };
87        assert_eq!(page.total_pages(), 0);
88    }
89
90    #[test]
91    fn has_next_page_true() {
92        let page: Page<i32> = Page {
93            items: vec![1, 2],
94            total: 10,
95            page: 1,
96            per_page: 5,
97        };
98        assert!(page.has_next_page());
99    }
100
101    #[test]
102    fn has_next_page_false_on_last_page() {
103        let page: Page<i32> = Page {
104            items: vec![1],
105            total: 10,
106            page: 2,
107            per_page: 5,
108        };
109        assert!(!page.has_next_page());
110    }
111
112    #[test]
113    fn has_next_page_false_when_empty() {
114        let page: Page<i32> = Page {
115            items: vec![],
116            total: 0,
117            page: 1,
118            per_page: 10,
119        };
120        assert!(!page.has_next_page());
121    }
122
123    #[test]
124    fn page_request_construction() {
125        let req = PageRequest {
126            page: 2,
127            per_page: 25,
128        };
129        assert_eq!(req.page, 2);
130        assert_eq!(req.per_page, 25);
131    }
132
133    #[test]
134    fn page_request_clone_and_eq() {
135        let req = PageRequest {
136            page: 1,
137            per_page: 10,
138        };
139        let cloned = req.clone();
140        assert_eq!(req, cloned);
141    }
142}