Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
/// Take the 1-based page `page` of `items`, `per_page` items at a time.
///
/// Page numbers are 1-based because every pagination UI, query string and API
/// contract in the wild is 1-based; page 0 can only come from a caller that has
/// the contract wrong, so it panics.
///
/// A page past the end is deliberately not an error. Between two requests a
/// collection can shrink under a client that is already on page 9, and that
/// client should render an empty page, not a 500.
///
/// Generic over the item type, like the TypeScript and Python versions: a page
/// of `Invoice` is a `Page<Invoice>`, and a page of open JSON records is a
/// `Page<Value>`.
///
/// # Panics
/// Panics if `per_page` is less than 1 or `page` is less than 1.
pub fn paginate<T: Clone>(items: &[T], page: i64, per_page: i64) -> Page<T> {
// A page size of zero cannot mean "no limit": that would quietly hand back
// the entire collection, which is the outage pagination exists to prevent.
if per_page < 1 {
panic!("per_page must be at least 1, received {}", per_page);
}
if page < 1 {
panic!("page must be 1 or greater, received {}", page);
}
let total = items.len() as i64;
// Zero items is zero pages, not one empty page, so `page <= total_pages` is
// the one correct test for "does this page exist".
let total_pages = (total + per_page - 1) / per_page;
// Clamp rather than index: a start past the end is the documented
// empty-page case, not a slice panic.
let start = ((page - 1) * per_page).min(total) as usize;
let end = (start as i64 + per_page).min(total) as usize;
Page {
items: items[start..end].to_vec(),
page,
per_page,
total,
total_pages,
has_next: page < total_pages,
// On page 9 of a 2-page collection there really are earlier pages.
has_prev: page > 1,
}
}
/// The 1-based page number a zero-based item index falls on.
///
/// # Panics
/// Panics if `index` is negative or `per_page` is less than 1.
pub fn page_of_index(index: i64, per_page: i64) -> i64 {
if index < 0 {
panic!("index must be a whole number of 0 or greater, received {}", index);
}
if per_page < 1 {
panic!("per_page must be at least 1, received {}", per_page);
}
index / per_page + 1
}
pub fn page_to_value(page: &Page<Value>) -> Value {
Value::obj(vec![
("items", Value::Arr(page.items.clone())),
("page", Value::Int(page.page)),
("perPage", Value::Int(page.per_page)),
("total", Value::Int(page.total)),
("totalPages", Value::Int(page.total_pages)),
("hasNext", Value::Bool(page.has_next)),
("hasPrev", Value::Bool(page.has_prev)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
// Refuse what the typed signature cannot hold, with the wording TypeScript
// and Python use, rather than let the conversion below quietly change it.
if let Value::Float(f) = args[1] {
if f.fract() != 0.0 {
panic!("page must be a whole number, received {}", f);
}
}
if let Value::Float(f) = args[2] {
if f.fract() != 0.0 {
panic!("perPage must be a whole number, received {}", f);
}
}
page_to_value(&paginate(
args[0].as_arr(),
args[1].as_i64(),
args[2].as_i64(),
))
}