module util.pagination; import handy_httpd; import handy_httpd.components.multivalue_map; import handy_httpd.components.optional; 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(ref HttpRequestContext ctx, PageRequest defaults) { import std.algorithm; import std.array; const(StringMultiValueMap) params = ctx.request.queryParams; uint pg = ctx.request.getParamAs!uint("page", defaults.page); ushort sz = ctx.request.getParamAs!ushort("size", defaults.size); Sort[] s = params.getAll("sort") .map!(Sort.parse) .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; }