use std::ops::RangeInclusive;
use thiserror::Error;
const NO_RESULTS_MESSAGE: &str = "that page contains no results";
#[derive(Debug, Error, PartialEq, Eq)]
pub enum PaginatorError {
#[error("that page number is not an integer")]
PageNotAnInteger,
#[error("that page number is less than 1")]
EmptyPage,
#[error("that page contains no results")]
InvalidPage { message: String },
}
#[derive(Debug, Clone)]
pub struct Paginator<T> {
object_list: Vec<T>,
per_page: usize,
orphans: usize,
allow_empty_first_page: bool,
}
impl<T: Clone> Paginator<T> {
#[must_use]
pub fn new(object_list: Vec<T>, per_page: usize) -> Self {
assert!(per_page > 0, "per_page must be greater than zero");
Self {
object_list,
per_page,
orphans: 0,
allow_empty_first_page: true,
}
}
#[must_use]
pub fn with_orphans(mut self, orphans: usize) -> Self {
self.orphans = orphans;
self
}
#[must_use]
pub fn with_allow_empty_first_page(mut self, allow: bool) -> Self {
self.allow_empty_first_page = allow;
self
}
pub fn validate_number(&self, number: usize) -> Result<usize, PaginatorError> {
if number == 0 {
return Err(PaginatorError::EmptyPage);
}
if number > self.num_pages() {
return Err(PaginatorError::InvalidPage {
message: NO_RESULTS_MESSAGE.to_string(),
});
}
Ok(number)
}
#[must_use]
pub fn get_page(&self, number: usize) -> Page<T> {
let num_pages = self.num_pages();
if num_pages == 0 {
return self.build_page(Vec::new(), 1);
}
let clamped = number.clamp(1, num_pages);
self.page(clamped)
.unwrap_or_else(|_| self.build_page(Vec::new(), 1))
}
pub fn page(&self, number: usize) -> Result<Page<T>, PaginatorError> {
let number = self.validate_number(number)?;
let bottom = (number - 1) * self.per_page;
let mut top = bottom + self.per_page;
let count = self.count();
if top + self.orphans >= count {
top = count;
}
Ok(self.build_page(self.object_list[bottom..top].to_vec(), number))
}
#[must_use]
pub fn count(&self) -> usize {
self.object_list.len()
}
#[must_use]
pub fn num_pages(&self) -> usize {
let count = self.count();
if count == 0 && !self.allow_empty_first_page {
return 0;
}
let hits = usize::max(1, count.saturating_sub(self.orphans));
hits.div_ceil(self.per_page)
}
#[must_use]
pub fn page_range(&self) -> RangeInclusive<usize> {
1..=self.num_pages().max(1)
}
fn build_page(&self, object_list: Vec<T>, number: usize) -> Page<T> {
Page {
object_list,
number,
paginator_count: self.count(),
paginator_num_pages: self.num_pages(),
paginator_per_page: self.per_page,
}
}
}
#[derive(Debug, Clone)]
pub struct Page<T> {
pub object_list: Vec<T>,
number: usize,
paginator_count: usize,
paginator_num_pages: usize,
paginator_per_page: usize,
}
impl<T> Page<T> {
#[must_use]
pub fn number(&self) -> usize {
self.number
}
#[must_use]
pub fn has_next(&self) -> bool {
self.number < self.paginator_num_pages
}
#[must_use]
pub fn has_previous(&self) -> bool {
self.number > 1
}
#[must_use]
pub fn has_other_pages(&self) -> bool {
self.paginator_num_pages > 1
}
#[must_use]
pub fn next_page_number(&self) -> Option<usize> {
self.has_next().then_some(self.number + 1)
}
#[must_use]
pub fn previous_page_number(&self) -> Option<usize> {
self.has_previous().then_some(self.number - 1)
}
#[must_use]
pub fn start_index(&self) -> usize {
if self.paginator_count == 0 {
return 0;
}
(self.paginator_per_page * (self.number - 1)) + 1
}
#[must_use]
pub fn end_index(&self) -> usize {
if self.paginator_count == 0 {
return 0;
}
if self.number == self.paginator_num_pages {
self.paginator_count
} else {
self.number * self.paginator_per_page
}
}
#[must_use]
pub fn len(&self) -> usize {
self.object_list.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.object_list.is_empty()
}
}