From 57f2bd821ef03c3db53acdcf4702a2aad7d2c519 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Fri, 16 Dec 2022 02:02:13 -0800 Subject: [PATCH] Add body_tag_from_query_param option When you provide a string for this option, and the request body type is a union, the query param provided will be treated as a value of std.meta.Tag(Body). Then the associated value will be parsed from the body during the request. --- src/main/controllers.zig | 61 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/src/main/controllers.zig b/src/main/controllers.zig index ae62126..ad971b5 100644 --- a/src/main/controllers.zig +++ b/src/main/controllers.zig @@ -80,6 +80,11 @@ pub fn EndpointRequest(comptime Endpoint: type) type { false, }; + const union_tag_from_query_param = if (@hasDecl(Endpoint, "body_tag_from_query_param")) blk: { + if (!std.meta.trait.is(.Union)(Body)) @compileError("body_tag_from_query_param only valid if body is a union"); + break :blk @as(?[]const u8, Endpoint.body_tag_from_query_param); + } else null; + allocator: std.mem.Allocator, method: http.Method, @@ -97,9 +102,9 @@ pub fn EndpointRequest(comptime Endpoint: type) type { //else mdw.ParsePathArgs(Endpoint.path, Args){}; - const body_middleware = //if (Body == void) - //mdw.injectContext(.{ .body = {} }) - //else + const body_middleware = if (union_tag_from_query_param) |param| + ParseBodyWithQueryType(Body, param, body_options){} + else mdw.ParseBody(Body, body_options){}; const query_middleware = //if (Query == void) @@ -109,6 +114,56 @@ pub fn EndpointRequest(comptime Endpoint: type) type { }; } +/// Gets a tag from the query param with the given name, then treats the request body +/// as the respective union type +fn ParseBodyWithQueryType(comptime Union: type, comptime query_param_name: []const u8, comptime options: anytype) type { + return struct { + pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void { + const Tag = std.meta.Tag(Union); + const Param = @Type(.{ .Struct = .{ + .fields = &.{.{ + .name = query_param_name, + .field_type = Tag, + .default_value = null, + .is_comptime = false, + .alignment = if (@sizeOf(Tag) == 0) 0 else @alignOf(Tag), + }}, + .decls = &.{}, + .layout = .Auto, + .is_tuple = false, + } }); + const param = try http.urlencode.parse(ctx.allocator, true, Param, ctx.query_string); + + var result: ?Union = null; + const content_type = req.headers.get("Content-Type"); + inline for (comptime std.meta.tags(Tag)) |tag| { + if (@field(param, query_param_name) == tag) { + std.debug.assert(result == null); + const P = std.meta.TagPayload(Union, tag); + + std.log.debug("Deserializing to type {}", .{P}); + + var stream = req.body orelse return error.NoBody; + result = @unionInit(Union, @tagName(tag), try mdw.parseBodyFromReader( + P, + options, + content_type, + stream.reader(), + ctx.allocator, + )); + } + } + + return mdw.injectContextValue("body", result.?).handle( + req, + res, + ctx, + next, + ); + } + }; +} + fn CallApiEndpoint(comptime Endpoint: type) type { return struct { pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, _: void) !void {