Add way to specify route params

This commit is contained in:
jaina heartles 2022-05-20 23:28:14 -07:00
parent c19cc267bf
commit 789e9062b2

View file

@ -45,15 +45,19 @@ const PathIter = struct {
} }
}; };
fn splitPath(comptime path: []const u8) []const []const u8 { fn splitRoutePath(comptime path: []const u8) []const RouteSegment {
comptime { comptime {
var segments: [path.len][]const u8 = undefined; var segments: [path.len]RouteSegment = undefined;
var iter = PathIter.from(path); var iter = PathIter.from(path);
var it = iter.next();
var count = 0; var count = 0;
while (it != null) : (it = iter.next()) { while (iter.next()) |it| {
segments[count] = it.?; if (it[0] == ':') {
segments[count] = .{ .param = it[1..] };
} else {
segments[count] = .{ .literal = it };
}
count += 1; count += 1;
} }
@ -61,17 +65,22 @@ fn splitPath(comptime path: []const u8) []const []const u8 {
} }
} }
const RouteSegment = union(enum) {
literal: []const u8,
param: []const u8,
};
fn RouteWithContext(comptime Context: type) type { fn RouteWithContext(comptime Context: type) type {
return struct { return struct {
const Self = @This(); const Self = @This();
pub const Handler = fn (Context) void; pub const Handler = fn (Context) void;
path_segments: []const []const u8, path_segments: []const RouteSegment,
method: http.Method, method: http.Method,
handler: Handler, handler: Handler,
pub fn bind(method: http.Method, comptime path: []const u8, handler: Handler) Self { pub fn bind(method: http.Method, comptime path: []const u8, handler: Handler) Self {
return .{ .method = method, .path_segments = splitPath(path), .handler = handler }; return .{ .method = method, .path_segments = splitRoutePath(path), .handler = handler };
} }
fn matchesPath(self: *const Self, request_path: []const u8) bool { fn matchesPath(self: *const Self, request_path: []const u8) bool {
@ -79,7 +88,12 @@ fn RouteWithContext(comptime Context: type) type {
for (self.path_segments) |route_seg| { for (self.path_segments) |route_seg| {
const request_seg = request_segments.next() orelse return false; const request_seg = request_segments.next() orelse return false;
if (!ciutf8.eql(route_seg, request_seg)) return false; switch (route_seg) {
.literal => |lit| {
if (!ciutf8.eql(lit, request_seg)) return false;
},
.param => {},
}
} }
return request_segments.next() == null; return request_segments.next() == null;
@ -136,35 +150,29 @@ const _tests = struct {
try std.testing.expectEqual(@as(?[]const u8, null), it.next()); try std.testing.expectEqual(@as(?[]const u8, null), it.next());
} }
fn expectEqualSegments(expected: []const []const u8, actual: []const []const u8) !void { fn expectEqualSegments(expected: []const RouteSegment, actual: []const RouteSegment) !void {
try std.testing.expectEqual(expected.len, actual.len); try std.testing.expectEqual(expected.len, actual.len);
for (expected) |_, i| { for (expected) |_, i| {
try std.testing.expectEqualStrings(expected[i], actual[i]); try std.testing.expectEqual(
std.meta.activeTag(expected[i]),
std.meta.activeTag(actual[i]),
);
switch (expected[i]) {
.literal => |exp| try std.testing.expectEqualStrings(exp, actual[i].literal),
.param => |exp| try std.testing.expectEqualStrings(exp, actual[i].param),
}
} }
} }
test "splitPath" { test "splitRoutePath" {
const path = "//ab/c/de"; const path = "//ab/c/:de/";
const segments = splitPath(path); const segments = splitRoutePath(path);
try expectEqualSegments(&[_][]const u8{ "ab", "c", "de" }, segments); try expectEqualSegments(&[_]RouteSegment{
} .{ .literal = "ab" },
.{ .literal = "c" },
fn expectEqualRoute(expected: RouteWithContext(Context), actual: RouteWithContext(Context)) !void { .{ .param = "de" },
try std.testing.expectEqual(expected.method, actual.method); }, segments);
try std.testing.expectEqual(expected.handler, actual.handler);
try expectEqualSegments(expected.path_segments, actual.path_segments);
}
test "RouteWithContext(T).bind" {
const R = RouteWithContext(Context);
const r = R.bind(.GET, "//ab//cd", dummyHandler);
try expectEqualRoute(R{
.method = .GET,
.path_segments = &[_][]const u8{ "ab", "cd" },
.handler = dummyHandler,
}, r);
} }
test "RouteWithContext(T).matchesPath" { test "RouteWithContext(T).matchesPath" {
@ -351,4 +359,24 @@ const _tests = struct {
try mock_a.expectCalledOnceWith(12); try mock_a.expectCalledOnceWith(12);
try mock_404.expectNotCalled(); try mock_404.expectNotCalled();
} }
test "Router(T).dispatch with variables" {
const mock_a = CallTracker(.{}, dummyHandler);
const mock_404 = CallTracker(.{}, dummyHandler);
const R = Router(Context).Route;
const routes = [_]R{
R.bind(.GET, "/test/:id/abcd", mock_a.func),
};
const router = Router(Context){ .routes = &routes, .route_404 = mock_404.func };
router.dispatch(.GET, "/test/lskdjflsdjfksld/abcd", 10);
try mock_a.expectCalledOnceWith(10);
try mock_404.expectNotCalled();
mock_a.reset();
router.dispatch(.GET, "/test//abcd", 10);
try mock_a.expectNotCalled();
try mock_404.expectCalledOnceWith(10);
}
}; };