const std = @import("std"); const Response = @import("Response.zig"); const Route = @import("Route.zig"); const Parameter = @import("Parameter.zig"); pub fn Context(comptime r: anytype) type { return struct { const route = Route.fromAny(r); const Params = Parameter.Struct(route); const Request = struct { base_request: *std.http.Server.Request, params: Params, arena: std.mem.Allocator, gpa: std.mem.Allocator, io: std.Io, /// Parses the body of the request and returns type `T`. /// The arena allocator will be used to do the parsing. pub fn json(self: @This(), T: type) !T { var buffer: [1024]u8 = undefined; var reader = std.json.Reader.init(self.arena, self.base_request.readerExpectNone(&buffer)); return std.json.parseFromTokenSourceLeaky(T, self.arena, &reader, .{}); } }; const badRequest = struct { pub fn badRequest(_: Request) Response { return .with(.bad_request, .string("method does not exist")); } }.badRequest; get: *const fn (Request) Response = badRequest, post: *const fn (Request) Response = badRequest, delete: *const fn (Request) Response = badRequest, head: *const fn (Request) Response = badRequest, put: *const fn (Request) Response = badRequest, connect: *const fn (Request) Response = badRequest, options: *const fn (Request) Response = badRequest, trace: *const fn (Request) Response = badRequest, patch: *const fn (Request) Response = badRequest, pub fn handle( self: *const @This(), gpa: std.mem.Allocator, arena: std.mem.Allocator, io: std.Io, request: *std.http.Server.Request, ) Response { const req: Request = .{ .base_request = request, .arena = arena, .gpa = gpa, .io = io, .params = Parameter.parse(Params, &route, request.head.target), }; return (switch (request.head.method) { .GET => self.get, .POST => self.post, .DELETE => self.delete, .HEAD => self.head, .PUT => self.put, .CONNECT => self.connect, .OPTIONS => self.options, .TRACE => self.trace, .PATCH => self.patch, })(req); } pub fn interface(self: *const @This()) Interface { return .from(self, route); } }; } pub const Interface = struct { route: Route, base: *const anyopaque, handler: *const fn ( *const anyopaque, std.mem.Allocator, std.mem.Allocator, std.Io, *std.http.Server.Request, ) Response, pub fn from(instance: anytype, route: Route) @This() { const PtrT = @TypeOf(instance); const func = struct { pub fn handle( base: *const anyopaque, gpa: std.mem.Allocator, arena: std.mem.Allocator, io: std.Io, request: *std.http.Server.Request, ) Response { return @as(PtrT, @ptrCast(@alignCast(base))).handle(gpa, arena, io, request); } }.handle; return .{ .base = @ptrCast(instance), .handler = func, .route = route, }; } pub fn handle( self: *const @This(), gpa: std.mem.Allocator, arena: std.mem.Allocator, io: std.Io, request: std.http.Server.Request, ) Response { return self.handler(self.base, gpa, arena, io, request); } }; test "interface" { const M = struct { const C = Context(.{ "api", "images", .id, "metadata" }); const context: C = .{ .post = post, .get = get, }; const Query = struct { condition: []const u8, start: usize, length: usize, }; fn post(request: C.Request) Response { const query = request.json(Query) catch return .with(.bad_request, .void); _ = query; _ = request.params.id; return .ok(.string(request.params.id.as([]const u8))); } fn get(request: C.Request) Response { return .ok(Response.Message.file(request.io, "./some/path.txt") catch return .with(.not_found, .void)); } }; _ = M.context.interface(); }