Skip to main content

cast_core/
paginator.rs

1//! Pagination — mirrors Laravel's `LengthAwarePaginator`.
2
3use serde::Serialize;
4
5/// Page-aware paginator. Produced by `QueryBuilder::paginate(per_page, page, pool)`.
6#[derive(Debug, Clone, Serialize)]
7pub struct Paginator<T> {
8    /// The rows on the current page.
9    pub items: Vec<T>,
10    /// Total row count across all pages (a `COUNT(*)` against the same query).
11    pub total: i64,
12    /// Rows per page.
13    pub per_page: u64,
14    /// 1-indexed current page.
15    pub current_page: u64,
16    /// 1-indexed last page (computed from `total` + `per_page`).
17    pub last_page: u64,
18}
19
20impl<T> Paginator<T> {
21    /// Build a paginator from a fetched page + the global count.
22    pub fn new(items: Vec<T>, total: i64, per_page: u64, current_page: u64) -> Self {
23        let per_page = per_page.max(1);
24        let total_u = total.max(0) as u64;
25        let last_page = if total_u == 0 {
26            1
27        } else {
28            total_u.div_ceil(per_page)
29        };
30        Self {
31            items,
32            total,
33            per_page,
34            current_page: current_page.max(1),
35            last_page,
36        }
37    }
38
39    pub fn has_more_pages(&self) -> bool {
40        self.current_page < self.last_page
41    }
42
43    pub fn has_previous_pages(&self) -> bool {
44        self.current_page > 1
45    }
46
47    pub fn next_page(&self) -> Option<u64> {
48        self.has_more_pages().then(|| self.current_page + 1)
49    }
50
51    pub fn previous_page(&self) -> Option<u64> {
52        self.has_previous_pages().then(|| self.current_page - 1)
53    }
54
55    /// Number of items on the current page.
56    pub fn count(&self) -> usize {
57        self.items.len()
58    }
59
60    /// `from` and `to` are 1-indexed row positions within the *full* result set.
61    pub fn from(&self) -> Option<u64> {
62        if self.items.is_empty() {
63            None
64        } else {
65            Some((self.current_page - 1) * self.per_page + 1)
66        }
67    }
68
69    pub fn to(&self) -> Option<u64> {
70        if self.items.is_empty() {
71            None
72        } else {
73            Some((self.current_page - 1) * self.per_page + self.items.len() as u64)
74        }
75    }
76
77    pub fn is_empty(&self) -> bool {
78        self.items.is_empty()
79    }
80
81    /// Map items via a function while keeping the pagination metadata.
82    pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> Paginator<U> {
83        Paginator {
84            items: self.items.into_iter().map(&mut f).collect(),
85            total: self.total,
86            per_page: self.per_page,
87            current_page: self.current_page,
88            last_page: self.last_page,
89        }
90    }
91}