aboutsummaryrefslogtreecommitdiff
path: root/src/router.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/router.zig')
-rw-r--r--src/router.zig212
1 files changed, 212 insertions, 0 deletions
diff --git a/src/router.zig b/src/router.zig
index 16e947248..38ddcc7a9 100644
--- a/src/router.zig
+++ b/src/router.zig
@@ -14,6 +14,7 @@ const Fs = @import("./fs.zig");
const Options = @import("./options.zig");
const allocators = @import("./allocators.zig");
const URLPath = @import("./http/url_path.zig");
+const PathnameScanner = @import("./query_string_map.zig").PathnameScanner;
const index_route_hash = @truncate(u32, std.hash.Wyhash.hash(0, "index"));
const arbitrary_max_route = 4096;
@@ -598,6 +599,7 @@ pub const RouteMap = struct {
.matched_route_buf = file_path_buf,
};
+ // iterate over the top-level routes
if (ctx.matchDynamicRoute(0, 0)) |_dynamic_route| {
// route name == the filesystem path relative to the pages dir excluding the file extension
var dynamic_route = _dynamic_route;
@@ -738,6 +740,10 @@ pub const Match = struct {
redirect_path: ?string = null,
query_string: string = "",
+ pub fn paramsIterator(this: *const Match) PathnameScanner {
+ return PathnameScanner.init(this.pathname, this.name, this.params);
+ }
+
pub fn nameWithBasename(file_path: string, dir: string) string {
var name = file_path;
if (strings.indexOf(name, dir)) |i| {
@@ -751,3 +757,209 @@ pub const Match = struct {
return std.mem.trimLeft(u8, this.pathname, "/");
}
};
+
+const FileSystem = Fs.FileSystem;
+
+const MockRequestContextType = struct {
+ controlled: bool = false,
+ url: URLPath,
+ match_file_path_buf: [1024]u8 = undefined,
+
+ handle_request_called: bool = false,
+ redirect_called: bool = false,
+ matched_route: ?Match = null,
+ has_called_done: bool = false,
+
+ pub fn handleRequest(this: *MockRequestContextType) !void {
+ this.handle_request_called = true;
+ }
+
+ pub fn handleRedirect(this: *MockRequestContextType, pathname: string) !void {
+ this.redirect_called = true;
+ }
+
+ pub const JavaScriptHandler = struct {
+ pub fn enqueue(ctx: *MockRequestContextType, server: *MockServer, filepath_buf: []u8, params: *Router.Param.List) !void {}
+ };
+};
+
+pub const MockServer = struct {
+ watchloop_handle: ?StoredFileDescriptorType = null,
+ watcher: Watcher = Watcher{},
+
+ pub const Watcher = struct {
+ watchloop_handle: ?StoredFileDescriptorType = null,
+ pub fn start(this: *Watcher) anyerror!void {}
+ };
+};
+
+fn makeTest(cwd_path: string, data: anytype) !void {
+ std.debug.assert(cwd_path.len > 1 and !strings.eql(cwd_path, "/") and !strings.endsWith(cwd_path, "bun"));
+ const bun_tests_dir = try std.fs.cwd().makeOpenPath("bun-test-scratch", .{ .iterate = true });
+ bun_tests_dir.deleteTree(cwd_path) catch {};
+
+ const cwd = try bun_tests_dir.makeOpenPath(cwd_path, .{ .iterate = true });
+ try cwd.setAsCwd();
+
+ const Data = @TypeOf(data);
+ const fields: []const std.builtin.TypeInfo.StructField = comptime std.meta.fields(Data);
+ inline for (fields) |field| {
+ const value = @field(data, field.name);
+
+ if (std.fs.path.dirname(field.name)) |dir| {
+ try cwd.makePath(dir);
+ }
+ var file = try cwd.createFile(field.name, .{ .truncate = true });
+ try file.writeAll(std.mem.span(value));
+ file.close();
+ }
+}
+
+const expect = std.testing.expect;
+const expectEqual = std.testing.expectEqual;
+const expectEqualStrings = std.testing.expectEqualStrings;
+const Logger = @import("./logger.zig");
+
+pub const Test = struct {
+ pub fn make(comptime testName: string, data: anytype) !Router {
+ try makeTest(testName, data);
+ const JSAst = @import("./js_ast.zig");
+ JSAst.Expr.Data.Store.create(default_allocator);
+ JSAst.Stmt.Data.Store.create(default_allocator);
+ var fs = try FileSystem.init1(default_allocator, null);
+ var top_level_dir = fs.top_level_dir;
+
+ var pages_parts = [_]string{ top_level_dir, "pages" };
+ var pages_dir = try Fs.FileSystem.instance.absAlloc(default_allocator, &pages_parts);
+ // _ = try std.fs.makeDirAbsolute(
+ // pages_dir,
+ // );
+ var router = try Router.init(&FileSystem.instance, default_allocator, Options.RouteConfig{
+ .dir = pages_dir,
+ .routes_enabled = true,
+ .extensions = &.{"js"},
+ });
+ Output.initTest();
+
+ const Resolver = @import("./resolver/resolver.zig").Resolver;
+ var logger = Logger.Log.init(default_allocator);
+ errdefer {
+ logger.printForLogLevel(Output.errorWriter()) catch {};
+ }
+
+ var opts = Options.BundleOptions{
+ .resolve_mode = .lazy,
+ .platform = .browser,
+ .loaders = undefined,
+ .define = undefined,
+ .log = &logger,
+ .entry_points = &.{},
+ .out_extensions = std.StringHashMap(string).init(default_allocator),
+ .transform_options = std.mem.zeroes(Api.TransformOptions),
+ .external = Options.ExternalModules.init(
+ default_allocator,
+ &FileSystem.instance.fs,
+ FileSystem.instance.top_level_dir,
+ &.{},
+ &logger,
+ .browser,
+ ),
+ };
+
+ var resolver = Resolver.init1(default_allocator, &logger, &FileSystem.instance, opts);
+
+ var root_dir = (try resolver.readDirInfo(pages_dir)).?;
+ var entries = root_dir.getEntries().?;
+ try router.loadRoutes(root_dir, Resolver, &resolver, 0, true);
+ var entry_points = try router.getEntryPoints(default_allocator);
+
+ try expectEqual(std.meta.fieldNames(@TypeOf(data)).len, entry_points.len);
+ return router;
+ }
+};
+
+test "Routes basic" {
+ var server = MockServer{};
+ var ctx = MockRequestContextType{
+ .url = try URLPath.parse("/hi"),
+ };
+ var router = try Test.make("routes-basic", .{
+ .@"pages/hi.js" = "//hi",
+ .@"pages/index.js" = "//index",
+ .@"pages/blog/hi.js" = "//blog/hi",
+ });
+ try router.match(&server, MockRequestContextType, &ctx);
+ try expectEqualStrings(ctx.matched_route.?.name, "/hi");
+
+ ctx = MockRequestContextType{
+ .url = try URLPath.parse("/"),
+ };
+
+ try router.match(&server, MockRequestContextType, &ctx);
+ try expectEqualStrings(ctx.matched_route.?.name, "/index");
+
+ ctx = MockRequestContextType{
+ .url = try URLPath.parse("/blog/hi"),
+ };
+
+ try router.match(&server, MockRequestContextType, &ctx);
+ try expectEqualStrings(ctx.matched_route.?.name, "/blog/hi");
+
+ ctx = MockRequestContextType{
+ .url = try URLPath.parse("/blog/hey"),
+ };
+
+ try router.match(&server, MockRequestContextType, &ctx);
+ try expect(ctx.matched_route == null);
+
+ ctx = MockRequestContextType{
+ .url = try URLPath.parse("/blog/"),
+ };
+
+ try router.match(&server, MockRequestContextType, &ctx);
+ try expect(ctx.matched_route == null);
+
+ ctx = MockRequestContextType{
+ .url = try URLPath.parse("/pages/hi"),
+ };
+
+ try router.match(&server, MockRequestContextType, &ctx);
+ try expect(ctx.matched_route == null);
+}
+
+test "Dynamic routes" {
+ var server = MockServer{};
+ var ctx = MockRequestContextType{
+ .url = try URLPath.parse("/blog/hi"),
+ };
+ var filepath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ var router = try Test.make("routes-dynamic", .{
+ .@"pages/index.js" = "//index.js",
+ .@"pages/blog/hi.js" = "//blog-hi",
+ .@"pages/posts/[id].js" = "//hi",
+ // .@"pages/blog/posts/bacon.js" = "//index",
+ });
+
+ try router.match(&server, MockRequestContextType, &ctx);
+ try expectEqualStrings(ctx.matched_route.?.name, "/blog/hi");
+
+ var params = ctx.matched_route.?.paramsIterator();
+ try expect(params.next() == null);
+
+ ctx.matched_route = null;
+
+ ctx.url = try URLPath.parse("/posts/123");
+ try router.match(&server, MockRequestContextType, &ctx);
+
+ params = ctx.matched_route.?.paramsIterator();
+
+ try expectEqualStrings(ctx.matched_route.?.name, "/posts/[id]");
+ try expectEqualStrings(params.next().?.rawValue(ctx.matched_route.?.pathname), "123");
+
+ // ctx = MockRequestContextType{
+ // .url = try URLPath.parse("/"),
+ // };
+
+ // try router.match(&server, MockRequestContextType, &ctx);
+ // try expectEqualStrings(ctx.matched_route.name, "index");
+}