/** * 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 { immutable uint page; immutable ushort size; 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; 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 ~= " "; } app ~= "LIMIT "; app ~= size.to!string; app ~= " OFFSET "; app ~= page.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); } } struct Page(T) { T[] items; PageRequest pageRequest; } Page!U mapItems(T, U)(in Page!T page, U function(const(T)) fn) { import std.algorithm : map; import std.array : array; return Page!U( page.items.map!fn.array, page.pageRequest ); }