use serde::{Deserialize, Serialize};
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
#[derive(Debug, Clone, Copy, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema, utoipa::IntoParams))]
pub struct PageRequest {
#[serde(default = "default_page")]
pub page: u32,
#[serde(default = "default_size")]
pub size: u32,
}
fn default_page() -> u32 {
1
}
fn default_size() -> u32 {
10
}
impl Default for PageRequest {
fn default() -> Self {
Self {
page: default_page(),
size: default_size(),
}
}
}
impl PageRequest {
pub fn normalized(self, max_page_size: u32) -> Self {
let size = self.size.clamp(1, max_page_size.max(1));
let page = self.page.max(1);
Self { page, size }
}
pub fn normalized_with_default(self, default_size: u32, max_page_size: u32) -> Self {
let max_size = max_page_size.max(1);
let default_size = default_size.clamp(1, max_size);
let size = if self.size == 0 {
default_size
} else {
self.size.clamp(1, max_size)
};
let page = self.page.max(1);
Self { page, size }
}
pub fn offset_limit(self) -> (u32, u32) {
let page = self.page.max(1);
let size = self.size.max(1);
let offset = (page - 1).saturating_mul(size);
(offset, size)
}
}
#[cfg(test)]
mod tests {
use super::PageRequest;
#[test]
fn normalized_with_default_uses_default_for_zero_size() {
let page = PageRequest { page: 0, size: 0 }.normalized_with_default(100, 500);
assert_eq!(page.page, 1);
assert_eq!(page.size, 100);
}
#[test]
fn normalized_with_default_clamps_to_max_size() {
let page = PageRequest {
page: 2,
size: 1_000,
}
.normalized_with_default(100, 500);
assert_eq!(page.page, 2);
assert_eq!(page.size, 500);
}
#[test]
fn normalized_with_default_keeps_minimum_size_safe() {
let page = PageRequest { page: 1, size: 1 }.normalized_with_default(0, 0);
assert_eq!(page.page, 1);
assert_eq!(page.size, 1);
}
#[test]
fn offset_limit_is_one_based() {
let (offset, limit) = PageRequest { page: 3, size: 25 }.offset_limit();
assert_eq!(offset, 50);
assert_eq!(limit, 25);
}
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct Paged<T> {
pub items: Vec<T>,
pub total_items: u64,
pub page: u32,
pub size: u32,
pub total_pages: u32,
}
impl<T> Paged<T> {
pub fn new(items: Vec<T>, total_items: u64, page: u32, size: u32) -> Self {
let size = size.max(1);
let total_pages = if total_items == 0 {
0
} else {
((total_items + size as u64 - 1) / size as u64) as u32
};
Self {
items,
total_items,
page: page.max(1),
size,
total_pages,
}
}
}