made generic, still broken

This commit is contained in:
Jeeves 2024-03-26 16:22:48 -06:00
parent c0511cebe0
commit a9eb927570

View file

@ -10,7 +10,7 @@ pub fn main() !void {
const allocator = gpa.allocator();
const address = try net.Address.parseIp("0.0.0.0", 8080);
var listener = Listener.init(.{
var listener = App.init(.{
.address = address,
.allocator = allocator,
.root_handler = &handleError,
@ -18,390 +18,403 @@ pub fn main() !void {
defer listener.deinit();
try listener.router.putRoute("/", &handle);
try listener.router.putRoute("/error", &handleError);
try listener.router.putRoute("//pee", &handleError);
try listener.router.putRoute("/error/*", &handleError);
// try listener.router.putRoute("/error", &handleError);
// try listener.router.putRoute("//pee", &handleError);
// try listener.router.putRoute("/error/*", &handleError);
try listener.listen();
}
pub fn handle(event: *Listener.Event) anyerror!void {
pub const App = Listener(DummyContext);
pub const DummyContext = struct {};
pub fn handle(event: *App.Event) anyerror!void {
try event.res.body.appendSlice("hello there");
}
pub fn handleError(event: *Listener.Event) anyerror!void {
pub fn handleError(event: *App.Event) anyerror!void {
try event.res.body.appendSlice("ahoy, an error occurred");
}
pub const Listener = struct {
allocator: mem.Allocator,
pub fn Listener(comptime Context: type) type {
return struct {
allocator: mem.Allocator,
/// Listen address
address: net.Address,
/// Main router
router: Router,
/// Initialization options
pub const Options = struct {
/// Listen address
address: net.Address,
allocator: mem.Allocator,
root_handler: HandlerFn,
};
/// Main router
router: Router,
pub fn init(options: Options) Listener {
return .{
.address = options.address,
.allocator = options.allocator,
.router = Router.init(options.allocator, options.root_handler),
/// Initialization options
pub const Options = struct {
address: net.Address,
allocator: mem.Allocator,
root_handler: HandlerFn,
};
}
pub fn deinit(self: *Listener) void {
self.router.deinit();
}
const Self = @This();
/// Listens for new connections forever
pub fn listen(self: *Listener) !void {
var arena = heap.ArenaAllocator.init(self.allocator);
defer arena.deinit();
var tcp = try self.address.listen(.{ .reuse_address = true });
defer tcp.deinit();
std.debug.print("listening at: {any}\n", .{self.address});
while (true) {
const read_buf = try arena.allocator().alloc(u8, 1024 * 32);
const connection = try tcp.accept();
var server = http.Server.init(connection, read_buf);
try self.handle(&server, arena.allocator());
_ = arena.reset(.retain_capacity);
}
}
fn handle(self: *Listener, server: *http.Server, allocator: mem.Allocator) !void {
handler: while (true) {
var req = server.receiveHead() catch |e| if (e == error.HttpConnectionClosing) break :handler else return e;
var event = Event{
.req = .{
.uri = try std.Uri.parseWithoutScheme(req.head.target),
.headers = std.ArrayList(*const http.Header).init(allocator),
},
.res = .{
.status = .ok,
.headers = std.StringHashMap(*http.Header).init(allocator),
.body = std.ArrayList(u8).init(allocator),
},
};
var header_it = req.iterateHeaders();
while (header_it.next()) |header| try event.req.headers.append(&header);
try self.router.handle(&event);
try respondFromEvent(&event, &req, allocator);
}
}
fn respondFromEvent(event: *Event, req: *http.Server.Request, allocator: mem.Allocator) !void {
const res_body = try event.res.body.toOwnedSlice();
const res_headers = try allocator.alloc(http.Header, event.res.headers.count());
var i: usize = 0;
var header_it = event.res.headers.iterator();
while (header_it.next()) |header_ptr| : (i += 1) res_headers[i] = header_ptr.value_ptr.*.*;
try req.respond(res_body, .{
.status = event.res.status,
.extra_headers = res_headers,
});
}
/// Single request-response context
pub const Event = struct {
req: Request,
res: Response,
pub const Request = struct {
uri: std.Uri,
// head: http.Server.Request.Head,
headers: std.ArrayList(*const http.Header),
};
pub const Response = struct {
status: http.Status,
headers: std.StringHashMap(*http.Header),
body: std.ArrayList(u8),
};
};
pub const HandlerFn = *const fn (event: *Event) anyerror!void;
/// HTTP router
/// Some code taken from [unjs/radix3](https://github.com/unjs/radix3)
pub const Router = struct {
allocator: mem.Allocator,
arena: heap.ArenaAllocator,
root_node: *Node,
// static_routes: std.StringHashMap(*Node),
pub fn init(allocator: mem.Allocator, root_handler: HandlerFn) Router {
var arena = heap.ArenaAllocator.init(allocator);
var node = Node.init(arena.allocator()) catch @panic("OOM");
node.handler = root_handler;
// std.debug.print("init router with root_node {any}\n", .{node});
pub fn init(options: Options) Self {
return .{
.allocator = allocator,
.arena = arena,
.root_node = node,
// .static_routes = std.StringHashMap(*Node).init(allocator),
.address = options.address,
.allocator = options.allocator,
.router = Router.init(options.allocator, options.root_handler),
};
}
pub fn deinit(self: *Router) void {
self.root_node.deinit(self.arena.allocator());
self.arena.deinit();
// self.static_routes.deinit();
pub fn deinit(self: *Self) void {
self.router.deinit();
}
pub fn handle(self: *Router, event: *Listener.Event) !void {
const route = try self.getRoute(event.req.uri.path);
try route.handler(event);
/// Listens for new connections forever
pub fn listen(self: *Self) !void {
var arena = heap.ArenaAllocator.init(self.allocator);
defer arena.deinit();
var tcp = try self.address.listen(.{ .reuse_address = true });
defer tcp.deinit();
std.debug.print("listening at: {any}\n", .{self.address});
while (true) {
const read_buf = try arena.allocator().alloc(u8, 1024 * 32);
const connection = try tcp.accept();
var server = http.Server.init(connection, read_buf);
try self.handle(&server, arena.allocator());
_ = arena.reset(.retain_capacity);
}
}
/// Insert a route if the path is not already present, otherwise overwrite preexisting HandlerFn.
pub fn putRoute(self: *Router, path: []const u8, handler: HandlerFn) !void {
std.debug.print("\npath {s}\n", .{path});
// var is_static_route = true;
var sections = mem.splitScalar(u8, path, '/');
if (sections.peek()) |sec| if (sec.len == 0) {
_ = sections.next();
fn handle(self: *Self, server: *http.Server, allocator: mem.Allocator) !void {
handler: while (true) {
var req = server.receiveHead() catch |e| if (e == error.HttpConnectionClosing) break :handler else return e;
var ctx = Context{};
var event = Event{
.ctx = &ctx,
.req = .{
.uri = try std.Uri.parseWithoutScheme(req.head.target),
.headers = std.ArrayList(*const http.Header).init(allocator),
},
.res = .{
.status = .ok,
.headers = std.StringHashMap(*http.Header).init(allocator),
.body = std.ArrayList(u8).init(allocator),
},
};
var header_it = req.iterateHeaders();
while (header_it.next()) |header| try event.req.headers.append(&header);
const route = try self.router.getRoute(event.req.uri.path);
try route.handler(&event);
try respondFromEvent(&event, &req, allocator);
}
}
fn respondFromEvent(event: *Event, req: *http.Server.Request, allocator: mem.Allocator) !void {
const res_body = try event.res.body.toOwnedSlice();
const res_headers = try allocator.alloc(http.Header, event.res.headers.count());
var i: usize = 0;
var header_it = event.res.headers.iterator();
while (header_it.next()) |header_ptr| : (i += 1) res_headers[i] = header_ptr.value_ptr.*.*;
try req.respond(res_body, .{
.status = event.res.status,
.extra_headers = res_headers,
});
}
/// Single request-response context
pub const Event = struct {
ctx: *Context,
req: Request,
res: Response,
pub const Request = struct {
uri: std.Uri,
// head: http.Server.Request.Head,
headers: std.ArrayList(*const http.Header),
};
var node = self.root_node;
var unnamed_placeholder_ctr: usize = 0;
var matched_nodes = std.ArrayList(*Node).init(self.allocator);
defer matched_nodes.deinit();
try matched_nodes.append(node);
while (sections.next()) |section| {
std.debug.print("adding section {s}\n", .{section});
if (node.children.get(section)) |child| {
std.debug.print("into child\n", .{});
node = child;
} else {
var child_node = try Node.init(self.allocator);
child_node.type = if (mem.startsWith(u8, section, "**"))
.wildcard
else if ((section.len > 0 and section[0] == ':') or mem.eql(u8, section, "*"))
.placeholder
else
.normal;
child_node.parent = node;
// child_node.handler = handler;
try node.children.put(section, child_node);
pub const Response = struct {
status: http.Status,
headers: std.StringHashMap(*http.Header),
body: std.ArrayList(u8),
};
};
switch (child_node.type) {
.normal => {},
.wildcard => {
std.debug.print("is wildcard\n", .{});
node.wildcard_child_node = child_node;
child_node.param_name = if (section.len > 3) section[3..] else "_";
// is_static_route = false;
},
.placeholder => {
std.debug.print("is placeholder\n", .{});
child_node.param_name = if (mem.eql(u8, section, "*")) blk: {
std.debug.print("is unnamed placeholder #{d}\n", .{unnamed_placeholder_ctr});
const s = try std.fmt.allocPrint(self.arena.allocator(), "_{d}", .{unnamed_placeholder_ctr}); // TODO: this will leak
unnamed_placeholder_ctr += 1;
break :blk s;
} else try self.arena.allocator().dupe(u8, section[1..]);
try node.placeholder_children.append(child_node);
// is_static_route = false;
},
pub const HandlerFn = *const fn (event: *Event) anyerror!void;
/// HTTP router
/// Some code taken from [unjs/radix3](https://github.com/unjs/radix3)
pub const Router = struct {
allocator: mem.Allocator,
arena: heap.ArenaAllocator,
root_node: *Node,
// static_routes: std.StringHashMap(*Node),
pub fn init(allocator: mem.Allocator, root_handler: HandlerFn) Router {
var arena = heap.ArenaAllocator.init(allocator);
var node = Node.init(arena.allocator()) catch @panic("OOM");
node.handler = root_handler;
// std.debug.print("init router with root_node {any}\n", .{node});
return .{
.allocator = allocator,
.arena = arena,
.root_node = node,
// .static_routes = std.StringHashMap(*Node).init(allocator),
};
}
pub fn deinit(self: *Router) void {
self.root_node.deinit(self.arena.allocator(), null);
self.arena.deinit();
// self.static_routes.deinit();
}
// pub fn handle(self: *Router, event: *Listener.Event) !void {
// const route = try self.getRoute(event.req.uri.path);
// try route.handler(event);
// }
/// Insert a route if the path is not already present, otherwise overwrite preexisting HandlerFn.
pub fn putRoute(self: *Router, path: []const u8, handler: HandlerFn) !void {
std.debug.print("\npath {s}\n", .{path});
// var is_static_route = true;
var sections = mem.splitScalar(u8, path, '/');
if (sections.peek()) |sec| if (sec.len == 0) {
_ = sections.next();
};
var node = self.root_node;
var unnamed_placeholder_ctr: usize = 0;
var matched_nodes = std.ArrayList(*Node).init(self.allocator);
defer matched_nodes.deinit();
try matched_nodes.append(node);
while (sections.next()) |section| {
std.debug.print("adding section {s}\n", .{section});
if (node.children.get(section)) |child| {
std.debug.print("into child\n", .{});
node = child;
} else {
var child_node = try Node.init(self.allocator);
child_node.type = if (mem.startsWith(u8, section, "**"))
.wildcard
else if ((section.len > 0 and section[0] == ':') or mem.eql(u8, section, "*"))
.placeholder
else
.normal;
child_node.parent = node;
// child_node.handler = handler;
try node.children.put(section, child_node);
switch (child_node.type) {
.normal => {},
.wildcard => {
std.debug.print("is wildcard\n", .{});
node.wildcard_child_node = child_node;
child_node.param_name = if (section.len > 3) section[3..] else "_";
// is_static_route = false;
},
.placeholder => {
std.debug.print("is placeholder\n", .{});
child_node.param_name = if (mem.eql(u8, section, "*")) blk: {
std.debug.print("is unnamed placeholder #{d}\n", .{unnamed_placeholder_ctr});
const s = try std.fmt.allocPrint(self.arena.allocator(), "_{d}", .{unnamed_placeholder_ctr}); // TODO: this will leak
unnamed_placeholder_ctr += 1;
break :blk s;
} else try self.arena.allocator().dupe(u8, section[1..]);
try node.placeholder_children.append(child_node);
// is_static_route = false;
},
}
// std.debug.print("added child node: {any}\n", .{child_node});
try matched_nodes.append(child_node);
node = child_node;
}
}
node.handler = handler;
// if (is_static_route) std.debug.print("was static route\n", .{});
// if (is_static_route) try self.static_routes.put(path, node);
}
/// Get the HandlerFn associated with path, otherwise get root_handler.
pub fn getRoute(self: *Router, path: []const u8) !*Node {
// if (self.static_routes.get(path)) |rt| return rt;
std.debug.print("\nget path {s}\n", .{path});
var params = std.StringHashMap([]const u8).init(self.allocator);
defer params.deinit();
var params_found = false;
var wildcard_node: ?*Node = null;
var node: *Node = self.root_node;
var wildcard_param: ?[]const u8 = null;
var remaining = mem.count(u8, path, "/") + 1;
var sections = mem.splitScalar(u8, path, '/');
if (sections.peek()) |sec| if (sec.len == 0) {
_ = sections.next();
};
while (sections.next()) |section| : (remaining -= 1) {
std.debug.print("finding section: {s}\n", .{section});
if (node.wildcard_child_node) |wildcard_child_node| {
std.debug.print("section has wildcard\n", .{});
wildcard_node = wildcard_child_node;
wildcard_param = sections.rest();
}
// std.debug.print("added child node: {any}\n", .{child_node});
try matched_nodes.append(child_node);
node = child_node;
}
}
node.handler = handler;
// if (is_static_route) std.debug.print("was static route\n", .{});
// if (is_static_route) try self.static_routes.put(path, node);
}
/// Get the HandlerFn associated with path, otherwise get root_handler.
pub fn getRoute(self: *Router, path: []const u8) !*Node {
// if (self.static_routes.get(path)) |rt| return rt;
std.debug.print("\nget path {s}\n", .{path});
var params = std.StringHashMap([]const u8).init(self.allocator);
defer params.deinit();
var params_found = false;
var wildcard_node: ?*Node = null;
var node: *Node = self.root_node;
var wildcard_param: ?[]const u8 = null;
var remaining = mem.count(u8, path, "/") + 1;
var sections = mem.splitScalar(u8, path, '/');
if (sections.peek()) |sec| if (sec.len == 0) {
_ = sections.next();
};
while (sections.next()) |section| : (remaining -= 1) {
std.debug.print("finding section: {s}\n", .{section});
if (node.wildcard_child_node) |wildcard_child_node| {
std.debug.print("section has wildcard\n", .{});
wildcard_node = wildcard_child_node;
wildcard_param = sections.rest();
}
const next_node = node.children.get(section);
if (next_node) |child| {
node = child;
std.debug.print("found section {s} child_node {any}\n", .{ section, child.type });
} else {
var child_node: ?*Node = null;
if (node.placeholder_children.items.len > 1) {
// TODO
for (node.placeholder_children.items) |child| place: {
if (child.max_depth == remaining) {
child_node = child;
break :place;
const next_node = node.children.get(section);
if (next_node) |child| {
node = child;
std.debug.print("found section {s} child_node {any}\n", .{ section, child.type });
} else {
var child_node: ?*Node = null;
if (node.placeholder_children.items.len > 1) {
// TODO
for (node.placeholder_children.items) |child| place: {
if (child.max_depth == remaining) {
child_node = child;
break :place;
}
}
}
} else if (node.placeholder_children.items.len == 1)
child_node = node.placeholder_children.items[0];
} else if (node.placeholder_children.items.len == 1)
child_node = node.placeholder_children.items[0];
if (child_node) |n| {
if (n.param_name) |name| try params.put(name, section);
params_found = true;
node = n;
} else break;
if (child_node) |n| {
if (n.param_name) |name| try params.put(name, section);
params_found = true;
node = n;
} else break;
}
}
std.debug.print("ended up with node {any}\nhas wildcard_node: {any}\nis root_node: {any}\n", .{ node.type, wildcard_node != null, node == self.root_node });
if (wildcard_node) |wildcard| {
node = wildcard;
try params.put("_", wildcard_param.?);
params_found = true;
}
return node;
}
std.debug.print("ended up with node {any}\nhas wildcard_node: {any}\nis root_node: {any}\n", .{ node.type, wildcard_node != null, node == self.root_node });
/// If there is a route with a matching path, it is deleted from the router, and this function return true. Otherwise it returns false.
pub fn removeRoute(self: *Router, path: []const u8) bool {
// _ = self.static_routes.remove(path);
std.debug.print("\nremoving path {s}\n", .{path});
if (wildcard_node) |wildcard| {
node = wildcard;
try params.put("_", wildcard_param.?);
params_found = true;
var opt_node: ?*Node = self.root_node;
var sections = mem.splitScalar(u8, path, '/');
if (sections.peek()) |sec| if (sec.len == 0) {
_ = sections.next();
};
while (sections.next()) |section| {
// TODO: reorder to be safe
std.debug.print("section: {s}\n", .{section});
opt_node = opt_node.?.children.get(section);
if (opt_node == null) return false;
}
// TODO: should this node.parent be an assert instead?
if (opt_node) |node| {
std.debug.print("found node\n", .{});
// if (node.children.count() == 0) {
// std.debug.print("node has no children\n", .{});
if (node.parent) |_| {
std.debug.print("removing self from parent\n", .{});
var rest_sections = mem.splitScalar(u8, path, '/');
var last_section: []const u8 = undefined;
while (rest_sections.peek()) |_| last_section = rest_sections.next().?;
// parent.wildcard_child_node = null;
// parent.placeholder_children.clearAndFree();
// _ = parent.children.remove(last_section); // TODO assert this is true
node.deinit(self.allocator, last_section);
} else node.deinit(self.allocator, null);
// } // else node.deinit(self.allocator);
return true;
}
return false;
}
return node;
}
pub const Node = struct {
type: Type,
max_depth: usize, // TODO: what is best here
parent: ?*Node = null,
children: std.StringHashMap(*Node),
handler: HandlerFn,
param_name: ?[]const u8 = null,
wildcard_child_node: ?*Node = null,
placeholder_children: std.ArrayList(*Node),
/// If there is a route with a matching path, it is deleted from the router, and this function return true. Otherwise it returns false.
pub fn removeRoute(self: *Router, path: []const u8) bool {
// _ = self.static_routes.remove(path);
std.debug.print("\nremoving path {s}\n", .{path});
pub const Type = enum { normal, wildcard, placeholder };
var opt_node: ?*Node = self.root_node;
var sections = mem.splitScalar(u8, path, '/');
if (sections.peek()) |sec| if (sec.len == 0) {
_ = sections.next();
/// Expects handler to be set later. Will cause problems otherwise!
pub fn init(allocator: mem.Allocator) !*Node {
var self = try allocator.create(Node);
self.type = .normal;
self.max_depth = 256;
self.children = std.StringHashMap(*Node).init(allocator);
self.placeholder_children = std.ArrayList(*Node).init(allocator);
self.parent = null;
self.param_name = null;
self.wildcard_child_node = null;
return self;
}
pub fn deinit(self: *Node, allocator: mem.Allocator, section: ?[]const u8) void {
std.debug.print("deiniting node with {d} children.. and {d} placeholder_children\n", .{ self.children.count(), self.placeholder_children.items.len });
if (section) |sec| if (self.parent) |parent| {
_ = parent.children.remove(sec);
};
for (self.placeholder_children.items) |child| std.debug.print("child {any}\n", .{child}); // child.deinit(allocator);
self.placeholder_children.deinit();
var child_it = self.children.valueIterator();
while (child_it.next()) |child| child.*.deinit(allocator, section);
self.children.deinit();
allocator.destroy(self);
}
};
while (sections.next()) |section| {
// TODO: reorder to be safe
std.debug.print("section: {s}\n", .{section});
opt_node = opt_node.?.children.get(section);
if (opt_node == null) return false;
}
// TODO: should this node.parent be an assert instead?
if (opt_node) |node| {
std.debug.print("found node\n", .{});
// if (node.children.count() == 0) {
// std.debug.print("node has no children\n", .{});
if (node.parent) |_| {
std.debug.print("removing self from parent\n", .{});
var rest_sections = mem.splitScalar(u8, path, '/');
var last_section: []const u8 = undefined;
while (rest_sections.peek()) |_| last_section = rest_sections.next().?;
// parent.wildcard_child_node = null;
// parent.placeholder_children.clearAndFree();
// _ = parent.children.remove(last_section); // TODO assert this is true
node.deinit(self.allocator, last_section);
} else node.deinit(self.allocator, null);
// } // else node.deinit(self.allocator);
return true;
}
return false;
}
pub const Node = struct {
type: Type,
max_depth: usize, // TODO: what is best here
parent: ?*Node = null,
children: std.StringHashMap(*Node),
handler: HandlerFn,
param_name: ?[]const u8 = null,
wildcard_child_node: ?*Node = null,
placeholder_children: std.ArrayList(*Node),
pub const Type = enum { normal, wildcard, placeholder };
/// Expects handler to be set later. Will cause problems otherwise!
pub fn init(allocator: mem.Allocator) !*Node {
var self = try allocator.create(Node);
self.type = .normal;
self.max_depth = 256;
self.children = std.StringHashMap(*Node).init(allocator);
self.placeholder_children = std.ArrayList(*Node).init(allocator);
self.parent = null;
self.param_name = null;
self.wildcard_child_node = null;
return self;
}
pub fn deinit(self: *Node, allocator: mem.Allocator, section: ?[]const u8) void {
std.debug.print("deiniting node with {d} children.. and {d} placeholder_children\n", .{ self.children.count(), self.placeholder_children.items.len });
if (section) |sec| if (self.parent) |parent| parent.children.remove(sec);
for (self.placeholder_children.items) |child| std.debug.print("child {any}\n", .{child}); // child.deinit(allocator);
self.placeholder_children.deinit();
var child_it = self.children.valueIterator();
while (child_it.next()) |child| child.*.deinit(allocator);
self.children.deinit();
allocator.destroy(self);
}
};
};
};
test "Router" {
var router = Listener.Router.init(std.testing.allocator, &dummyHandler);
defer router.deinit();
try router.putRoute("/foo", &hummyDandler);
try router.putRoute("/foo/bar", &hummyDandler);
try router.putRoute("/foo/foobar", &tummyCandler);
try router.putRoute("/bar", &hummyDandler);
try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/")).handler);
try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/foo")).handler);
try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/foo/bar")).handler);
try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/foobar")).handler);
try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/bar")).handler);
try std.testing.expect(router.removeRoute("/foo"));
try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/foo/bar")).handler);
try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/foo")).handler);
try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/")).handler);
try router.putRoute("/foo", &hummyDandler);
try router.putRoute("/foo/**", &tummyCandler);
try router.putRoute("/bar/*", &tummyCandler);
try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/foo")).handler);
try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/bar")).handler);
try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/bar/foo")).handler);
try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/foof")).handler);
try std.testing.expect(router.removeRoute("/bar/*"));
}
fn dummyHandler(_: *Listener.Event) anyerror!void {}
fn hummyDandler(_: *Listener.Event) anyerror!void {}
fn tummyCandler(_: *Listener.Event) anyerror!void {}
// test "Router" {
// var router = Listener.Router.init(std.testing.allocator, &dummyHandler);
// defer router.deinit();
// try router.putRoute("/foo", &hummyDandler);
// try router.putRoute("/foo/bar", &hummyDandler);
// try router.putRoute("/foo/foobar", &tummyCandler);
// try router.putRoute("/bar", &hummyDandler);
// try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/")).handler);
// try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/foo")).handler);
// try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/foo/bar")).handler);
// try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/foobar")).handler);
// try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/bar")).handler);
// try std.testing.expect(router.removeRoute("/foo"));
// try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/foo/bar")).handler);
// try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/foo")).handler);
// try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/")).handler);
// try router.putRoute("/foo", &hummyDandler);
// try router.putRoute("/foo/**", &tummyCandler);
// try router.putRoute("/bar/*", &tummyCandler);
// try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/foo")).handler);
// try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/bar")).handler);
// try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/bar/foo")).handler);
// try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/foof")).handler);
// try std.testing.expect(router.removeRoute("/bar/*"));
// }
// fn dummyHandler(_: *Listener.Event) anyerror!void {}
// fn hummyDandler(_: *Listener.Event) anyerror!void {}
// fn tummyCandler(_: *Listener.Event) anyerror!void {}