From 4e81441a0da462e3ac7c7725cd449a3caed7b9be Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Sun, 13 Nov 2022 21:57:32 -0800 Subject: [PATCH] Move query format helpers into common code --- src/main/controllers/communities.zig | 122 +-------------------------- src/main/query.zig | 44 ++++++++++ 2 files changed, 48 insertions(+), 118 deletions(-) diff --git a/src/main/controllers/communities.zig b/src/main/controllers/communities.zig index 7e1995a..3aeec1f 100644 --- a/src/main/controllers/communities.zig +++ b/src/main/controllers/communities.zig @@ -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; -} diff --git a/src/main/query.zig b/src/main/query.zig index 75f9051..d79ada8 100644 --- a/src/main/query.zig +++ b/src/main/query.zig @@ -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,