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, }; }