1use crate::rate_limit::RateLimit;
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum PaginationError {
8 PageZero,
10 PerPageZero,
12 PageLimitZero,
14 InvalidPreviousPage,
16 InvalidNextPage,
18 InvalidLastPage,
20 UnexpectedPage,
22 EmptyPageWithNextPage,
24 InvalidEntryCount,
26 PageSizeChanged,
28 TraversalChanged,
30 PageLimitExceeded,
32 Complete,
34}
35
36impl_static_error!(PaginationError,
37 Self::PageZero => "page number must be nonzero",
38 Self::PerPageZero => "page size must be nonzero",
39 Self::PageLimitZero => "page limit must be nonzero",
40 Self::InvalidPreviousPage => "previous-page metadata is invalid",
41 Self::InvalidNextPage => "next-page metadata is invalid",
42 Self::InvalidLastPage => "last-page metadata is invalid",
43 Self::UnexpectedPage => "response page differs from requested page",
44 Self::EmptyPageWithNextPage => "nonterminal response page is empty",
45 Self::InvalidEntryCount => "response entry count is invalid",
46 Self::PageSizeChanged => "response page size changed during traversal",
47 Self::TraversalChanged => "pagination metadata changed during traversal",
48 Self::PageLimitExceeded => "pagination page limit was exceeded",
49 Self::Complete => "pagination traversal is complete",
50);
51
52#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
54pub struct PageNumber(u64);
55
56impl PageNumber {
57 pub const fn new(value: u64) -> Result<Self, PaginationError> {
59 if value == 0 {
60 return Err(PaginationError::PageZero);
61 }
62 Ok(Self(value))
63 }
64
65 #[must_use]
67 pub const fn get(self) -> u64 {
68 self.0
69 }
70}
71
72#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
74pub struct PageLimit(u32);
75
76impl PageLimit {
77 pub const fn new(value: u32) -> Result<Self, PaginationError> {
79 if value == 0 {
80 return Err(PaginationError::PageLimitZero);
81 }
82 Ok(Self(value))
83 }
84
85 #[must_use]
87 pub const fn get(self) -> u32 {
88 self.0
89 }
90}
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
94pub struct PageMetadata {
95 page: PageNumber,
96 per_page: u64,
97 previous_page: Option<PageNumber>,
98 next_page: Option<PageNumber>,
99 last_page: Option<PageNumber>,
100 total_entries: Option<u64>,
101}
102
103impl PageMetadata {
104 pub const fn new(
106 page: PageNumber,
107 per_page: u64,
108 previous_page: Option<PageNumber>,
109 next_page: Option<PageNumber>,
110 last_page: Option<PageNumber>,
111 total_entries: Option<u64>,
112 ) -> Result<Self, PaginationError> {
113 if per_page == 0 {
114 return Err(PaginationError::PerPageZero);
115 }
116 if let Some(previous) = previous_page {
117 let Some(expected) = page.0.checked_sub(1) else {
118 return Err(PaginationError::InvalidPreviousPage);
119 };
120 if previous.0 != expected {
121 return Err(PaginationError::InvalidPreviousPage);
122 }
123 }
124 if let Some(next) = next_page {
125 let Some(expected) = page.0.checked_add(1) else {
126 return Err(PaginationError::InvalidNextPage);
127 };
128 if next.0 != expected {
129 return Err(PaginationError::InvalidNextPage);
130 }
131 }
132 if let Some(last) = last_page {
133 if last.0 < page.0 {
134 return Err(PaginationError::InvalidLastPage);
135 }
136 if (page.0 < last.0) != next_page.is_some() {
137 return Err(PaginationError::InvalidLastPage);
138 }
139 if let Some(next) = next_page
140 && next.0 > last.0
141 {
142 return Err(PaginationError::InvalidLastPage);
143 }
144 }
145 Ok(Self {
146 page,
147 per_page,
148 previous_page,
149 next_page,
150 last_page,
151 total_entries,
152 })
153 }
154
155 #[must_use]
157 pub const fn page(self) -> PageNumber {
158 self.page
159 }
160
161 #[must_use]
163 pub const fn per_page(self) -> u64 {
164 self.per_page
165 }
166
167 #[must_use]
169 pub const fn previous_page(self) -> Option<PageNumber> {
170 self.previous_page
171 }
172
173 #[must_use]
175 pub const fn next_page(self) -> Option<PageNumber> {
176 self.next_page
177 }
178
179 #[must_use]
181 pub const fn last_page(self) -> Option<PageNumber> {
182 self.last_page
183 }
184
185 #[must_use]
187 pub const fn total_entries(self) -> Option<u64> {
188 self.total_entries
189 }
190}
191
192#[derive(Clone, Copy, Debug, Eq, PartialEq)]
194pub struct PageBoundary {
195 metadata: PageMetadata,
196 entries: usize,
197 rate_limit: Option<RateLimit>,
198}
199
200impl PageBoundary {
201 #[must_use]
203 pub const fn metadata(self) -> PageMetadata {
204 self.metadata
205 }
206
207 #[must_use]
209 pub const fn entries(self) -> usize {
210 self.entries
211 }
212
213 #[must_use]
215 pub const fn rate_limit(self) -> Option<RateLimit> {
216 self.rate_limit
217 }
218
219 #[must_use]
221 pub const fn is_terminal(self) -> bool {
222 self.metadata.next_page.is_none()
223 }
224}
225
226#[derive(Clone, Copy, Debug, Eq, PartialEq)]
228pub struct PaginationCursor {
229 next_page: Option<PageNumber>,
230 pages_seen: u32,
231 limit: PageLimit,
232 expected_per_page: u64,
233 expected_total_entries: Option<u64>,
234 expected_last_page: Option<PageNumber>,
235}
236
237impl PaginationCursor {
238 pub const fn new(
240 first_page: PageNumber,
241 expected_per_page: u64,
242 limit: PageLimit,
243 ) -> Result<Self, PaginationError> {
244 if expected_per_page == 0 {
245 return Err(PaginationError::PerPageZero);
246 }
247 Ok(Self {
248 next_page: Some(first_page),
249 pages_seen: 0,
250 limit,
251 expected_per_page,
252 expected_total_entries: None,
253 expected_last_page: None,
254 })
255 }
256
257 pub const fn next_page(self) -> Result<PageNumber, PaginationError> {
259 match self.next_page {
260 Some(page) => Ok(page),
261 None => Err(PaginationError::Complete),
262 }
263 }
264
265 #[must_use]
267 pub const fn pages_seen(self) -> u32 {
268 self.pages_seen
269 }
270
271 pub fn observe(
273 &mut self,
274 metadata: PageMetadata,
275 entries: usize,
276 rate_limit: Option<RateLimit>,
277 ) -> Result<PageBoundary, PaginationError> {
278 let expected = match self.next_page {
279 Some(page) => page,
280 None => return Err(PaginationError::Complete),
281 };
282 if metadata.page.0 != expected.0 {
283 return Err(PaginationError::UnexpectedPage);
284 }
285 if metadata.per_page != self.expected_per_page {
286 return Err(PaginationError::PageSizeChanged);
287 }
288 if self.pages_seen != 0
289 && (metadata.total_entries != self.expected_total_entries
290 || metadata.last_page != self.expected_last_page)
291 {
292 return Err(PaginationError::TraversalChanged);
293 }
294 let entry_count = u64::try_from(entries).map_err(|_| PaginationError::InvalidEntryCount)?;
295 if entry_count > metadata.per_page {
296 return Err(PaginationError::InvalidEntryCount);
297 }
298 if let Some(total) = metadata.total_entries {
299 let page_offset = metadata
300 .page
301 .0
302 .checked_sub(1)
303 .and_then(|page| page.checked_mul(metadata.per_page))
304 .ok_or(PaginationError::InvalidEntryCount)?;
305 let expected_entries = total.saturating_sub(page_offset).min(metadata.per_page);
306 let expected_continuation = total
307 > page_offset
308 .checked_add(entry_count)
309 .ok_or(PaginationError::InvalidEntryCount)?;
310 if entry_count != expected_entries
311 || expected_continuation != metadata.next_page.is_some()
312 {
313 return Err(PaginationError::InvalidEntryCount);
314 }
315 }
316 if entries == 0 && metadata.next_page.is_some() {
317 return Err(PaginationError::EmptyPageWithNextPage);
318 }
319 let pages_seen = match self.pages_seen.checked_add(1) {
320 Some(value) => value,
321 None => return Err(PaginationError::PageLimitExceeded),
322 };
323 if metadata.next_page.is_some() && pages_seen >= self.limit.0 {
324 return Err(PaginationError::PageLimitExceeded);
325 }
326 self.pages_seen = pages_seen;
327 self.next_page = metadata.next_page;
328 if pages_seen == 1 {
329 self.expected_total_entries = metadata.total_entries;
330 self.expected_last_page = metadata.last_page;
331 }
332 Ok(PageBoundary {
333 metadata,
334 entries,
335 rate_limit,
336 })
337 }
338}
339
340#[cfg(test)]
341mod tests;