Compare commits
5 commits
f47c1ee751
...
d4703a2127
Author | SHA1 | Date | |
---|---|---|---|
d4703a2127 | |||
6dc8447343 | |||
57f2bd821e | |||
e2281f7c14 | |||
471ca527bb |
5 changed files with 128 additions and 22 deletions
|
@ -634,7 +634,7 @@ const BaseContentType = enum {
|
||||||
other,
|
other,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn parseBodyFromRequest(
|
pub fn parseBodyFromReader(
|
||||||
comptime T: type,
|
comptime T: type,
|
||||||
comptime options: ParseBodyOptions,
|
comptime options: ParseBodyOptions,
|
||||||
content_type: ?[]const u8,
|
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;
|
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);
|
defer util.deepFree(ctx.allocator, body);
|
||||||
|
|
||||||
return next.handle(
|
return next.handle(
|
||||||
|
@ -719,11 +719,11 @@ pub fn parseBody(comptime Body: type) ParseBody(Body) {
|
||||||
return .{};
|
return .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parseBodyFromRequest" {
|
test "parseBodyFromReader" {
|
||||||
const testCase = struct {
|
const testCase = struct {
|
||||||
fn case(content_type: []const u8, body: []const u8, expected: anytype) !void {
|
fn case(content_type: []const u8, body: []const u8, expected: anytype) !void {
|
||||||
var stream = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
|
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);
|
defer util.deepFree(std.testing.allocator, result);
|
||||||
|
|
||||||
try util.testing.expectDeepEqual(expected, result);
|
try util.testing.expectDeepEqual(expected, result);
|
||||||
|
|
|
@ -80,6 +80,11 @@ pub fn EndpointRequest(comptime Endpoint: type) type {
|
||||||
false,
|
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,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
method: http.Method,
|
method: http.Method,
|
||||||
|
@ -97,9 +102,9 @@ pub fn EndpointRequest(comptime Endpoint: type) type {
|
||||||
//else
|
//else
|
||||||
mdw.ParsePathArgs(Endpoint.path, Args){};
|
mdw.ParsePathArgs(Endpoint.path, Args){};
|
||||||
|
|
||||||
const body_middleware = //if (Body == void)
|
const body_middleware = if (union_tag_from_query_param) |param|
|
||||||
//mdw.injectContext(.{ .body = {} })
|
ParseBodyWithQueryType(Body, param, body_options){}
|
||||||
//else
|
else
|
||||||
mdw.ParseBody(Body, body_options){};
|
mdw.ParseBody(Body, body_options){};
|
||||||
|
|
||||||
const query_middleware = //if (Query == void)
|
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 {
|
fn CallApiEndpoint(comptime Endpoint: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, _: void) !void {
|
pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, _: void) !void {
|
||||||
|
|
|
@ -279,27 +279,39 @@ const drive = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
const Action = enum {
|
const Action = enum {
|
||||||
mkcol,
|
mkdir,
|
||||||
|
delete,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Body = struct {
|
pub const body_tag_from_query_param = "action";
|
||||||
action: Action,
|
pub const Body = union(Action) {
|
||||||
data: union(Action) {
|
mkdir: struct {
|
||||||
mkcol: struct {
|
name: []const u8,
|
||||||
name: []const u8,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
delete: struct {},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||||
if (req.body.action != req.body.data) return error.BadRequest;
|
switch (req.body) {
|
||||||
switch (req.body.data) {
|
.mkdir => |body| {
|
||||||
.mkcol => |data| {
|
_ = try srv.driveMkdir(req.args.path, body.name);
|
||||||
_ = try srv.driveMkdir(req.args.path, data.name);
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
try servePage(req, res, srv);
|
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);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,12 +29,11 @@
|
||||||
<a class="button popup-close" href="#">
|
<a class="button popup-close" href="#">
|
||||||
<i class="fa-solid fa-xmark"></i>
|
<i class="fa-solid fa-xmark"></i>
|
||||||
</a>
|
</a>
|
||||||
<form class="popup-dialog" method="post" enctype="multipart/form-data">
|
<form class="popup-dialog" action="?action=mkdir" method="post" enctype="multipart/form-data">
|
||||||
<label>
|
<label>
|
||||||
<div>Create Directory</div>
|
<div>Create Directory</div>
|
||||||
<input type="text" name="mkcol.name" /> <!-- TODO: Rename this form param -->
|
<input type="text" name="name" />
|
||||||
</label>
|
</label>
|
||||||
<input type="hidden" name="action" value="mkcol" />
|
|
||||||
<button type="submit">Create</button>
|
<button type="submit">Create</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,6 +48,25 @@
|
||||||
{$dir.name.?}
|
{$dir.name.?}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td />
|
||||||
|
<td />
|
||||||
|
<td />
|
||||||
|
<td class="actions">
|
||||||
|
<div class="popup" id="delete-{$dir.name.?}">
|
||||||
|
<a href="#delete-{$dir.name.?}">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
<form class="popup-dialog" action="
|
||||||
|
{= .mount_path}/{.base_drive_path}
|
||||||
|
{= #for @slice(.breadcrumbs, 0, .breadcrumbs.len) |$c|}/{$c}{/for =}/{$dir.name.? =}
|
||||||
|
?action=delete" method="post"
|
||||||
|
>
|
||||||
|
<div>Are you sure you want to delete this directory?</div>
|
||||||
|
<button type="submit">Yes, Delete</button>
|
||||||
|
<a href="#">No, Cancel</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
{#case file |$file|}
|
{#case file |$file|}
|
||||||
<td class="icons">
|
<td class="icons">
|
||||||
{#if %user |$u|}
|
{#if %user |$u|}
|
||||||
|
@ -72,6 +90,23 @@
|
||||||
<td class="content-type">{#if $file.meta.content_type |$t|}{$t}{/if}</td>
|
<td class="content-type">{#if $file.meta.content_type |$t|}{$t}{/if}</td>
|
||||||
<td class="size">{$file.meta.size}</td>
|
<td class="size">{$file.meta.size}</td>
|
||||||
<td class="created-at">{$file.meta.created_at}</td>
|
<td class="created-at">{$file.meta.created_at}</td>
|
||||||
|
<td class="actions">
|
||||||
|
<div class="popup" id="delete-{$file.name.?}">
|
||||||
|
<a href="#delete-{$file.name.?}">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
<form class="popup-dialog" action="
|
||||||
|
{= .mount_path}/{.base_drive_path}
|
||||||
|
{= #for @slice(.breadcrumbs, 0, .breadcrumbs.len) |$c|}/{$c}{/for =}/{$file.name.? =}
|
||||||
|
?action=delete" method="post"
|
||||||
|
>
|
||||||
|
<div>Are you sure you want to delete this file?</div>
|
||||||
|
<input type="hidden" name="action" value="delete" />
|
||||||
|
<button type="submit">Yes, Delete</button>
|
||||||
|
<a href="#">No, Cancel</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
{/switch =}
|
{/switch =}
|
||||||
</tr>
|
</tr>
|
||||||
{/for=}
|
{/for=}
|
||||||
|
|
|
@ -266,7 +266,11 @@ pub fn DeserializerContext(comptime Result: type, comptime From: type, comptime
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish(self: *@This(), allocator: std.mem.Allocator) !Result {
|
pub fn finish(self: *@This(), allocator: std.mem.Allocator) !Result {
|
||||||
return (try self.deserialize(allocator, Result, self.data, &.{})) orelse error.MissingField;
|
return (try self.deserialize(allocator, Result, self.data, &.{})) orelse
|
||||||
|
if (std.meta.fields(Result).len == 0)
|
||||||
|
return .{}
|
||||||
|
else
|
||||||
|
return error.MissingField;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getSerializedField(self: *@This(), comptime field_ref: FieldRef) ?From {
|
fn getSerializedField(self: *@This(), comptime field_ref: FieldRef) ?From {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue