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 std = @import("std");
|
||||||
const api = @import("api");
|
const api = @import("api");
|
||||||
const util = @import("util");
|
const util = @import("util");
|
||||||
|
const query_utils = @import("../query.zig");
|
||||||
|
|
||||||
const QueryArgs = api.CommunityQueryArgs;
|
const QueryArgs = api.CommunityQueryArgs;
|
||||||
const Uuid = util.Uuid;
|
const Uuid = util.Uuid;
|
||||||
|
@ -25,43 +26,6 @@ pub const query = struct {
|
||||||
pub const method = .GET;
|
pub const method = .GET;
|
||||||
pub const path = "/communities";
|
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 const Query = QueryArgs;
|
||||||
|
|
||||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||||
|
@ -71,12 +35,9 @@ pub const query = struct {
|
||||||
const link_writer = link.writer();
|
const link_writer = link.writer();
|
||||||
defer link.deinit();
|
defer link.deinit();
|
||||||
|
|
||||||
const next_page = queryArgsToControllerQuery(results.next_page);
|
try writeLink(link_writer, srv.community, path, results.next_page, "next");
|
||||||
const prev_page = queryArgsToControllerQuery(results.prev_page);
|
|
||||||
|
|
||||||
try writeLink(link_writer, srv.community, path, next_page, "next");
|
|
||||||
try link_writer.writeByte(',');
|
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);
|
try res.headers.put("Link", link.items);
|
||||||
|
|
||||||
|
@ -98,7 +59,7 @@ fn writeLink(
|
||||||
.{ @tagName(community.scheme), community.host, path },
|
.{ @tagName(community.scheme), community.host, path },
|
||||||
);
|
);
|
||||||
|
|
||||||
try std.fmt.format(writer, "{}", .{params});
|
try query_utils.formatQuery(params, writer);
|
||||||
|
|
||||||
try std.fmt.format(
|
try std.fmt.format(
|
||||||
writer,
|
writer,
|
||||||
|
@ -106,78 +67,3 @@ fn writeLink(
|
||||||
.{rel},
|
.{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;
|
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 {
|
test {
|
||||||
const TestQuery = struct {
|
const TestQuery = struct {
|
||||||
int: usize = 3,
|
int: usize = 3,
|
||||||
|
|
Loading…
Reference in a new issue