use crate::clip::Clip;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PageRequest {
pub offset: usize,
pub page_size: usize,
}
impl PageRequest {
#[must_use]
pub fn new(offset: usize, page_size: usize) -> Self {
Self {
offset,
page_size: page_size.max(1),
}
}
#[must_use]
pub fn first(page_size: usize) -> Self {
Self::new(0, page_size)
}
#[must_use]
pub fn next_page(&self, total_items: usize) -> Option<Self> {
let next_offset = self.offset + self.page_size;
if next_offset < total_items {
Some(Self::new(next_offset, self.page_size))
} else {
None
}
}
#[must_use]
pub fn page_number(&self) -> usize {
self.offset / self.page_size + 1
}
#[must_use]
pub fn total_pages(&self, total_items: usize) -> usize {
if self.page_size == 0 {
return 0;
}
(total_items + self.page_size - 1) / self.page_size
}
}
impl Default for PageRequest {
fn default() -> Self {
Self::new(0, 50)
}
}
#[derive(Debug, Clone)]
pub struct PageResult<T> {
pub items: Vec<T>,
pub total_items: usize,
pub request: PageRequest,
}
impl<T> PageResult<T> {
#[must_use]
pub fn new(items: Vec<T>, total_items: usize, request: PageRequest) -> Self {
Self {
items,
total_items,
request,
}
}
#[must_use]
pub fn page_len(&self) -> usize {
self.items.len()
}
#[must_use]
pub fn has_next_page(&self) -> bool {
self.request.offset + self.request.page_size < self.total_items
}
#[must_use]
pub fn has_prev_page(&self) -> bool {
self.request.offset > 0
}
#[must_use]
pub fn next_page_request(&self) -> Option<PageRequest> {
self.request.next_page(self.total_items)
}
#[must_use]
pub fn page_number(&self) -> usize {
self.request.page_number()
}
#[must_use]
pub fn total_pages(&self) -> usize {
self.request.total_pages(self.total_items)
}
pub fn map<U, F: Fn(T) -> U>(self, f: F) -> PageResult<U> {
PageResult {
items: self.items.into_iter().map(f).collect(),
total_items: self.total_items,
request: self.request,
}
}
}
#[must_use]
pub fn paginate_clips(clips: &[Clip], request: PageRequest) -> PageResult<Clip> {
let total_items = clips.len();
let start = request.offset.min(total_items);
let end = (request.offset + request.page_size).min(total_items);
let items: Vec<Clip> = clips[start..end].to_vec();
PageResult::new(items, total_items, request)
}
#[must_use]
pub fn paginate_refs<'a, T>(items: &'a [T], request: PageRequest) -> PageResult<&'a T> {
let total_items = items.len();
let start = request.offset.min(total_items);
let end = (request.offset + request.page_size).min(total_items);
let page: Vec<&T> = items[start..end].iter().collect();
PageResult::new(page, total_items, request)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn make_clips(n: usize) -> Vec<Clip> {
(0..n)
.map(|i| {
let mut c = Clip::new(PathBuf::from(format!("/clip_{i}.mov")));
c.set_name(&format!("Clip {i}"));
c
})
.collect()
}
#[test]
fn test_page_request_new_clamps_page_size() {
let req = PageRequest::new(0, 0);
assert_eq!(req.page_size, 1);
}
#[test]
fn test_page_request_first() {
let req = PageRequest::first(10);
assert_eq!(req.offset, 0);
assert_eq!(req.page_size, 10);
}
#[test]
fn test_page_number() {
let req = PageRequest::new(20, 10);
assert_eq!(req.page_number(), 3);
}
#[test]
fn test_total_pages() {
let req = PageRequest::new(0, 10);
assert_eq!(req.total_pages(25), 3);
assert_eq!(req.total_pages(20), 2);
assert_eq!(req.total_pages(0), 0);
}
#[test]
fn test_next_page_returns_none_at_end() {
let req = PageRequest::new(40, 10);
assert!(req.next_page(50).is_none());
}
#[test]
fn test_next_page_returns_some_when_more_items() {
let req = PageRequest::new(0, 10);
let next = req.next_page(25);
assert!(next.is_some());
assert_eq!(next.unwrap().offset, 10);
}
#[test]
fn test_paginate_clips_first_page() {
let clips = make_clips(25);
let req = PageRequest::first(10);
let page = paginate_clips(&clips, req);
assert_eq!(page.page_len(), 10);
assert_eq!(page.total_items, 25);
assert!(page.has_next_page());
assert!(!page.has_prev_page());
}
#[test]
fn test_paginate_clips_last_page() {
let clips = make_clips(25);
let req = PageRequest::new(20, 10);
let page = paginate_clips(&clips, req);
assert_eq!(page.page_len(), 5);
assert!(!page.has_next_page());
assert!(page.has_prev_page());
}
#[test]
fn test_paginate_empty_slice() {
let clips: Vec<Clip> = Vec::new();
let req = PageRequest::first(10);
let page = paginate_clips(&clips, req);
assert_eq!(page.page_len(), 0);
assert_eq!(page.total_items, 0);
assert!(!page.has_next_page());
}
#[test]
fn test_paginate_refs() {
let data: Vec<i32> = (0..15).collect();
let req = PageRequest::new(5, 5);
let page = paginate_refs(&data, req);
assert_eq!(page.page_len(), 5);
assert_eq!(*page.items[0], 5);
assert_eq!(*page.items[4], 9);
}
#[test]
fn test_page_result_map() {
let data: Vec<i32> = (1..=10).collect();
let req = PageRequest::first(10);
let page = paginate_refs(&data, req);
let doubled = page.map(|&x| x * 2);
assert_eq!(doubled.items[0], 2);
assert_eq!(doubled.total_items, 10);
}
#[test]
fn test_page_result_total_pages() {
let clips = make_clips(30);
let req = PageRequest::new(0, 7);
let page = paginate_clips(&clips, req);
assert_eq!(page.total_pages(), 5); }
#[test]
fn test_next_page_request_chaining() {
let clips = make_clips(100);
let mut req = PageRequest::first(20);
let mut pages_visited = 0usize;
loop {
let page = paginate_clips(&clips, req);
pages_visited += 1;
match page.next_page_request() {
Some(next) => req = next,
None => break,
}
}
assert_eq!(pages_visited, 5);
}
}