Support for 'Data' field in controllers

This commit is contained in:
jaina heartles 2022-11-14 23:49:23 -08:00
parent 85f57df0cb
commit 0b04ad5e00
1 changed files with 52 additions and 5 deletions

View File

@ -84,13 +84,39 @@ fn parseRouteArg(comptime T: type, segment: []const u8) !T {
@compileError("Unsupported Type " ++ @typeName(T));
}
fn parseBody(comptime T: type, reader: anytype, alloc: std.mem.Allocator) !T {
const BaseContentType = enum {
json,
url_encoded,
octet_stream,
other,
};
fn parseBody(comptime T: type, content_type: BaseContentType, reader: anytype, alloc: std.mem.Allocator) !T {
const buf = try reader.readAllAlloc(alloc, 1 << 16);
defer alloc.free(buf);
const body = try json_utils.parse(T, buf, alloc);
defer json_utils.parseFree(body, alloc);
return try util.deepClone(alloc, body);
switch (content_type) {
.octet_stream, .json => {
const body = try json_utils.parse(T, buf, alloc);
defer json_utils.parseFree(body, alloc);
return try util.deepClone(alloc, body);
},
else => return error.UnsupportedMediaType,
}
}
fn matchContentType(hdr: ?[]const u8) ?BaseContentType {
if (hdr) |h| {
if (std.ascii.eqlIgnoreCase(h, "application/x-www-form-urlencoded")) return .url_encoded;
if (std.ascii.eqlIgnoreCase(h, "application/json")) return .json;
if (std.ascii.eqlIgnoreCase(h, "application/octet-stream")) return .octet_stream;
return .other;
}
return null;
}
pub fn Context(comptime Route: type) type {
@ -107,6 +133,8 @@ pub fn Context(comptime Route: type) type {
// leave it as a simple string instead of void
pub const Query = if (@hasDecl(Route, "Query")) Route.Query else void;
pub const Data = if (@hasDecl(Route, "Data")) Route.Data else void;
base_request: *http.Request,
allocator: std.mem.Allocator,
@ -118,6 +146,7 @@ pub fn Context(comptime Route: type) type {
args: Args,
body: Body,
query: Query,
data: Data,
// TODO
body_buf: ?[]const u8 = null,
@ -144,9 +173,11 @@ pub fn Context(comptime Route: type) type {
alloc: std.mem.Allocator,
args: Args,
) !void {
const base_content_type = matchContentType(req.headers.get("Content-Type"));
const body = if (Body != void) blk: {
var stream = req.body orelse return error.NoBody;
break :blk try parseBody(Body, stream.reader(), alloc);
break :blk try parseBody(Body, base_content_type orelse .json, stream.reader(), alloc);
} else {};
defer if (Body != void) util.deepFree(alloc, body);
@ -157,6 +188,21 @@ pub fn Context(comptime Route: type) type {
break :blk try query_utils.parseQuery(Query, q);
};
var should_free_data: bool = false;
const data = if (Data != void) blk: {
if (Query != void or Body != void) @compileError("Cannot specify both Data and Query/Body");
if (base_content_type == .url_encoded) {
const path = std.mem.sliceTo(req.uri, '?');
const q = req.uri[path.len..];
break :blk try query_utils.parseQuery(Query, q);
} else {
should_free_data = true;
var stream = req.body orelse return error.NoBody;
break :blk try parseBody(Body, base_content_type orelse .json, stream.reader(), alloc);
}
};
defer if (should_free_data) util.deepFree(alloc, data);
var api_conn = conn: {
const host = req.headers.get("Host") orelse return error.NoHost;
const auth_header = req.headers.get("Authorization");
@ -184,6 +230,7 @@ pub fn Context(comptime Route: type) type {
.args = args,
.body = body,
.query = query,
.data = data,
};
try Route.handler(self, res, &api_conn);