diff --git a/src/main/controllers.zig b/src/main/controllers.zig index 28b3baa..bbc1338 100644 --- a/src/main/controllers.zig +++ b/src/main/controllers.zig @@ -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);