1use std::convert::Infallible;
2
3use axum::extract::FromRequestParts;
4use axum::http::request::Parts;
5use serde::Serialize;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct Page(pub u32);
19
20impl<S: Send + Sync> FromRequestParts<S> for Page {
21 type Rejection = Infallible;
22
23 async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Infallible> {
24 let page = parts
25 .uri
26 .query()
27 .and_then(|query| {
28 form_urlencoded::parse(query.as_bytes())
29 .find(|(key, _)| key == "page")
30 .and_then(|(_, value)| value.parse().ok())
31 })
32 .unwrap_or(1u32)
33 .max(1);
34 Ok(Self(page))
35 }
36}
37
38#[derive(Debug, Clone, Serialize)]
45#[non_exhaustive]
46pub struct Paginated<T> {
47 pub items: Vec<T>,
49 pub page: u32,
51 pub per_page: u32,
53 pub total: u64,
55 pub last_page: u32,
57 pub from: u64,
59 pub to: u64,
61 pub has_prev: bool,
63 pub has_next: bool,
65 pub pages: Vec<Option<u32>>,
67}
68
69impl<T> Paginated<T> {
70 pub fn new(items: Vec<T>, page: u32, per_page: u32, total: u64) -> Self {
72 let last_page = total.div_ceil(u64::from(per_page)).max(1) as u32;
73 let from = if items.is_empty() {
74 0
75 } else {
76 u64::from(page - 1) * u64::from(per_page) + 1
77 };
78 let to = if items.is_empty() {
79 0
80 } else {
81 from + items.len() as u64 - 1
82 };
83 Self {
84 items,
85 page,
86 per_page,
87 total,
88 last_page,
89 from,
90 to,
91 has_prev: page > 1,
92 has_next: page < last_page,
93 pages: window(page, last_page),
94 }
95 }
96
97 pub fn map<U>(self, f: impl FnMut(T) -> U) -> Paginated<U> {
99 Paginated {
100 items: self.items.into_iter().map(f).collect(),
101 page: self.page,
102 per_page: self.per_page,
103 total: self.total,
104 last_page: self.last_page,
105 from: self.from,
106 to: self.to,
107 has_prev: self.has_prev,
108 has_next: self.has_next,
109 pages: self.pages,
110 }
111 }
112}
113
114#[derive(Debug, Clone, Serialize)]
122#[non_exhaustive]
123pub struct SimplePage<T> {
124 pub items: Vec<T>,
126 pub page: u32,
128 pub per_page: u32,
130 pub has_prev: bool,
132 pub has_next: bool,
134}
135
136impl<T> SimplePage<T> {
137 pub fn map<U>(self, f: impl FnMut(T) -> U) -> SimplePage<U> {
139 SimplePage {
140 items: self.items.into_iter().map(f).collect(),
141 page: self.page,
142 per_page: self.per_page,
143 has_prev: self.has_prev,
144 has_next: self.has_next,
145 }
146 }
147}
148
149#[derive(Debug, Clone, Serialize)]
153#[non_exhaustive]
154pub struct CursorPage<T> {
155 pub items: Vec<T>,
157 pub per_page: u32,
159 pub next_cursor: Option<String>,
161}
162
163impl<T> CursorPage<T> {
164 pub fn map<U>(self, f: impl FnMut(T) -> U) -> CursorPage<U> {
166 CursorPage {
167 items: self.items.into_iter().map(f).collect(),
168 per_page: self.per_page,
169 next_cursor: self.next_cursor,
170 }
171 }
172}
173
174fn window(page: u32, last: u32) -> Vec<Option<u32>> {
176 let mut pages = Vec::new();
177 let mut previous = 0;
178 for n in 1..=last {
179 if n == 1 || n == last || n.abs_diff(page) <= 2 {
180 if n > previous + 1 {
181 pages.push(None);
182 }
183 pages.push(Some(n));
184 previous = n;
185 }
186 }
187 pages
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn counts_pages_and_positions() {
196 let p = Paginated::new(vec![11, 12, 13], 2, 10, 23);
197 assert_eq!((p.last_page, p.from, p.to), (3, 11, 13));
198 assert!(p.has_prev && p.has_next);
199
200 let empty = Paginated::<i32>::new(vec![], 1, 10, 0);
201 assert_eq!((empty.last_page, empty.from, empty.to), (1, 0, 0));
202 assert!(!empty.has_prev && !empty.has_next);
203 }
204
205 #[test]
206 fn windows_page_links() {
207 assert_eq!(window(1, 3), vec![Some(1), Some(2), Some(3)]);
208 assert_eq!(
209 window(10, 20),
210 vec![
211 Some(1),
212 None,
213 Some(8),
214 Some(9),
215 Some(10),
216 Some(11),
217 Some(12),
218 None,
219 Some(20)
220 ]
221 );
222 assert_eq!(
223 window(2, 9),
224 vec![Some(1), Some(2), Some(3), Some(4), None, Some(9)]
225 );
226 }
227
228 #[test]
230 fn map_keeps_the_page_information() {
231 let page = Paginated {
232 items: vec![1, 2],
233 page: 2,
234 per_page: 2,
235 total: 5,
236 last_page: 3,
237 from: 3,
238 to: 4,
239 has_prev: true,
240 has_next: true,
241 pages: window(2, 3),
242 }
243 .map(|n| n * 10);
244 assert_eq!(page.items, [10, 20]);
245 assert_eq!((page.page, page.total, page.last_page), (2, 5, 3));
246 assert!(page.has_prev && page.has_next);
247
248 let simple = SimplePage {
249 items: vec!["a"],
250 page: 1,
251 per_page: 10,
252 has_prev: false,
253 has_next: true,
254 }
255 .map(str::to_uppercase);
256 assert_eq!(simple.items, ["A"]);
257 assert!(!simple.has_prev && simple.has_next);
258
259 let cursor = CursorPage {
260 items: vec![1],
261 per_page: 1,
262 next_cursor: Some("abc".into()),
263 }
264 .map(|n| n + 1);
265 assert_eq!(cursor.items, [2]);
266 assert_eq!(cursor.next_cursor.as_deref(), Some("abc"));
267 }
268}