headless_lms_utils/
pagination.rs1use std::{fmt, num::ParseIntError};
2
3use anyhow::bail;
4use serde::{
5 Deserialize, Deserializer,
6 de::{self, MapAccess, Visitor},
7};
8
9#[derive(Debug, Clone, Copy)]
11
12pub struct Pagination {
13 page: u32,
15 limit: u32,
17}
18
19impl Pagination {
20 pub fn new(page: u32, limit: u32) -> anyhow::Result<Self> {
22 if page == 0 {
23 bail!("Page must be a positive value.");
24 }
25 if limit == 0 {
26 bail!("Limit must be a positive value.");
27 }
28 if limit > 10_000 {
29 bail!("Limit can be at most 10000.")
30 }
31 Ok(Pagination { page, limit })
32 }
33
34 pub fn page(&self) -> i64 {
36 self.page.into()
37 }
38
39 pub fn limit(&self) -> i64 {
41 self.limit.into()
42 }
43
44 pub fn offset(&self) -> i64 {
46 i64::from(self.limit) * (i64::from(self.page) - 1)
49 }
50
51 pub fn total_pages(&self, total_count: u32) -> u32 {
53 let remainder = total_count % self.limit;
54 if remainder == 0 {
55 total_count / self.limit
56 } else {
57 total_count / self.limit + 1
58 }
59 }
60
61 pub fn paginate<T>(&self, v: &mut Vec<T>) {
63 let limit = self.limit as usize;
64 let start = limit * (self.page as usize - 1);
65 v.truncate(start + limit);
66 v.drain(..start);
67 }
68
69 pub fn next_page(&mut self) {
70 self.page += 1;
71 }
72}
73
74impl Default for Pagination {
75 fn default() -> Self {
76 Self {
77 page: 1,
78 limit: 100,
79 }
80 }
81}
82
83impl<'de> Deserialize<'de> for Pagination {
84 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
85 where
86 D: Deserializer<'de>,
87 {
88 struct PaginationVisitor;
89
90 impl<'de> Visitor<'de> for PaginationVisitor {
91 type Value = Pagination;
92
93 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
94 formatter.write_str("query parameters `page` and `limit`")
95 }
96
97 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
98 where
99 A: MapAccess<'de>,
100 {
101 let mut page = None;
102 let mut limit = None;
103 while let Some(key) = map.next_key().map_err(|e| {
104 de::Error::custom(format!("Failed to deserialize map key: {}", e))
105 })? {
106 match key {
107 "page" => {
108 if page.is_some() {
109 return Err(de::Error::duplicate_field("page"));
110 }
111 let value: StrOrInt = map.next_value().map_err(|e| {
112 de::Error::custom(format!(
113 "Failed to deserialize page value: {}",
114 e
115 ))
116 })?;
117 let value = value.into_int().map_err(|e| {
118 de::Error::custom(format!(
119 "Failed to deserialize page value: {}",
120 e
121 ))
122 })?;
123 if value < 1 {
124 return Err(de::Error::custom(
125 "query parameter `page` must be a positive integer",
126 ));
127 }
128 page = Some(value);
129 }
130 "limit" => {
131 if limit.is_some() {
132 return Err(de::Error::duplicate_field("limit"));
133 }
134 let value: StrOrInt = map.next_value().map_err(|e| {
135 de::Error::custom(format!(
136 "Failed to deserialize limit value: {}",
137 e
138 ))
139 })?;
140 let value = value.into_int().map_err(|e| {
141 de::Error::custom(format!(
142 "Failed to deserialize limit value: {}",
143 e
144 ))
145 })?;
146 if !(1..=10000).contains(&value) {
147 return Err(de::Error::custom(
148 "query parameter `limit` must be an integer between 1 and 10000",
149 ));
150 }
151 limit = Some(value);
152 }
153 field => {
154 return Err(de::Error::custom(format!(
155 "unexpected parameter `{}`",
156 field
157 )));
158 }
159 }
160 }
161 Ok(Pagination {
162 page: page.unwrap_or(Pagination::default().page),
163 limit: limit.unwrap_or(Pagination::default().limit),
164 })
165 }
166 }
167
168 deserializer.deserialize_struct("Pagination", &["page", "limit"], PaginationVisitor)
169 }
170}
171
172#[derive(Debug, Deserialize)]
175#[serde(untagged)]
176enum StrOrInt<'a> {
177 Str(&'a str),
178 Int(u32),
179}
180
181impl StrOrInt<'_> {
182 fn into_int(self) -> Result<u32, ParseIntError> {
183 match self {
184 Self::Str(s) => s.parse(),
185 Self::Int(i) => Ok(i),
186 }
187 }
188}
189
190#[cfg(test)]
191mod test {
192 use super::*;
193
194 #[test]
195 fn paginates() {
196 let mut v = vec![1, 2, 3, 4, 5, 6, 7, 8];
197 let pagination = Pagination::new(2, 3).unwrap();
198 pagination.paginate(&mut v);
199 assert_eq!(v, &[4, 5, 6]);
200 }
201
202 #[test]
203 fn paginates_non_existent_page() {
204 let mut v = vec![1, 2, 3, 4, 5, 6, 7, 8];
205 let pagination = Pagination::new(3, 4).unwrap();
206 pagination.paginate(&mut v);
207 assert_eq!(v, &[] as &[i32]);
208 }
209
210 #[test]
211 fn paginates_incomplete_page() {
212 let mut v = vec![1, 2, 3, 4, 5, 6, 7, 8];
213 let pagination = Pagination::new(2, 5).unwrap();
214 pagination.paginate(&mut v);
215 assert_eq!(v, &[6, 7, 8]);
216 }
217}