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
1 changed files with 59 additions and 31 deletions

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 {
var segments: [path.len][]const u8 = undefined;
var segments: [path.len]RouteSegment = undefined;
var iter = PathIter.from(path);
var it = iter.next();
var count = 0;
while (it != null) : (it = iter.next()) {
segments[count] = it.?;
while (iter.next()) |it| {
if (it[0] == ':') {
segments[count] = .{ .param = it[1..] };
} else {
segments[count] = .{ .literal = it };
}
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 {
return struct {
const Self = @This();
pub const Handler = fn (Context) void;
path_segments: []const []const u8,
path_segments: []const RouteSegment,
method: http.Method,
handler: Handler,
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 {
@ -79,7 +88,12 @@ fn RouteWithContext(comptime Context: type) type {
for (self.path_segments) |route_seg| {
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;
@ -136,35 +150,29 @@ const _tests = struct {
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);
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" {
const path = "//ab/c/de";
const segments = splitPath(path);
test "splitRoutePath" {
const path = "//ab/c/:de/";
const segments = splitRoutePath(path);
try expectEqualSegments(&[_][]const u8{ "ab", "c", "de" }, segments);
}
fn expectEqualRoute(expected: RouteWithContext(Context), actual: RouteWithContext(Context)) !void {
try std.testing.expectEqual(expected.method, actual.method);
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);
try expectEqualSegments(&[_]RouteSegment{
.{ .literal = "ab" },
.{ .literal = "c" },
.{ .param = "de" },
}, segments);
}
test "RouteWithContext(T).matchesPath" {
@ -351,4 +359,24 @@ const _tests = struct {
try mock_a.expectCalledOnceWith(12);
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);
}
};