161 lines
4.3 KiB
D
161 lines
4.3 KiB
D
/**
|
|
* Defines various components useful for paginated operations.
|
|
*/
|
|
module util.pagination;
|
|
|
|
import handy_http_primitives;
|
|
|
|
import std.conv;
|
|
|
|
enum SortDir : string {
|
|
ASC = "ASC",
|
|
DESC = "DESC"
|
|
}
|
|
|
|
struct Sort {
|
|
immutable string attribute;
|
|
immutable SortDir dir;
|
|
|
|
static Optional!Sort parse(string expr) {
|
|
import std.string;
|
|
string[] parts = expr.split(",");
|
|
if (parts.length == 1) return Optional!Sort.of(Sort(parts[0], SortDir.ASC));
|
|
if (parts.length != 2) return Optional!Sort.empty;
|
|
string attr = parts[0];
|
|
string dirExpr = parts[1];
|
|
SortDir d;
|
|
if (dirExpr == SortDir.ASC) {
|
|
d = SortDir.ASC;
|
|
} else if (dirExpr == SortDir.DESC) {
|
|
d = SortDir.DESC;
|
|
} else {
|
|
return Optional!Sort.empty;
|
|
}
|
|
return Optional!Sort.of(Sort(attr, d));
|
|
}
|
|
}
|
|
|
|
struct PageRequest {
|
|
/**
|
|
* The requested page number, starting from 1 for the first page, or zero
|
|
* for an unpaged request.
|
|
*/
|
|
immutable uint page;
|
|
|
|
/**
|
|
* The maximum number of items to include in each page of results.
|
|
*/
|
|
immutable ushort size;
|
|
|
|
/**
|
|
* A list of sorts to apply.
|
|
*/
|
|
immutable Sort[] sorts;
|
|
|
|
bool isUnpaged() const {
|
|
return page < 1;
|
|
}
|
|
|
|
static PageRequest unpaged() {
|
|
return PageRequest(0, 0, []);
|
|
}
|
|
|
|
static PageRequest parse(in ServerHttpRequest request, PageRequest defaults) {
|
|
import std.algorithm;
|
|
import std.array;
|
|
uint pg = request.getParamAs!uint("page", defaults.page);
|
|
ushort sz = request.getParamAs!ushort("size", defaults.size);
|
|
Sort[] s = request.queryParams
|
|
.filter!(p => p.key == "sort" && p.values.length > 0)
|
|
.map!(p => Sort.parse(p.values[0]))
|
|
.filter!(o => !o.isNull)
|
|
.map!(o => o.value)
|
|
.array;
|
|
if (s.length == 0 && defaults.sorts.length > 0) {
|
|
s = defaults.sorts.dup;
|
|
}
|
|
return PageRequest(pg, sz, s.idup);
|
|
}
|
|
|
|
string toSql() const {
|
|
import std.array;
|
|
auto app = appender!string;
|
|
|
|
if (sorts.length > 0) {
|
|
app ~= "ORDER BY ";
|
|
for (size_t i = 0; i < sorts.length; i++) {
|
|
app ~= sorts[i].attribute;
|
|
app ~= " ";
|
|
app ~= cast(string) sorts[i].dir;
|
|
if (i + 1 < sorts.length) app ~= ",";
|
|
}
|
|
app ~= " ";
|
|
}
|
|
if (!isUnpaged()) {
|
|
app ~= "LIMIT ";
|
|
app ~= size.to!string;
|
|
app ~= " OFFSET ";
|
|
app ~= ((page - 1) * size).to!string;
|
|
}
|
|
return app[];
|
|
}
|
|
|
|
PageRequest next() const {
|
|
if (isUnpaged) return this;
|
|
return PageRequest(page + 1, size, sorts);
|
|
}
|
|
|
|
PageRequest prev() const {
|
|
if (isUnpaged) return this;
|
|
return PageRequest(page - 1, size, sorts);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Container for a paginated response, which contains the actual page of items,
|
|
* as well as some metadata to assist any API client in navigating to other
|
|
* pages.
|
|
*/
|
|
struct Page(T) {
|
|
T[] items;
|
|
PageRequest pageRequest;
|
|
ulong totalElements;
|
|
ulong totalPages;
|
|
bool isFirst;
|
|
bool isLast;
|
|
|
|
|
|
Page!U mapTo(U)(U function(T) fn) {
|
|
import std.algorithm : map;
|
|
import std.array : array;
|
|
return Page!(U)(items.map!(fn).array, pageRequest, totalElements, totalPages, isFirst, isLast);
|
|
}
|
|
|
|
static Page of(T[] items, PageRequest pageRequest, ulong totalCount) {
|
|
ulong pageCount = getTotalPageCount(totalCount, pageRequest.size);
|
|
return Page(
|
|
items,
|
|
pageRequest,
|
|
totalCount,
|
|
pageCount,
|
|
pageRequest.page == 1,
|
|
pageRequest.page == pageCount
|
|
);
|
|
}
|
|
}
|
|
|
|
private ulong getTotalPageCount(ulong totalElements, ulong pageSize) {
|
|
return totalElements / pageSize + (totalElements % pageSize > 0 ? 1 : 0);
|
|
}
|
|
|
|
unittest {
|
|
assert(getTotalPageCount(5, 1) == 5);
|
|
assert(getTotalPageCount(5, 2) == 3);
|
|
assert(getTotalPageCount(5, 3) == 2);
|
|
assert(getTotalPageCount(5, 4) == 2);
|
|
assert(getTotalPageCount(5, 5) == 1);
|
|
assert(getTotalPageCount(5, 6) == 1);
|
|
assert(getTotalPageCount(5, 123) == 1);
|
|
assert(getTotalPageCount(250, 100) == 3);
|
|
}
|