use axum::extract::Query;
use serde::Deserialize;
use crate::errors::response::{ApiResponse, PaginatedData};
#[derive(Debug, Clone, Deserialize, Default)]
pub struct PaginationParams {
#[serde(default = "default_page")]
pub page: i64,
#[serde(default = "default_page_size")]
pub page_size: i64,
}
fn default_page() -> i64 {
1
}
fn default_page_size() -> i64 {
20
}
impl PaginationParams {
pub const MAX_PAGE_SIZE: i64 = 100;
pub fn paginate<T: serde::Serialize>(
self,
items: Vec<T>,
total: i64,
) -> ApiResponse<PaginatedData<T>> {
ApiResponse::success(PaginatedData {
items,
total,
page: self.page,
page_size: self.page_size,
})
}
pub fn from_options(page: Option<i64>, page_size: Option<i64>) -> Self {
let mut params = Self::default();
if let Some(p) = page {
params.page = p.max(1);
}
if let Some(ps) = page_size {
params.page_size = ps.clamp(1, Self::MAX_PAGE_SIZE);
}
params.sanitize();
params
}
pub fn paginate_in_memory<T>(self, all: Vec<T>) -> ApiResponse<PaginatedData<T>>
where
T: serde::Serialize,
{
let total = all.len() as i64;
let offset = self.offset() as usize;
let items: Vec<_> = all
.into_iter()
.skip(offset)
.take(self.page_size as usize)
.collect();
self.paginate(items, total)
}
#[must_use]
pub fn offset(&self) -> i64 {
(self.page - 1).saturating_mul(self.page_size)
}
pub fn sanitize(&mut self) {
self.page = self.page.max(1);
self.page_size = self.page_size.clamp(1, Self::MAX_PAGE_SIZE);
}
}
impl From<Query<PaginationParams>> for PaginationParams {
fn from(query: Query<PaginationParams>) -> Self {
let mut params = query.0;
params.sanitize();
params
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn offset_page1() {
let p = PaginationParams {
page: 1,
page_size: 20,
};
assert_eq!(p.offset(), 0);
}
#[test]
fn offset_page3() {
let p = PaginationParams {
page: 3,
page_size: 10,
};
assert_eq!(p.offset(), 20);
}
#[test]
fn sanitize_clamps_page_to_one() {
let mut p = PaginationParams {
page: -5,
page_size: 20,
};
p.sanitize();
assert_eq!(p.page, 1);
}
#[test]
fn sanitize_clamps_page_size_to_max() {
let mut p = PaginationParams {
page: 1,
page_size: 999,
};
p.sanitize();
assert_eq!(p.page_size, PaginationParams::MAX_PAGE_SIZE);
}
#[test]
fn sanitize_clamps_page_size_to_one() {
let mut p = PaginationParams {
page: 1,
page_size: 0,
};
p.sanitize();
assert_eq!(p.page_size, 1);
}
}