use crate::{CoreError, CoreResult};
pub const DEFAULT_PAGE: usize = 1;
pub const DEFAULT_PAGE_SIZE: usize = 50;
pub const MAX_PAGE_SIZE: usize = 500;
pub fn resolve_page_params(
page: Option<u32>,
page_size: Option<u32>,
) -> CoreResult<(usize, usize)> {
let page = page.map(|p| p as usize).unwrap_or(DEFAULT_PAGE);
let page_size = page_size.map(|p| p as usize).unwrap_or(DEFAULT_PAGE_SIZE);
if page == 0 {
return Err(CoreError::Validation(
"page must be >= 1 (1-based)".to_string(),
));
}
if page_size == 0 {
return Err(CoreError::Validation("page_size must be >= 1".to_string()));
}
if page_size > MAX_PAGE_SIZE {
return Err(CoreError::Validation(format!(
"page_size must be <= {MAX_PAGE_SIZE}"
)));
}
Ok((page, page_size))
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PaginatedList<T> {
pub items: Vec<T>,
pub total: usize,
pub page: usize,
pub page_size: usize,
pub total_pages: usize,
}
impl<T> PaginatedList<T> {
pub fn paginate(items: Vec<T>, page: usize, page_size: usize) -> CoreResult<Self> {
if page == 0 {
return Err(CoreError::Validation(
"page must be >= 1 (1-based)".to_string(),
));
}
if page_size == 0 {
return Err(CoreError::Validation("page_size must be >= 1".to_string()));
}
if page_size > MAX_PAGE_SIZE {
return Err(CoreError::Validation(format!(
"page_size must be <= {MAX_PAGE_SIZE}"
)));
}
let total = items.len();
let total_pages = total.div_ceil(page_size);
let offset = (page - 1).saturating_mul(page_size);
let items = items.into_iter().skip(offset).take(page_size).collect();
Ok(Self {
items,
total,
page,
page_size,
total_pages,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_paginate_normal() {
let items: Vec<i32> = (1..=10).collect();
let result = PaginatedList::paginate(items, 2, 3).unwrap();
assert_eq!(result.items, vec![4, 5, 6]);
assert_eq!(result.total, 10);
assert_eq!(result.total_pages, 4);
assert_eq!(result.page, 2);
}
#[test]
fn test_paginate_empty() {
let result = PaginatedList::<i32>::paginate(vec![], 1, 10).unwrap();
assert_eq!(result.items, Vec::<i32>::new());
assert_eq!(result.total, 0);
assert_eq!(result.total_pages, 0);
assert_eq!(result.page, 1);
}
#[test]
fn test_paginate_page_zero_errors() {
let items: Vec<i32> = (1..=5).collect();
let err = PaginatedList::paginate(items, 0, 3).unwrap_err();
assert!(err.to_string().contains("page must be >= 1"));
}
#[test]
fn test_paginate_page_size_zero_errors() {
let items: Vec<i32> = (1..=5).collect();
let err = PaginatedList::paginate(items, 1, 0).unwrap_err();
assert!(err.to_string().contains("page_size must be >= 1"));
}
#[test]
fn test_paginate_out_of_bounds() {
let items: Vec<i32> = (1..=5).collect();
let result = PaginatedList::paginate(items, 3, 5).unwrap();
assert_eq!(result.items, Vec::<i32>::new());
assert_eq!(result.total, 5);
assert_eq!(result.total_pages, 1);
}
#[test]
fn test_paginate_fits_on_one_page() {
let items: Vec<i32> = (1..=3).collect();
let result = PaginatedList::paginate(items, 1, 10).unwrap();
assert_eq!(result.items, vec![1, 2, 3]);
assert_eq!(result.total_pages, 1);
}
#[test]
fn test_paginate_page_size_too_large_errors() {
let items: Vec<i32> = (1..=5).collect();
let err = PaginatedList::paginate(items, 1, MAX_PAGE_SIZE + 1).unwrap_err();
assert!(err.to_string().contains("page_size must be <="));
}
#[test]
fn test_paginate_exact_boundary() {
let items: Vec<i32> = (1..=9).collect();
let result = PaginatedList::paginate(items, 3, 3).unwrap();
assert_eq!(result.items, vec![7, 8, 9]);
assert_eq!(result.total_pages, 3);
}
#[test]
fn test_resolve_page_params_defaults() {
let (page, page_size) = resolve_page_params(None, None).unwrap();
assert_eq!(page, DEFAULT_PAGE);
assert_eq!(page_size, DEFAULT_PAGE_SIZE);
}
#[test]
fn test_resolve_page_params_explicit_values() {
let (page, page_size) = resolve_page_params(Some(3), Some(25)).unwrap();
assert_eq!(page, 3);
assert_eq!(page_size, 25);
}
#[test]
fn test_resolve_page_params_max_page_size() {
let (page, page_size) = resolve_page_params(Some(1), Some(MAX_PAGE_SIZE as u32)).unwrap();
assert_eq!(page, 1);
assert_eq!(page_size, MAX_PAGE_SIZE);
}
#[test]
fn test_resolve_page_params_page_zero_errors() {
let err = resolve_page_params(Some(0), None).unwrap_err();
assert!(err.to_string().contains("page must be >= 1"));
}
#[test]
fn test_resolve_page_params_page_size_zero_errors() {
let err = resolve_page_params(None, Some(0)).unwrap_err();
assert!(err.to_string().contains("page_size must be >= 1"));
}
#[test]
fn test_resolve_page_params_page_size_too_large_errors() {
let err = resolve_page_params(None, Some(MAX_PAGE_SIZE as u32 + 1)).unwrap_err();
assert!(err.to_string().contains("page_size must be <="));
}
#[test]
fn test_paginate_empty_total_pages_is_zero() {
let result = PaginatedList::<i32>::paginate(vec![], 1, 10).unwrap();
assert_eq!(result.total, 0);
assert_eq!(result.total_pages, 0);
assert_eq!(result.items, Vec::<i32>::new());
}
}