diff options
Diffstat (limited to 'src/http/handler.zig')
| -rw-r--r-- | src/http/handler.zig | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/src/http/handler.zig b/src/http/handler.zig new file mode 100644 index 0000000..2220fd0 --- /dev/null +++ b/src/http/handler.zig @@ -0,0 +1,149 @@ +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(); +} |