rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
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()
    }
}