mirror of
https://github.com/zigzap/zap.git
synced 2025-10-21 07:34:08 +00:00
117 lines
3.1 KiB
Zig
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 Router = @This();
|
|
|
|
/// This is a singleton
|
|
var _instance: *Router = 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) anyerror!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) Router {
|
|
return .{
|
|
.routes = std.StringHashMap(Callback).init(allocator),
|
|
|
|
.not_found = options.not_found,
|
|
};
|
|
}
|
|
|
|
/// Deinit the router
|
|
pub fn deinit(self: *Router) 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: *Router, 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: *Router, 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: *Router) zap.HttpRequestFn {
|
|
_instance = self;
|
|
return zap_on_request;
|
|
}
|
|
|
|
fn zap_on_request(r: zap.Request) !void {
|
|
return serve(_instance, r);
|
|
}
|
|
|
|
fn serve(self: *Router, r: zap.Request) !void {
|
|
const path = r.path orelse "/";
|
|
|
|
if (self.routes.get(path)) |routeInfo| {
|
|
switch (routeInfo) {
|
|
.bound => |b| try @call(.auto, @as(BoundHandler, @ptrFromInt(b.handler)), .{ @as(*anyopaque, @ptrFromInt(b.instance)), r }),
|
|
.unbound => |h| try h(r),
|
|
}
|
|
} else if (self.not_found) |handler| {
|
|
// not found handler
|
|
try handler(r);
|
|
} else {
|
|
// default 404 output
|
|
r.setStatus(.not_found);
|
|
try r.sendBody("404 Not Found");
|
|
}
|
|
}
|