use serde::Deserialize;
use serde_json::{Value, json};
use crate::config::Config;
const DEFAULT_PAGE_SIZE: i64 = 50;
const MAX_OFFSET: i64 = i64::MAX / 2;
#[derive(Debug, Deserialize, Default)]
pub struct PageParams {
pub limit: Option<i64>,
pub offset: Option<i64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Page {
pub limit: i64,
pub offset: i64,
}
impl PageParams {
#[must_use]
pub fn from(limit: Option<i64>, offset: Option<i64>) -> Self {
Self { limit, offset }
}
#[must_use]
pub fn resolve(&self, config: &Config) -> Page {
let max = config.admin.page_size_max.max(1);
Page {
limit: self.limit.unwrap_or(DEFAULT_PAGE_SIZE).clamp(1, max),
offset: self.offset.unwrap_or(0).clamp(0, MAX_OFFSET),
}
}
}
#[must_use]
pub fn page_envelope(items: Vec<Value>, total: i64, page: Page) -> Value {
json!({
"items": items,
"total": total,
"limit": page.limit,
"offset": page.offset,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn config_with_max(max: i64) -> Config {
let mut config = Config::default();
config.admin.page_size_max = max;
config
}
#[test]
fn an_absent_window_is_the_default_page() {
let page = PageParams::default().resolve(&Config::default());
assert_eq!(
page,
Page {
limit: 50,
offset: 0
}
);
}
#[test]
fn a_limit_is_clamped_into_range_rather_than_refused() {
let config = config_with_max(200);
let cases = [
(Some(10), 10),
(Some(200), 200),
(Some(100_000), 200),
(Some(0), 1),
(Some(-5), 1),
];
for (requested, expected) in cases {
let page = PageParams {
limit: requested,
offset: None,
}
.resolve(&config);
assert_eq!(page.limit, expected, "limit={requested:?}");
}
}
#[test]
fn a_negative_offset_becomes_zero() {
let page = PageParams {
limit: None,
offset: Some(-7),
}
.resolve(&Config::default());
assert_eq!(page.offset, 0);
}
#[test]
fn an_offset_at_the_top_of_the_range_is_clamped_below_an_overflow() {
let page = PageParams {
limit: None,
offset: Some(i64::MAX),
}
.resolve(&Config::default());
assert_eq!(page.offset, MAX_OFFSET);
assert!(page.offset.checked_add(page.limit).is_some());
}
#[test]
fn a_page_size_max_of_zero_still_yields_a_usable_page() {
let page = PageParams {
limit: Some(50),
offset: None,
}
.resolve(&config_with_max(0));
assert_eq!(page.limit, 1);
}
#[test]
fn the_envelope_reports_the_window_it_was_given() {
let page = Page {
limit: 2,
offset: 4,
};
let envelope = page_envelope(vec![json!({"id": "a"})], 17, page);
assert_eq!(envelope["total"], 17);
assert_eq!(envelope["limit"], 2);
assert_eq!(envelope["offset"], 4);
assert_eq!(envelope["items"].as_array().unwrap().len(), 1);
}
}