Skip to main content

uptrakit_web_api_types/
pagination.rs

1use serde::{Deserialize, Serialize};
2
3pub const DEFAULT_PER_PAGE: u64 = 20;
4pub const MAX_PER_PAGE: u64 = 1000;
5
6/// Raw pagination query parameters (both optional).
7#[derive(Debug, Clone, Serialize, Deserialize)]
8#[cfg_attr(feature = "openapi", derive(utoipa::IntoParams))]
9pub struct PaginationParams {
10    /// Page number (1-indexed). Defaults to 1.
11    pub page: Option<u64>,
12    /// Items per page. Defaults to 20, max 1000.
13    pub per_page: Option<u64>,
14}
15
16/// Resolved (validated, clamped) pagination values.
17#[derive(Debug, Clone, Copy)]
18pub struct ResolvedPagination {
19    pub page: u64,
20    pub per_page: u64,
21}
22
23impl PaginationParams {
24    pub fn resolve(&self) -> ResolvedPagination {
25        let page = self.page.unwrap_or(1).max(1);
26        let per_page = self
27            .per_page
28            .unwrap_or(DEFAULT_PER_PAGE)
29            .clamp(1, MAX_PER_PAGE);
30        ResolvedPagination { page, per_page }
31    }
32}
33
34impl ResolvedPagination {
35    pub fn offset(&self) -> u64 {
36        (self.page - 1) * self.per_page
37    }
38
39    pub fn total_pages(&self, total: u64) -> u64 {
40        total.div_ceil(self.per_page)
41    }
42}
43
44/// Paginated response wrapper.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
47pub struct PaginatedResponse<T> {
48    pub items: Vec<T>,
49    pub total: u64,
50    pub page: u64,
51    pub per_page: u64,
52    pub total_pages: u64,
53}
54
55impl<T> PaginatedResponse<T> {
56    pub fn new(items: Vec<T>, total: u64, pagination: ResolvedPagination) -> Self {
57        Self {
58            items,
59            total,
60            page: pagination.page,
61            per_page: pagination.per_page,
62            total_pages: pagination.total_pages(total),
63        }
64    }
65}