diff --git a/src/http/middleware.zig b/src/http/middleware.zig index fb3095a..190a60d 100644 --- a/src/http/middleware.zig +++ b/src/http/middleware.zig @@ -634,7 +634,7 @@ const BaseContentType = enum { other, }; -fn parseBodyFromRequest( +pub fn parseBodyFromReader( comptime T: type, comptime options: ParseBodyOptions, content_type: ?[]const u8, @@ -703,7 +703,7 @@ pub fn ParseBody(comptime Body: type, comptime options: ParseBodyOptions) type { } var stream = req.body orelse return error.NoBody; - const body = try parseBodyFromRequest(Body, options, content_type, stream.reader(), ctx.allocator); + const body = try parseBodyFromReader(Body, options, content_type, stream.reader(), ctx.allocator); defer util.deepFree(ctx.allocator, body); return next.handle( @@ -719,11 +719,11 @@ pub fn parseBody(comptime Body: type) ParseBody(Body) { return .{}; } -test "parseBodyFromRequest" { +test "parseBodyFromReader" { const testCase = struct { fn case(content_type: []const u8, body: []const u8, expected: anytype) !void { var stream = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) }; - const result = try parseBodyFromRequest(@TypeOf(expected), .{}, content_type, stream.reader(), std.testing.allocator); + const result = try parseBodyFromReader(@TypeOf(expected), .{}, content_type, stream.reader(), std.testing.allocator); defer util.deepFree(std.testing.allocator, result); try util.testing.expectDeepEqual(expected, result); 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 { diff --git a/src/main/controllers/web.zig b/src/main/controllers/web.zig index 835e64e..5ec6d55 100644 --- a/src/main/controllers/web.zig +++ b/src/main/controllers/web.zig @@ -279,27 +279,39 @@ const drive = struct { }; const Action = enum { - mkcol, + mkdir, + delete, }; - pub const Body = struct { - action: Action, - data: union(Action) { - mkcol: struct { - name: []const u8, - }, + pub const body_tag_from_query_param = "action"; + pub const Body = union(Action) { + mkdir: struct { + name: []const u8, }, + delete: struct {}, }; pub fn handler(req: anytype, res: anytype, srv: anytype) !void { - if (req.body.action != req.body.data) return error.BadRequest; - switch (req.body.data) { - .mkcol => |data| { - _ = try srv.driveMkdir(req.args.path, data.name); + switch (req.body) { + .mkdir => |body| { + _ = try srv.driveMkdir(req.args.path, body.name); // TODO try servePage(req, res, srv); }, + .delete => { + const trimmed_path = std.mem.trim(u8, req.args.path, "/"); + _ = try srv.driveDelete(trimmed_path); + + const dir = trimmed_path[0 .. std.mem.lastIndexOfScalar(u8, trimmed_path, '/') orelse trimmed_path.len]; + const url = try std.fmt.allocPrint(srv.allocator, "{s}/drive/{s}", .{ + req.mount_path, + dir, + }); + defer srv.allocator.free(url); + try res.headers.put("Location", url); + return res.status(.see_other); + }, } } }; diff --git a/src/main/controllers/web/drive/directory.tmpl.html b/src/main/controllers/web/drive/directory.tmpl.html index 5135b6a..39f4b2d 100644 --- a/src/main/controllers/web/drive/directory.tmpl.html +++ b/src/main/controllers/web/drive/directory.tmpl.html @@ -29,12 +29,11 @@ -
@@ -49,6 +48,25 @@ {$dir.name.?} +