Move query format helpers into common code

This commit is contained in:
jaina heartles 2022-11-13 21:57:32 -08:00
parent bfd73b7a1f
commit 4e81441a0d
2 changed files with 48 additions and 118 deletions

View file

@ -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;
}

View file

@ -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,