aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-08-07 02:28:53 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-08-07 02:28:53 -0700
commit7b48e206db14c8a4bcf845cc79a342fad20c36dd (patch)
tree028d944fa6fd58a3490d53c75428babf3735ce54 /src
parent4b1f89114e02f8472b63794534698ec13929080e (diff)
downloadbun-7b48e206db14c8a4bcf845cc79a342fad20c36dd.tar.gz
bun-7b48e206db14c8a4bcf845cc79a342fad20c36dd.tar.zst
bun-7b48e206db14c8a4bcf845cc79a342fad20c36dd.zip
Dynamic routes resolve! Just gotta pass query params to the JS correctly
Former-commit-id: 2c4d3184a4f06ba9dd7d27d653834589796ebd0f
Diffstat (limited to 'src')
-rw-r--r--src/http.zig83
-rw-r--r--src/http/url_path.zig75
-rw-r--r--src/query_string_map.zig3
-rw-r--r--src/resolver/dir_info.zig74
-rw-r--r--src/resolver/resolve_path.zig2
-rw-r--r--src/resolver/resolver.zig69
-rw-r--r--src/router.zig113
-rw-r--r--src/strings.zig8
8 files changed, 223 insertions, 204 deletions
diff --git a/src/http.zig b/src/http.zig
index 86f27bbb4..8d83a2e72 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -56,80 +56,7 @@ pub fn println(comptime fmt: string, args: anytype) void {
}
const HTTPStatusCode = u10;
-
-pub const URLPath = struct {
- extname: string = "",
- path: string = "",
- pathname: string = "",
- first_segment: string = "",
- query_string: string = "",
-
- // This does one pass over the URL path instead of like 4
- pub fn parse(raw_path: string) URLPath {
- var question_mark_i: i16 = -1;
- var period_i: i16 = -1;
- var first_segment_end: i16 = std.math.maxInt(i16);
- var last_slash: i16 = -1;
-
- var i: i16 = @intCast(i16, raw_path.len) - 1;
- while (i >= 0) : (i -= 1) {
- const c = raw_path[@intCast(usize, i)];
-
- switch (c) {
- '?' => {
- question_mark_i = std.math.max(question_mark_i, i);
- if (question_mark_i < period_i) {
- period_i = -1;
- }
-
- if (last_slash > question_mark_i) {
- last_slash = -1;
- }
- },
- '.' => {
- period_i = std.math.max(period_i, i);
- },
- '/' => {
- last_slash = std.math.max(last_slash, i);
-
- if (i > 0) {
- first_segment_end = std.math.min(first_segment_end, i);
- }
- },
- else => {},
- }
- }
-
- if (last_slash > period_i) {
- period_i = -1;
- }
-
- const extname = brk: {
- if (question_mark_i > -1 and period_i > -1) {
- period_i += 1;
- break :brk raw_path[@intCast(usize, period_i)..@intCast(usize, question_mark_i)];
- } else if (period_i > -1) {
- period_i += 1;
- break :brk raw_path[@intCast(usize, period_i)..];
- } else {
- break :brk &([_]u8{});
- }
- };
-
- const path = if (question_mark_i < 0) raw_path[1..] else raw_path[1..@intCast(usize, question_mark_i)];
-
- const first_segment = raw_path[1..std.math.min(@intCast(usize, first_segment_end), raw_path.len)];
-
- return URLPath{
- .extname = extname,
- .pathname = raw_path,
- .first_segment = first_segment,
- .path = if (raw_path.len == 1) "." else path,
- .query_string = if (question_mark_i > -1) raw_path[@intCast(usize, question_mark_i)..@intCast(usize, raw_path.len)] else "",
- };
- }
-};
-
+pub const URLPath = @import("./http/url_path.zig");
pub const Method = enum {
GET,
HEAD,
@@ -754,17 +681,23 @@ pub const RequestContext = struct {
js_ast.Expr.Data.Store.reset();
}
var handler: *JavaScriptHandler = try channel.readItem();
+
try JavaScript.EventListenerMixin.emitFetchEvent(vm, &handler.ctx);
}
}
var one: [1]*JavaScriptHandler = undefined;
- pub fn enqueue(ctx: *RequestContext, server: *Server) !void {
+ pub fn enqueue(ctx: *RequestContext, server: *Server, filepath_buf: []u8) !void {
var clone = try ctx.allocator.create(JavaScriptHandler);
clone.ctx = ctx.*;
clone.conn = ctx.conn.*;
clone.ctx.conn = &clone.conn;
+ // it's a dead pointer now
+ clone.ctx.matched_route.?.file_path = filepath_buf[0..ctx.matched_route.?.file_path.len];
+ // this copy may be unnecessary, i'm not 100% sure where when
+ std.mem.copy(u8, &clone.ctx.match_file_path_buf, filepath_buf[0..ctx.matched_route.?.file_path.len]);
+
if (!has_loaded_channel) {
has_loaded_channel = true;
channel = Channel.init();
diff --git a/src/http/url_path.zig b/src/http/url_path.zig
new file mode 100644
index 000000000..a2e55555d
--- /dev/null
+++ b/src/http/url_path.zig
@@ -0,0 +1,75 @@
+usingnamespace @import("../global.zig");
+const std = @import("std");
+
+const URLPath = @This();
+
+extname: string = "",
+path: string = "",
+pathname: string = "",
+first_segment: string = "",
+query_string: string = "",
+
+// This does one pass over the URL path instead of like 4
+pub fn parse(raw_path: string) URLPath {
+ var question_mark_i: i16 = -1;
+ var period_i: i16 = -1;
+ var first_segment_end: i16 = std.math.maxInt(i16);
+ var last_slash: i16 = -1;
+
+ var i: i16 = @intCast(i16, raw_path.len) - 1;
+ while (i >= 0) : (i -= 1) {
+ const c = raw_path[@intCast(usize, i)];
+
+ switch (c) {
+ '?' => {
+ question_mark_i = std.math.max(question_mark_i, i);
+ if (question_mark_i < period_i) {
+ period_i = -1;
+ }
+
+ if (last_slash > question_mark_i) {
+ last_slash = -1;
+ }
+ },
+ '.' => {
+ period_i = std.math.max(period_i, i);
+ },
+ '/' => {
+ last_slash = std.math.max(last_slash, i);
+
+ if (i > 0) {
+ first_segment_end = std.math.min(first_segment_end, i);
+ }
+ },
+ else => {},
+ }
+ }
+
+ if (last_slash > period_i) {
+ period_i = -1;
+ }
+
+ const extname = brk: {
+ if (question_mark_i > -1 and period_i > -1) {
+ period_i += 1;
+ break :brk raw_path[@intCast(usize, period_i)..@intCast(usize, question_mark_i)];
+ } else if (period_i > -1) {
+ period_i += 1;
+ break :brk raw_path[@intCast(usize, period_i)..];
+ } else {
+ break :brk &([_]u8{});
+ }
+ };
+
+ const path = if (question_mark_i < 0) raw_path[1..] else raw_path[1..@intCast(usize, question_mark_i)];
+
+ const first_segment = raw_path[1..std.math.min(@intCast(usize, first_segment_end), raw_path.len)];
+
+ return URLPath{
+ .extname = extname,
+ .pathname = raw_path,
+ .first_segment = first_segment,
+ .path = if (raw_path.len == 1) "." else path,
+ .query_string = if (question_mark_i > -1) raw_path[@intCast(usize, question_mark_i)..@intCast(usize, raw_path.len)] else "",
+ };
+}
diff --git a/src/query_string_map.zig b/src/query_string_map.zig
index aec41ac98..dc091a54e 100644
--- a/src/query_string_map.zig
+++ b/src/query_string_map.zig
@@ -28,7 +28,8 @@ pub const QueryStringMap = struct {
}
pub const Iterator = struct {
- // Assume no query string param will exceed 2048 keys
+ // Assume no query string param map will exceed 2048 keys
+ // Browsers typically limit URL lengths to around 64k
const VisitedMap = std.bit_set.ArrayBitSet(usize, 2048);
i: usize = 0,
diff --git a/src/resolver/dir_info.zig b/src/resolver/dir_info.zig
new file mode 100644
index 000000000..c175cf2f2
--- /dev/null
+++ b/src/resolver/dir_info.zig
@@ -0,0 +1,74 @@
+usingnamespace @import("../global.zig");
+
+const allocators = @import("../allocators.zig");
+const DirInfo = @This();
+const Fs = @import("../fs.zig");
+const TSConfigJSON = @import("./tsconfig_json.zig").TSConfigJSON;
+const PackageJSON = @import("./package_json.zig").PackageJSON;
+
+pub const Index = allocators.IndexType;
+
+// These objects are immutable, so we can just point to the parent directory
+// and avoid having to lock the cache again
+parent: Index = allocators.NotFound,
+
+// A pointer to the enclosing dirInfo with a valid "browser" field in
+// package.json. We need this to remap paths after they have been resolved.
+enclosing_browser_scope: Index = allocators.NotFound,
+
+abs_path: string = "",
+entries: Index = undefined,
+has_node_modules: bool = false, // Is there a "node_modules" subdirectory?
+package_json: ?*PackageJSON = null, // Is there a "package.json" file?
+tsconfig_json: ?*TSConfigJSON = null, // Is there a "tsconfig.json" file in this directory or a parent directory?
+abs_real_path: string = "", // If non-empty, this is the real absolute path resolving any symlinks
+
+pub fn getFileDescriptor(dirinfo: *const DirInfo) StoredFileDescriptorType {
+ if (!FeatureFlags.store_file_descriptors) {
+ return 0;
+ }
+
+ if (dirinfo.getEntries()) |entries| {
+ return entries.fd;
+ } else {
+ return 0;
+ }
+}
+
+pub fn getEntries(dirinfo: *const DirInfo) ?*Fs.FileSystem.DirEntry {
+ var entries_ptr = Fs.FileSystem.instance.fs.entries.atIndex(dirinfo.entries) orelse return null;
+ switch (entries_ptr.*) {
+ .entries => |entr| {
+ return &entries_ptr.entries;
+ },
+ .err => {
+ return null;
+ },
+ }
+}
+
+pub fn getEntriesConst(dirinfo: *const DirInfo) ?*const Fs.FileSystem.DirEntry {
+ const entries_ptr = Fs.FileSystem.instance.fs.entries.atIndex(dirinfo.entries) orelse return null;
+ switch (entries_ptr.*) {
+ .entries => |entr| {
+ return &entries_ptr.entries;
+ },
+ .err => {
+ return null;
+ },
+ }
+}
+
+pub fn getParent(i: *const DirInfo) ?*DirInfo {
+ return HashMap.instance.atIndex(i.parent);
+}
+pub fn getEnclosingBrowserScope(i: *const DirInfo) ?*DirInfo {
+ return HashMap.instance.atIndex(i.enclosing_browser_scope);
+}
+
+// Goal: Really fast, low allocation directory map exploiting cache locality where we don't worry about lifetimes much.
+// 1. Don't store the keys or values of directories that don't exist
+// 2. Don't expect a provided key to exist after it's queried
+// 3. Store whether a directory has been queried and whether that query was successful.
+// 4. Allocate onto the https://en.wikipedia.org/wiki/.bss#BSS_in_C instead of the heap, so we can avoid memory leaks
+pub const HashMap = allocators.BSSMap(DirInfo, Fs.Preallocate.Counts.dir_entry, false, 128);
diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig
index a2ed2773d..212b2ff4d 100644
--- a/src/resolver/resolve_path.zig
+++ b/src/resolver/resolve_path.zig
@@ -664,7 +664,7 @@ pub fn joinStringBuf(buf: []u8, _parts: anytype, comptime _platform: Platform) [
const platform = comptime _platform.resolve();
for (_parts) |part| {
- if (part.len == 0 or (part.len == 1 and part[1] == '.')) {
+ if (part.len == 0 or (part.len == 1 and part[0] == '.')) {
continue;
}
diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig
index 59b5fdce8..63acc11bf 100644
--- a/src/resolver/resolver.zig
+++ b/src/resolver/resolver.zig
@@ -9,6 +9,7 @@ const sync = @import("../sync.zig");
const TSConfigJSON = @import("./tsconfig_json.zig").TSConfigJSON;
const PackageJSON = @import("./package_json.zig").PackageJSON;
usingnamespace @import("./data_url.zig");
+pub const DirInfo = @import("./dir_info.zig");
const Wyhash = std.hash.Wyhash;
@@ -34,74 +35,6 @@ pub const SideEffectsData = struct {
is_side_effects_array_in_json: bool = false,
};
-pub const DirInfo = struct {
- pub const Index = allocators.IndexType;
-
- // These objects are immutable, so we can just point to the parent directory
- // and avoid having to lock the cache again
- parent: Index = allocators.NotFound,
-
- // A pointer to the enclosing dirInfo with a valid "browser" field in
- // package.json. We need this to remap paths after they have been resolved.
- enclosing_browser_scope: Index = allocators.NotFound,
-
- abs_path: string = "",
- entries: Index = undefined,
- has_node_modules: bool = false, // Is there a "node_modules" subdirectory?
- package_json: ?*PackageJSON = null, // Is there a "package.json" file?
- tsconfig_json: ?*TSConfigJSON = null, // Is there a "tsconfig.json" file in this directory or a parent directory?
- abs_real_path: string = "", // If non-empty, this is the real absolute path resolving any symlinks
-
- pub fn getFileDescriptor(dirinfo: *const DirInfo) StoredFileDescriptorType {
- if (!FeatureFlags.store_file_descriptors) {
- return 0;
- }
-
- if (dirinfo.getEntries()) |entries| {
- return entries.fd;
- } else {
- return 0;
- }
- }
-
- pub fn getEntries(dirinfo: *const DirInfo) ?*Fs.FileSystem.DirEntry {
- var entries_ptr = Fs.FileSystem.instance.fs.entries.atIndex(dirinfo.entries) orelse return null;
- switch (entries_ptr.*) {
- .entries => |entr| {
- return &entries_ptr.entries;
- },
- .err => {
- return null;
- },
- }
- }
-
- pub fn getEntriesConst(dirinfo: *const DirInfo) ?*const Fs.FileSystem.DirEntry {
- const entries_ptr = Fs.FileSystem.instance.fs.entries.atIndex(dirinfo.entries) orelse return null;
- switch (entries_ptr.*) {
- .entries => |entr| {
- return &entries_ptr.entries;
- },
- .err => {
- return null;
- },
- }
- }
-
- pub fn getParent(i: *const DirInfo) ?*DirInfo {
- return HashMap.instance.atIndex(i.parent);
- }
- pub fn getEnclosingBrowserScope(i: *const DirInfo) ?*DirInfo {
- return HashMap.instance.atIndex(i.enclosing_browser_scope);
- }
-
- // Goal: Really fast, low allocation directory map exploiting cache locality where we don't worry about lifetimes much.
- // 1. Don't store the keys or values of directories that don't exist
- // 2. Don't expect a provided key to exist after it's queried
- // 3. Store whether a directory has been queried and whether that query was successful.
- // 4. Allocate onto the https://en.wikipedia.org/wiki/.bss#BSS_in_C instead of the heap, so we can avoid memory leaks
- pub const HashMap = allocators.BSSMap(DirInfo, Fs.Preallocate.Counts.dir_entry, false, 128);
-};
pub const TemporaryBuffer = struct {
pub threadlocal var ExtensionPathBuf: [512]u8 = undefined;
pub threadlocal var TSConfigMatchStarBuf: [512]u8 = undefined;
diff --git a/src/router.zig b/src/router.zig
index d122db0a6..a0aad89d7 100644
--- a/src/router.zig
+++ b/src/router.zig
@@ -6,12 +6,13 @@
const Router = @This();
const std = @import("std");
-const DirInfo = @import("./resolver/resolver.zig").DirInfo;
usingnamespace @import("global.zig");
+
+const DirInfo = @import("./resolver/dir_info.zig");
const Fs = @import("./fs.zig");
const Options = @import("./options.zig");
const allocators = @import("./allocators.zig");
-const URLPath = @import("./http.zig").URLPath;
+const URLPath = @import("./http/url_path.zig");
const index_route_hash = @truncate(u32, std.hash.Wyhash.hash(0, "index"));
const arbitrary_max_route = 4096;
@@ -118,13 +119,14 @@ pub fn loadRoutes(
var route: Route = Route.parse(
entry.base,
- entry.dir[this.config.dir.len..],
+ Fs.PathName.init(entry.dir[this.config.dir.len..]).dirWithTrailingSlash(),
"",
entry_ptr.value,
);
route.parent = parent;
- route.children.offset = @truncate(u16, this.routes.routes.len);
+
+ route.children.offset = @truncate(u16, this.routes.routes.len + 1);
try this.routes.routes.append(this.allocator, route);
// potential stack overflow!
@@ -136,7 +138,7 @@ pub fn loadRoutes(
false,
);
- this.routes.routes.items(.children)[route.children.offset].len = @truncate(u16, this.routes.routes.len) - route.children.offset;
+ this.routes.routes.items(.children)[route.children.offset - 1].len = @truncate(u16, this.routes.routes.len) - route.children.offset;
}
},
@@ -149,7 +151,8 @@ pub fn loadRoutes(
if (strings.eql(extname[1..], _extname)) {
var route = Route.parse(
entry.base,
- entry.dir[this.config.dir.len..],
+ // we extend the pointer length by one to get it's slash
+ entry.dir.ptr[this.config.dir.len..entry.dir.len],
extname,
entry_ptr.value,
);
@@ -217,10 +220,14 @@ pub const Route = struct {
pub const Ptr = TinyPtr;
pub fn parse(base: string, dir: string, extname: string, entry_index: allocators.IndexType) Route {
- var parts = [_]string{ dir, base };
+ const ensure_slash = if (dir.len > 0 and dir[dir.len - 1] != '/') "/" else "";
+
+ var parts = [3]string{ dir, ensure_slash, base };
// this isn't really absolute, it's relative to the pages dir
- const absolute = Fs.FileSystem.instance.abs(&parts);
+ const absolute = Fs.FileSystem.instance.join(&parts);
const name = base[0 .. base.len - extname.len];
+ const start_index: usize = if (absolute[0] == '/') 1 else 0;
+ var hash_path = absolute[start_index .. absolute.len - extname.len];
return Route{
.name = name,
@@ -237,7 +244,7 @@ pub const Route = struct {
u32,
std.hash.Wyhash.hash(
0,
- absolute[0 .. absolute.len - extname.len],
+ hash_path,
),
),
.part = RoutePart.parse(name),
@@ -326,8 +333,7 @@ pub const RouteMap = struct {
redirect_path: ?string = "",
url_path: URLPath,
- matched_route_name: PathBuilder = PathBuilder.init(),
- matched_route_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined,
+ matched_route_buf: []u8 = undefined,
file_path: string = "",
@@ -336,31 +342,18 @@ pub const RouteMap = struct {
head_i: u16,
segment_i: u16,
) ?Match {
- if (this.segments.len == 0) return null;
-
- const _match = this._matchDynamicRoute(head_i, segment_i) orelse return null;
- this.matched_route_name.append("/");
- this.matched_route_name.append(_match.name);
- return _match;
- }
-
- fn _matchDynamicRoute(
- this: *MatchContext,
- head_i: u16,
- segment_i: u16,
- ) ?Match {
const start_len = this.params.len;
var head = this.map.routes.get(head_i);
- const segment: string = this.segments[segment_i];
- const remaining: []string = this.segments[segment_i..];
+ const remaining: []string = this.segments[segment_i + 1 ..];
- if (remaining.len > 0 and head.children.len == 0) {
+ if ((remaining.len > 0 and head.children.len == 0)) {
return null;
}
switch (head.part.tag) {
.exact => {
- if (this.hashes[segment_i] != head.hash) {
+ // is it the end of an exact match?
+ if (!(this.hashes.len > segment_i and this.hashes[segment_i] == head.hash)) {
return null;
}
},
@@ -391,16 +384,17 @@ pub const RouteMap = struct {
} else {
if (Fs.FileSystem.DirEntry.EntryStore.instance.at(head.entry_index)) |entry| {
var parts = [_]string{ entry.dir, entry.base };
+ const file_path = Fs.FileSystem.instance.absBuf(&parts, this.matched_route_buf);
match_result = Match{
.path = head.path,
- .name = head.name,
+ .name = file_path,
.params = this.params,
.hash = head.full_hash,
.query_string = this.url_path.query_string,
.pathname = this.url_path.pathname,
- .file_path = Fs.FileSystem.instance.absBuf(&parts, &this.matched_route_buf),
.basename = entry.base,
+ .file_path = file_path,
};
this.matched_route_buf[match_result.file_path.len] = 0;
@@ -414,7 +408,7 @@ pub const RouteMap = struct {
this.allocator,
Param{
.key = head.part.str(head.name),
- .value = segment,
+ .value = this.segments[segment_i],
.kind = head.part.tag,
},
) catch unreachable;
@@ -428,7 +422,7 @@ pub const RouteMap = struct {
// This makes many passes over the list of routes
// However, most of those passes are basically array.indexOf(number) and then smallerArray.indexOf(number)
- pub fn matchPage(this: *RouteMap, file_path_buf: []u8, url_path: URLPath, params: *Param.List) ?Match {
+ pub fn matchPage(this: *RouteMap, routes_dir: string, file_path_buf: []u8, url_path: URLPath, params: *Param.List) ?Match {
// Trim trailing slash
var path = url_path.path;
var redirect = false;
@@ -479,24 +473,25 @@ pub const RouteMap = struct {
}
const full_hash = @truncate(u32, std.hash.Wyhash.hash(0, path));
+ const routes_slice = this.routes.slice();
// Check for an exact match
// These means there are no params.
- if (std.mem.indexOfScalar(u32, this.routes.items(.full_hash), full_hash)) |exact_match| {
+ if (std.mem.indexOfScalar(u32, routes_slice.items(.full_hash), full_hash)) |exact_match| {
const route = this.routes.get(exact_match);
// It might be a folder with an index route
// /bacon/index.js => /bacon
if (route.children.len > 0) {
- const children = this.routes.items(.hash)[route.children.offset .. route.children.offset + route.children.len];
+ const children = routes_slice.items(.hash)[route.children.offset .. route.children.offset + route.children.len];
for (children) |child_hash, i| {
if (child_hash == index_route_hash) {
- const entry = Fs.FileSystem.DirEntry.EntryStore.instance.at(this.routes.items(.entry_index)[i + route.children.offset]).?;
+ const entry = Fs.FileSystem.DirEntry.EntryStore.instance.at(routes_slice.items(.entry_index)[i + route.children.offset]).?;
const parts = [_]string{ entry.dir, entry.base };
return Match{
.params = params,
- .name = this.routes.items(.name)[i],
- .path = this.routes.items(.path)[i],
+ .name = routes_slice.items(.name)[i],
+ .path = routes_slice.items(.path)[i],
.pathname = url_path.pathname,
.basename = entry.base,
.hash = child_hash,
@@ -528,22 +523,25 @@ pub const RouteMap = struct {
var segments: []string = segments_buf[0..];
var hashes: []u32 = segments_hash[0..];
var segment_i: usize = 0;
- for (path) |i, c| {
- if (c == '/') {
- // if the URL is /foo/./foo
- // rewrite it as /foo/foo
- segments[segment_i] = path[last_slash_i..i];
- hashes[segment_i] = @truncate(u32, std.hash.Wyhash.hash(0, segments[segment_i]));
-
- if (!(segments[segment_i].len == 1 and segments[segment_i][0] == '.')) {
- segment_i += 1;
- }
-
- last_slash_i = i + 1;
- }
+ var splitter = std.mem.tokenize(path, "/");
+ while (splitter.next()) |part| {
+ if (part.len == 0 or (part.len == 1 and part[0] == '.')) continue;
+ segments[segment_i] = part;
+ hashes[segment_i] = @truncate(u32, std.hash.Wyhash.hash(0, part));
+ segment_i += 1;
}
segments = segments[0..segment_i];
-
+ hashes = hashes[0..segment_i];
+
+ // Now, we've established that there is no exact match.
+ // Something will be dynamic
+ // There are three tricky things about this.
+ // 1. It's possible that the correct route is a catch-all route or an optional catch-all route.
+ // 2. Given routes like this:
+ // * [name]/[id]
+ // * foo/[id]
+ // If the URL is /foo/123
+ // Then the correct route is foo/[id]
var ctx = MatchContext{
.params = params,
.segments = segments,
@@ -552,11 +550,17 @@ pub const RouteMap = struct {
.redirect_path = if (redirect) path else null,
.allocator = this.allocator,
.url_path = url_path,
+ .matched_route_buf = file_path_buf,
};
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;
- dynamic_route.name = ctx.matched_route_name.str();
+ dynamic_route.name = dynamic_route.name[this.config.dir.len..];
+ dynamic_route.name = dynamic_route.name[0 .. dynamic_route.name.len - std.fs.path.extension(dynamic_route.file_path).len];
+ std.debug.assert(dynamic_route.name.len > 0);
+ if (dynamic_route.name[0] == '/') dynamic_route.name = dynamic_route.name[1..];
+
return dynamic_route;
}
@@ -649,7 +653,8 @@ pub fn match(app: *Router, server: anytype, comptime RequestContextType: type, c
}
params_list.shrinkRetainingCapacity(0);
- if (app.routes.matchPage(&ctx.match_file_path_buf, ctx.url, &params_list)) |route| {
+ var filepath_buf = std.mem.span(&ctx.match_file_path_buf);
+ if (app.routes.matchPage(app.config.dir, filepath_buf, ctx.url, &params_list)) |route| {
if (route.redirect_path) |redirect| {
try ctx.handleRedirect(redirect);
return;
@@ -664,7 +669,7 @@ pub fn match(app: *Router, server: anytype, comptime RequestContextType: type, c
}
ctx.matched_route = route;
- RequestContextType.JavaScriptHandler.enqueue(ctx, server) catch {
+ RequestContextType.JavaScriptHandler.enqueue(ctx, server, filepath_buf) catch {
server.javascript_enabled = false;
};
}
diff --git a/src/strings.zig b/src/strings.zig
index cb54349d3..189116b2b 100644
--- a/src/strings.zig
+++ b/src/strings.zig
@@ -12,16 +12,14 @@ pub const eql = std.meta.eql;
pub fn NewStringBuilder(comptime size: usize) type {
return struct {
const This = @This();
- buffer: [size + 1]u8 = undefined,
+ buffer: [*]u8 = undefined,
remain: []u8 = undefined,
pub fn init() This {
- var instance = This{};
- instance.load();
- return instance;
+ return This{};
}
- fn load(this: *This) void {
+ pub fn load(this: *This) void {
this.remain = (&this.buffer)[0..size];
}