1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 23:24:09 +00:00
zap/src/router.zig
2024-03-23 22:35:49 +01:00

117 lines
3.1 KiB
Zig

const std = @import("std");
const zap = @import("zap.zig");
const Allocator = std.mem.Allocator;
/// Errors returnable by init()
const RouterError = error{
AlreadyExists,
EmptyPath,
};
const Self = @This();
/// This is a singleton
var _instance: *Self = undefined;
/// Options to pass to init()
pub const Options = struct {
/// an optional zap request function for 404 not found case
not_found: ?zap.HttpRequestFn = null,
};
const CallbackTag = enum { bound, unbound };
const BoundHandler = *fn (*const anyopaque, zap.Request) void;
const Callback = union(CallbackTag) {
bound: struct { instance: usize, handler: usize },
unbound: zap.HttpRequestFn,
};
routes: std.StringHashMap(Callback),
not_found: ?zap.HttpRequestFn,
/// Create a new Router
pub fn init(allocator: Allocator, options: Options) Self {
return .{
.routes = std.StringHashMap(Callback).init(allocator),
.not_found = options.not_found,
};
}
/// Deinit the router
pub fn deinit(self: *Self) void {
self.routes.deinit();
}
/// Call this to add a route with an unbound handler: a handler that is not member of a struct.
pub fn handle_func_unbound(self: *Self, path: []const u8, h: zap.HttpRequestFn) !void {
if (path.len == 0) {
return RouterError.EmptyPath;
}
if (self.routes.contains(path)) {
return RouterError.AlreadyExists;
}
try self.routes.put(path, Callback{ .unbound = h });
}
/// Call this to add a route with a handler that is bound to an instance of a struct.
/// Example:
///
/// ```zig
/// const HandlerType = struct {
/// pub fn getA(self: *HandlerType, r: zap.Request) void {
/// _ = self;
/// r.sendBody("hello\n\n") catch return;
/// }
/// }
/// var handler_instance = HandlerType{};
///
/// my_router.handle_func("/getA", &handler_instance, HandlerType.getA);
/// ```
pub fn handle_func(self: *Self, path: []const u8, instance: *anyopaque, handler: anytype) !void {
// TODO: assert type of instance has handler
if (path.len == 0) {
return RouterError.EmptyPath;
}
if (self.routes.contains(path)) {
return RouterError.AlreadyExists;
}
try self.routes.put(path, Callback{ .bound = .{
.instance = @intFromPtr(instance),
.handler = @intFromPtr(handler),
} });
}
/// Get the zap request handler function needed for a listener
pub fn on_request_handler(self: *Self) zap.HttpRequestFn {
_instance = self;
return zap_on_request;
}
fn zap_on_request(r: zap.Request) void {
return serve(_instance, r);
}
fn serve(self: *Self, r: zap.Request) void {
const path = r.path orelse "/";
if (self.routes.get(path)) |routeInfo| {
switch (routeInfo) {
.bound => |b| @call(.auto, @as(BoundHandler, @ptrFromInt(b.handler)), .{ @as(*anyopaque, @ptrFromInt(b.instance)), r }),
.unbound => |h| h(r),
}
} else if (self.not_found) |handler| {
// not found handler
handler(r);
} else {
// default 404 output
r.setStatus(.not_found);
r.sendBody("404 Not Found") catch return;
}
}