Move query format helpers into common code
This commit is contained in:
parent
bfd73b7a1f
commit
4e81441a0d
2 changed files with 48 additions and 118 deletions
|
@ -1,6 +1,7 @@
|
|||
const std = @import("std");
|
||||
const api = @import("api");
|
||||
const util = @import("util");
|
||||
const query_utils = @import("../query.zig");
|
||||
|
||||
const QueryArgs = api.CommunityQueryArgs;
|
||||
const Uuid = util.Uuid;
|
||||
|
@ -25,43 +26,6 @@ pub const query = struct {
|
|||
pub const method = .GET;
|
||||
pub const path = "/communities";
|
||||
|
||||
// NOTE: This has to match QueryArgs
|
||||
// TODO: Support union fields in query strings natively, so we don't
|
||||
// have to keep these in sync
|
||||
pub const QueryOld = struct {
|
||||
const OrderBy = QueryArgs.OrderBy;
|
||||
const Direction = QueryArgs.Direction;
|
||||
const PageDirection = QueryArgs.PageDirection;
|
||||
|
||||
// Max items to fetch
|
||||
max_items: usize = 20,
|
||||
|
||||
// Selection filters
|
||||
owner_id: ?Uuid = null,
|
||||
like: ?[]const u8 = null,
|
||||
created_before: ?DateTime = null,
|
||||
created_after: ?DateTime = null,
|
||||
|
||||
// Ordering parameter
|
||||
order_by: OrderBy = .created_at,
|
||||
direction: Direction = .ascending,
|
||||
|
||||
// the `prev` struct has a slightly different format to QueryArgs
|
||||
prev: struct {
|
||||
id: ?Uuid = null,
|
||||
|
||||
// Only one of these can be present, and must match order_by above
|
||||
name: ?[]const u8 = null,
|
||||
host: ?[]const u8 = null,
|
||||
created_at: ?DateTime = null,
|
||||
} = .{},
|
||||
|
||||
// What direction to scan the page window
|
||||
page_direction: PageDirection = .forward,
|
||||
|
||||
pub const format = formatQueryParams;
|
||||
};
|
||||
|
||||
pub const Query = QueryArgs;
|
||||
|
||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||
|
@ -71,12 +35,9 @@ pub const query = struct {
|
|||
const link_writer = link.writer();
|
||||
defer link.deinit();
|
||||
|
||||
const next_page = queryArgsToControllerQuery(results.next_page);
|
||||
const prev_page = queryArgsToControllerQuery(results.prev_page);
|
||||
|
||||
try writeLink(link_writer, srv.community, path, next_page, "next");
|
||||
try writeLink(link_writer, srv.community, path, results.next_page, "next");
|
||||
try link_writer.writeByte(',');
|
||||
try writeLink(link_writer, srv.community, path, prev_page, "prev");
|
||||
try writeLink(link_writer, srv.community, path, results.prev_page, "prev");
|
||||
|
||||
try res.headers.put("Link", link.items);
|
||||
|
||||
|
@ -98,7 +59,7 @@ fn writeLink(
|
|||
.{ @tagName(community.scheme), community.host, path },
|
||||
);
|
||||
|
||||
try std.fmt.format(writer, "{}", .{params});
|
||||
try query_utils.formatQuery(params, writer);
|
||||
|
||||
try std.fmt.format(
|
||||
writer,
|
||||
|
@ -106,78 +67,3 @@ fn writeLink(
|
|||
.{rel},
|
||||
);
|
||||
}
|
||||
|
||||
fn formatQueryParams(
|
||||
params: anytype,
|
||||
comptime fmt: []const u8,
|
||||
opt: std.fmt.FormatOptions,
|
||||
writer: anytype,
|
||||
) !void {
|
||||
if (comptime std.meta.trait.is(.Pointer)(@TypeOf(params))) {
|
||||
return formatQueryParams(params.*, fmt, opt, writer);
|
||||
}
|
||||
|
||||
return formatRecursive("", params, writer);
|
||||
}
|
||||
|
||||
fn formatField(comptime name: []const u8, val: anytype, writer: anytype) !void {
|
||||
// TODO: percent-encode this
|
||||
_ = try switch (@TypeOf(val)) {
|
||||
[]const u8 => blk: {
|
||||
break :blk std.fmt.format(writer, "{s}={s}&", .{ name, val });
|
||||
},
|
||||
|
||||
else => |U| blk: {
|
||||
if (comptime std.meta.trait.isContainer(U) and std.meta.trait.hasFn("format")(U)) {
|
||||
break :blk std.fmt.format(writer, "{s}={}&", .{ name, val });
|
||||
}
|
||||
|
||||
break :blk switch (@typeInfo(U)) {
|
||||
.Enum => std.fmt.format(writer, "{s}={s}&", .{ name, @tagName(val) }),
|
||||
.Struct => formatRecursive(name ++ ".", val, writer),
|
||||
else => std.fmt.format(writer, "{s}={}&", .{ name, val }),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn formatRecursive(comptime prefix: []const u8, params: anytype, writer: anytype) !void {
|
||||
inline for (std.meta.fields(@TypeOf(params))) |field| {
|
||||
const val = @field(params, field.name);
|
||||
const name = prefix ++ field.name;
|
||||
if (comptime std.meta.trait.is(.Optional)(field.field_type)) {
|
||||
if (val) |v| {
|
||||
try formatField(name, v, writer);
|
||||
}
|
||||
} else {
|
||||
try formatField(name, val, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn queryArgsToControllerQuery(args: QueryArgs) query.Query {
|
||||
return args;
|
||||
|
||||
// var result = query.Query{
|
||||
// .max_items = args.max_items,
|
||||
// .owner_id = args.owner_id,
|
||||
// .like = args.like,
|
||||
// .created_before = args.created_before,
|
||||
// .created_after = args.created_after,
|
||||
// .order_by = args.order_by,
|
||||
// .direction = args.direction,
|
||||
// .prev = .{},
|
||||
// .page_direction = args.page_direction,
|
||||
// };
|
||||
|
||||
// if (args.prev) |prev| {
|
||||
// result.prev = .{
|
||||
// .id = prev.id,
|
||||
// .name = if (prev.order_val == .name) prev.order_val.name else null,
|
||||
// .host = if (prev.order_val == .host) prev.order_val.host else null,
|
||||
// .created_at = if (prev.order_val == .created_at) prev.order_val.created_at else null,
|
||||
// };
|
||||
// }
|
||||
|
||||
// return result;
|
||||
}
|
||||
|
|
|
@ -252,6 +252,50 @@ fn isScalar(comptime T: type) bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
pub fn formatQuery(params: anytype, writer: anytype) !void {
|
||||
try format("", "", params, writer);
|
||||
}
|
||||
|
||||
fn formatScalar(comptime name: []const u8, val: anytype, writer: anytype) !void {
|
||||
const T = @TypeOf(val);
|
||||
if (comptime std.meta.trait.isZigString(T)) return std.fmt.format(writer, "{s}={s}&", .{ name, val });
|
||||
_ = try switch (@typeInfo(T)) {
|
||||
.Enum => std.fmt.format(writer, "{s}={s}&", .{ name, @tagName(val) }),
|
||||
.Optional => if (val) |v| formatScalar(name, v, writer),
|
||||
else => std.fmt.format(writer, "{s}={}&", .{ name, val }),
|
||||
};
|
||||
}
|
||||
|
||||
fn format(comptime prefix: []const u8, comptime name: []const u8, params: anytype, writer: anytype) !void {
|
||||
const T = @TypeOf(params);
|
||||
const eff_prefix = if (prefix.len == 0) "" else prefix ++ ".";
|
||||
if (comptime isScalar(T)) return formatScalar(eff_prefix ++ name, params, writer);
|
||||
|
||||
switch (@typeInfo(T)) {
|
||||
.Struct => {
|
||||
inline for (std.meta.fields(T)) |field| {
|
||||
const val = @field(params, field.name);
|
||||
try format(eff_prefix ++ name, field.name, val, writer);
|
||||
}
|
||||
},
|
||||
.Union => {
|
||||
//inline for (std.meta.tags(T)) |tag| {
|
||||
inline for (std.meta.fields(T)) |field| {
|
||||
const tag = @field(std.meta.Tag(T), field.name);
|
||||
const tag_name = field.name;
|
||||
if (@as(std.meta.Tag(T), params) == tag) {
|
||||
const val = @field(params, tag_name);
|
||||
try format(prefix, tag_name, val, writer);
|
||||
}
|
||||
}
|
||||
},
|
||||
.Optional => {
|
||||
if (params) |p| try format(prefix, name, p, writer);
|
||||
},
|
||||
else => @compileError("Unsupported query type"),
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
const TestQuery = struct {
|
||||
int: usize = 3,
|
||||
|
|
Loading…
Reference in a new issue