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.
This commit is contained in:
jaina heartles 2022-12-16 02:02:13 -08:00
parent e2281f7c14
commit 57f2bd821e

View file

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