use serde::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema};
use crate::validation::{ValidationError, require_in_range};
pub const DEFAULT_PAGE: u32 = 1;
pub const DEFAULT_PAGE_SIZE: u32 = 20;
pub const MAX_PAGE_SIZE: u32 = 100;
fn default_page() -> u32 {
DEFAULT_PAGE
}
fn default_page_size() -> u32 {
DEFAULT_PAGE_SIZE
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, IntoParams)]
#[into_params(parameter_in = Query)]
pub struct PageRequest {
#[serde(default = "default_page")]
pub page: u32,
#[serde(default = "default_page_size")]
pub page_size: u32,
}
impl Default for PageRequest {
fn default() -> Self {
Self {
page: DEFAULT_PAGE,
page_size: DEFAULT_PAGE_SIZE,
}
}
}
impl PageRequest {
pub fn validate(&self) -> Result<(), ValidationError> {
let mut err = ValidationError::new();
if let Err(msg) = require_in_range(self.page, 1, u32::MAX, "page") {
err.add("page", msg);
}
if let Err(msg) = require_in_range(self.page_size, 1, MAX_PAGE_SIZE, "page_size") {
err.add("page_size", msg);
}
err.finish()
}
pub fn offset(&self) -> u64 {
(self.page.saturating_sub(1) as u64) * u64::from(self.page_size)
}
pub fn limit(&self) -> u32 {
self.page_size
}
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct PageResult<T> {
pub items: Vec<T>,
pub page: u32,
pub page_size: u32,
pub total: u64,
}
impl<T> PageResult<T> {
pub fn new(items: Vec<T>, request: &PageRequest, total: u64) -> Self {
Self {
items,
page: request.page,
page_size: request.page_size,
total,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_from_empty_json() {
let req: PageRequest = serde_json::from_str("{}").unwrap();
assert_eq!(req.page, 1);
assert_eq!(req.page_size, 20);
req.validate().unwrap();
}
#[test]
fn zero_page_fails() {
let req = PageRequest {
page: 0,
page_size: 20,
};
assert!(req.validate().is_err());
}
#[test]
fn page_size_cap() {
let req = PageRequest {
page: 1,
page_size: 101,
};
assert!(req.validate().is_err());
}
#[test]
fn offset_from_page() {
let req = PageRequest {
page: 3,
page_size: 20,
};
assert_eq!(req.offset(), 40);
}
}