aboutsummaryrefslogtreecommitdiff
path: root/src/routes/handler-info.zig
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2025-11-14 21:55:59 +0100
committerNathan Reiner <nathan@nathanreiner.xyz>2025-11-14 21:55:59 +0100
commit3f18f02d07802d1fc705a500e5978a9b3cb2e751 (patch)
tree283970a2f5a693706456b853c550eeaa669b5d72 /src/routes/handler-info.zig
parent351ad457f0ff95e20301a146b8c88a8f0f659aa1 (diff)
implement login
Diffstat (limited to 'src/routes/handler-info.zig')
-rw-r--r--src/routes/handler-info.zig113
1 files changed, 113 insertions, 0 deletions
diff --git a/src/routes/handler-info.zig b/src/routes/handler-info.zig
new file mode 100644
index 0000000..bac7612
--- /dev/null
+++ b/src/routes/handler-info.zig
@@ -0,0 +1,113 @@
+const std = @import("std");
+const Context = @import("context.zig");
+const Storage = @import("../storage/root.zig");
+
+const log = std.log.scoped(.handler_info);
+
+handler: *const fn (*Context) anyerror![]const u8,
+needs_auth: bool,
+method: std.http.Method,
+
+pub fn handle(
+ self: *const @This(),
+ request: *std.http.Server.Request,
+ storage: *Storage,
+ allocator: std.mem.Allocator,
+) !void {
+ if (request.head.method != self.method) {
+ return request.respond(
+ "{ \"error\": \"Bad Request\" }",
+ .{ .status = .bad_request }
+ );
+ }
+
+ var arena = std.heap.ArenaAllocator.init(allocator);
+ defer arena.deinit();
+ var context: Context = .{
+ .request = request,
+ .storage = storage,
+ .allocator = arena.allocator(),
+ };
+
+ const response = self.handler(&context) catch |err| {
+ const response, const status_code: std.http.Status = switch (err) {
+ error.BadRequest => .{ "{ \"error\": \"Bad Request\" }", .bad_request },
+ error.Unauthorized => .{ "{ \"error\": \"Unauthorized\" }", .unauthorized },
+ error.Forbidden => .{ "{ \"error\": \"Forbidden\" }", .forbidden },
+ error.NotFound => .{ "{ \"error\": \"Not Found\" }", .not_found },
+ else => blk: {
+ log.err("handler for '{s}' returned {}", .{request.head.target, err});
+ break :blk .{ "{ \"error\": \"Internal Server Error\" }", .internal_server_error };
+ },
+ };
+
+ return request.respond(response, .{ .status = status_code });
+ };
+
+ try request.respond(response, .{
+ .extra_headers = &.{
+ .{
+ .name = "Content-Type",
+ .value = context.response.headers.content_type
+ },
+ },
+ });
+}
+
+pub fn from_type(T: type) @This() {
+ const info = @typeInfo(@TypeOf(T.handler));
+ const return_type = info.@"fn".return_type orelse void;
+ const payload_type = @typeInfo(return_type).error_union.payload;
+
+ const Handler = type: {
+ break :type struct {
+ pub fn call(ctx: *Context) anyerror![]const u8 {
+ const args = args: {
+ const tuple = std.meta.fields(std.meta.ArgsTuple(@TypeOf(T.handler)));
+
+ if (tuple.len == 1) {
+ break :args .{ ctx };
+ } else if (tuple.len == 2) {
+ const Body = tuple[1].@"type";
+
+ var writer = std.Io.Writer.Allocating.init(ctx.allocator);
+ const interface = &writer.writer;
+
+ var buffer: [1024]u8 = undefined;
+ const reader = try ctx.request.readerExpectContinue(&buffer);
+
+ try reader.streamExact64(interface, ctx.request.head.content_length orelse 0);
+
+ const body = std.json.parseFromSliceLeaky(
+ Body,
+ ctx.allocator,
+ writer.written(),
+ .{}
+ ) catch return error.BadRequest;
+ break :args .{ ctx, body };
+ } else {
+ @compileError("invalid amount of arguments for request function");
+ }
+ };
+
+ if (payload_type == []const u8) {
+ return @call(.auto, T.handler, args);
+ } else if (payload_type == void) {
+ try @call(.auto, T.handler, args);
+ return "";
+ } else {
+ var writer = std.Io.Writer.Allocating.init(ctx.allocator);
+ var stringify = std.json.Stringify { .writer = &writer.writer };
+ try stringify.write(try @call(.auto, T.handler, args));
+ return writer.written();
+ }
+ }
+ };
+ };
+
+ return @This() {
+ .handler = Handler.call,
+ .needs_auth = T.needs_auth,
+ .method = T.method,
+ };
+}