const std = @import("std"); const Route = @import("Route.zig"); const handler = @import("handler/root.zig"); const Interface = handler.Interface; const Context = handler.Context; literals: std.StaticStringMap(@This()), identifiers: []const @This(), interface: ?Interface, pub inline fn init(comptime interfaces: []const Interface) @This() { return initPrefix(.index, interfaces); } pub inline fn initPrefix( comptime prefix: Route, comptime interfaces: []const Interface, ) @This() { return comptime { @setEvalBranchQuota(interfaces.len * interfaces.len * 1000); const KV = struct { []const u8, @This() }; var literals: []const KV = &.{}; var identifiers: []const @This() = &.{}; var current_interface: ?Interface = null; for (interfaces) |interface| { if (prefix.equals(&interface.route)) { if (current_interface != null) { @compileError("re-assignment of interface"); } current_interface = interface; } else if (prefix.isPrefixOf(&interface.route)) { const inner_prefix: Route = .{ .segments = interface.route.segments[0 .. prefix.segments.len + 1], }; switch (interface.route.segments[prefix.segments.len]) { .literal => |l| { const lit: []const KV = &.{.{ l, .initPrefix(inner_prefix, interfaces) }}; literals = literals ++ lit; }, .identifier => { const ident: []const @This() = &.{.initPrefix(inner_prefix, interfaces)}; identifiers = identifiers ++ ident; }, } } } return .{ .literals = .initComptime(literals), .identifiers = identifiers, .interface = current_interface, }; }; } pub fn getInterface(self: *const @This(), url: []const u8) ?Interface { var current = std.mem.trimStart(u8, url, "/"); const index = std.mem.indexOfScalar(u8, current, '/') orelse current.len; const next = current[index..]; current = current[0..index]; if (current.len == 0) { return self.interface; } if (self.literals.get(current)) |child| { if (child.getInterface(next)) |interface| { return interface; } } for (self.identifiers) |id| { if (id.getInterface(next)) |interface| { return interface; } } return null; } test "init" { const ctx1: Context(.{ "api", "images", .id, "metadata" }) = .{}; const ctx2: Context(.{ "api", "users", "settings", "overview" }) = .{}; const ctx3: Context(.{ "api", "users", .id, "settings", .key }) = .{}; const ctx4: Context(.{ "api", "users" }) = .{}; const routes = init(&.{ ctx1.interface(), ctx2.interface(), ctx3.interface(), ctx4.interface(), }); { const interface = routes.getInterface("/api/images/123456/metadata") orelse return error.TestFailed; try std.testing.expectEqual(@as(*const anyopaque, @ptrCast(&ctx1)), interface.base); } { const interface = routes.getInterface("/api/users/settings/overview") orelse return error.TestFailed; try std.testing.expectEqual(@as(*const anyopaque, @ptrCast(&ctx2)), interface.base); } { const interface = routes.getInterface("/api/users/1234/settings/name") orelse return error.TestFailed; try std.testing.expectEqual(@as(*const anyopaque, @ptrCast(&ctx3)), interface.base); } { const interface = routes.getInterface("/////api//users///") orelse return error.TestFailed; try std.testing.expectEqual(@as(*const anyopaque, @ptrCast(&ctx4)), interface.base); } { try std.testing.expectEqual(null, routes.getInterface("/api/images/1234/")); } }