/// Paginated result container #[derive(Debug, Clone)] pub struct Page { /// Items for this page pub items: Vec, /// Total count of all matching items pub total_count: u64, /// Current page number (1-indexed) pub page: u32, /// Items per page pub page_size: u32, } impl Page { pub fn new(items: Vec, total_count: u64, page: u32, page_size: u32) -> Self { Self { items, total_count, page, page_size } } /// Total number of pages pub fn total_pages(&self) -> u32 { if self.page_size == 0 { return 0; } ((self.total_count as f64) / (self.page_size as f64)).ceil() as u32 } /// Whether there is a next page pub fn has_next(&self) -> bool { self.page < self.total_pages() } /// Whether there is a previous page pub fn has_prev(&self) -> bool { self.page > 1 } /// Check if page is empty pub fn is_empty(&self) -> bool { self.items.is_empty() } /// Number of items on this page pub fn len(&self) -> usize { self.items.len() } /// Map items to a different type pub fn map U>(self, f: F) -> Page { Page { items: self.items.into_iter().map(f).collect(), total_count: self.total_count, page: self.page, page_size: self.page_size, } } /// Iterator over items pub fn iter(&self) -> impl Iterator { self.items.iter() } /// Take ownership of items pub fn into_items(self) -> Vec { self.items } } impl IntoIterator for Page { type Item = T; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.items.into_iter() } } /// Pagination request options #[derive(Debug, Clone, Default)] pub struct PageRequest { /// Page number (1-indexed, minimum 1) pub page: u32, /// Items per page pub page_size: u32, } impl PageRequest { pub fn new(page: u32, page_size: u32) -> Self { Self { page: page.max(1), page_size, } } /// Calculate SQL OFFSET (0-indexed) pub fn offset(&self) -> u32 { if self.page <= 1 { 0 } else { (self.page - 1) * self.page_size } } /// Calculate SQL LIMIT pub fn limit(&self) -> u32 { self.page_size } /// First page pub fn first(page_size: u32) -> Self { Self::new(1, page_size) } }