Skip to content
Clone
main
116 lines 2.6 KB Rust
use std::fmt;
use serde::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema};
pub const DEFAULT_PAGE_LIMIT: u64 = 50;
pub const MAX_PAGE_LIMIT: u64 = 100;
pub const MAX_OFFSET: u64 = 5000;
#[derive(Debug, Clone, Copy, Default, Deserialize, IntoParams, ToSchema)]
#[serde(deny_unknown_fields)]
pub struct PageQuery {
pub limit: Option<u64>,
pub offset: Option<u64>,
}
impl PageQuery {
pub fn resolve(&self, default: u64, max: u64) -> Result<(u64, u64), PageError> {
resolve_bounds(self.limit, self.offset, default, max)
}
pub fn bounds(&self) -> Result<(u64, u64), PageError> {
self.resolve(DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT)
}
}
pub fn page_bounds(
limit: Option<u64>,
offset: Option<u64>,
default: u64,
max: u64,
) -> Result<(u64, u64), PageError> {
resolve_bounds(limit, offset, default, max)
}
fn resolve_bounds(
limit: Option<u64>,
offset: Option<u64>,
default: u64,
max: u64,
) -> Result<(u64, u64), PageError> {
let limit = limit.unwrap_or(default);
if limit == 0 || limit > max {
return Err(PageError(format!("limit must be between 1 and {max}")));
}
let offset = offset.unwrap_or(0);
if offset > MAX_OFFSET {
return Err(PageError(format!("offset must not exceed {MAX_OFFSET}")));
}
Ok((limit, offset))
}
#[derive(Debug, Clone)]
pub struct PageError(String);
impl PageError {
pub fn message(&self) -> &str {
&self.0
}
}
impl fmt::Display for PageError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
impl std::error::Error for PageError {}
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct Page<T> {
pub items: Vec<T>,
pub total: u64,
pub limit: u64,
pub offset: u64,
}
impl<T> Page<T> {
pub fn new(items: Vec<T>, total: u64, limit: u64, offset: u64) -> Self {
Self {
items,
total,
limit,
offset,
}
}
}
pub fn paginate<T>(items: Vec<T>, page: &PageQuery) -> Result<Page<T>, PageError> {
let (limit, offset) = page.bounds()?;
let total = items.len() as u64;
let items = items
.into_iter()
.skip(offset as usize)
.take(limit as usize)
.collect();
Ok(Page {
items,
total,
limit,
offset,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum SortOrder {
Asc,
Desc,
}
impl SortOrder {
pub fn is_desc(self) -> bool {
matches!(self, Self::Desc)
}
}