diff --git a/src/application.zig b/src/application.zig new file mode 100644 index 0000000..795f706 --- /dev/null +++ b/src/application.zig @@ -0,0 +1,392 @@ +const std = @import("std"); +const mem = std.mem; +const net = std.net; +const heap = std.heap; +const http = std.http; + +pub fn Application(comptime Context: type) type { + return struct { + allocator: mem.Allocator, + + /// Listen address + address: net.Address, + /// Main router + // router: Router, + handler: HandlerFn, + + /// Initialization options + pub const Options = struct { + address: net.Address, + allocator: mem.Allocator, + root_handler: HandlerFn, + }; + + const Self = @This(); + + pub fn init(options: Options) Self { + return .{ + .address = options.address, + .allocator = options.allocator, + .handler = options.root_handler, + // .router = Router.init(options.allocator, options.root_handler), + }; + } + + pub fn deinit(_: *Self) void { + // self.router.deinit(); + } + + /// 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); + } + } + + 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 self.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), + }; + 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 fn Router(comptime Context: type) type { + return struct { + allocator: mem.Allocator, + arena: heap.ArenaAllocator, + + root_node: *Node, + // static_routes: std.StringHashMap(*Node), + + pub fn init(allocator: mem.Allocator, root_ctx: Context) Router { + var arena = heap.ArenaAllocator.init(allocator); + var node = Node.init(arena.allocator()) catch @panic("OOM"); + node.data = root_ctx; + // 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, ctx: Context) !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.data = ctx; + + // 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; + } + } + } 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; + } + } + + 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; + } + + /// 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}); + + 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; + } + + pub const Node = struct { + type: Type, + max_depth: usize, // TODO: what is best here + parent: ?*Node = null, + children: std.StringHashMap(*Node), + data: Context, + 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, section); + 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 {} diff --git a/src/main.zig b/src/main.zig index 7e73432..b1267af 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,6 +4,8 @@ const net = std.net; const http = std.http; const heap = std.heap; +pub const Application = @import("application.zig").Application; + pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); @@ -25,399 +27,13 @@ pub fn main() !void { try listener.listen(); } -pub const App = Listener(DummyContext); -pub const DummyContext = struct {}; +const App = Application(DummyContext); +const DummyContext = struct {}; -pub fn handle(event: *App.Event) anyerror!void { +fn handle(event: *App.Event) anyerror!void { try event.res.body.appendSlice("hello there"); } -pub fn handleError(event: *App.Event) anyerror!void { +fn handleError(event: *App.Event) anyerror!void { try event.res.body.appendSlice("ahoy, an error occurred"); } - -pub fn Listener(comptime Context: type) type { - return struct { - allocator: mem.Allocator, - - /// Listen address - address: net.Address, - /// Main router - // router: Router, - handler: HandlerFn, - - /// Initialization options - pub const Options = struct { - address: net.Address, - allocator: mem.Allocator, - root_handler: HandlerFn, - }; - - const Self = @This(); - - pub fn init(options: Options) Self { - return .{ - .address = options.address, - .allocator = options.allocator, - .handler = options.root_handler, - // .router = Router.init(options.allocator, options.root_handler), - }; - } - - pub fn deinit(_: *Self) void { - // self.router.deinit(); - } - - /// 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); - } - } - - 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 self.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), - }; - 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}); - 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(); - } - - 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]; - - 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; - } - - /// 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}); - - 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; - } - - 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, section); - 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 {}