laterite_core/
pagination.rs1use serde::{Deserialize, Serialize};
4
5pub const MAX_PER_PAGE: u32 = 200;
6pub const DEFAULT_PER_PAGE: u32 = 25;
7
8#[derive(Debug, Clone, Copy, Deserialize)]
10pub struct PageRequest {
11 #[serde(default = "default_page")]
12 pub page: u32,
13 #[serde(default = "default_per_page")]
14 pub per_page: u32,
15}
16
17fn default_page() -> u32 {
18 1
19}
20
21fn default_per_page() -> u32 {
22 DEFAULT_PER_PAGE
23}
24
25impl Default for PageRequest {
26 fn default() -> Self {
27 Self {
28 page: 1,
29 per_page: DEFAULT_PER_PAGE,
30 }
31 }
32}
33
34impl PageRequest {
35 pub fn clamped(self) -> Self {
37 Self {
38 page: self.page.max(1),
39 per_page: self.per_page.clamp(1, MAX_PER_PAGE),
40 }
41 }
42
43 pub fn limit(&self) -> i64 {
44 i64::from(self.clamped().per_page)
45 }
46
47 pub fn offset(&self) -> i64 {
48 let clamped = self.clamped();
49 i64::from(clamped.page - 1) * i64::from(clamped.per_page)
50 }
51}
52
53#[derive(Debug, Clone, Serialize)]
55pub struct Page<T> {
56 pub items: Vec<T>,
57 pub page: u32,
58 pub per_page: u32,
59 pub total: u64,
60}
61
62impl<T> Page<T> {
63 pub fn total_pages(&self) -> u32 {
64 if self.per_page == 0 {
65 return 0;
66 }
67 let pages = self.total.div_ceil(u64::from(self.per_page));
68 u32::try_from(pages).unwrap_or(u32::MAX)
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn clamps_out_of_range_requests() {
78 let request = PageRequest {
79 page: 0,
80 per_page: 10_000,
81 };
82 let clamped = request.clamped();
83 assert_eq!(clamped.page, 1);
84 assert_eq!(clamped.per_page, MAX_PER_PAGE);
85 }
86
87 #[test]
88 fn computes_limit_and_offset() {
89 let request = PageRequest {
90 page: 3,
91 per_page: 25,
92 };
93 assert_eq!(request.limit(), 25);
94 assert_eq!(request.offset(), 50);
95 }
96
97 #[test]
98 fn computes_total_pages() {
99 let page = Page::<u8> {
100 items: vec![],
101 page: 1,
102 per_page: 25,
103 total: 51,
104 };
105 assert_eq!(page.total_pages(), 3);
106 }
107}