Match routes by path segments
This commit is contained in:
parent
283b85cfcc
commit
d885dc5fc8
1 changed files with 105 additions and 3 deletions
108
src/router.zig
108
src/router.zig
|
@ -2,6 +2,49 @@ const std = @import("std");
|
||||||
const http = @import("./http.zig");
|
const http = @import("./http.zig");
|
||||||
const ciutf8 = @import("./util.zig").ciutf8;
|
const ciutf8 = @import("./util.zig").ciutf8;
|
||||||
|
|
||||||
|
const PathIter = struct {
|
||||||
|
is_first: bool,
|
||||||
|
path: []const u8,
|
||||||
|
|
||||||
|
pub fn from(path: []const u8) PathIter {
|
||||||
|
return .{ .path = path, .is_first = true };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(self: *PathIter) ?[]const u8 {
|
||||||
|
if (self.path.len == 0) {
|
||||||
|
if (self.is_first) {
|
||||||
|
self.is_first = false;
|
||||||
|
return self.path;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var start: usize = 0;
|
||||||
|
var end: usize = start;
|
||||||
|
while (end < self.path.len) : (end += 1) {
|
||||||
|
// skip leading slash
|
||||||
|
if (end == start and self.path[start] == '/') {
|
||||||
|
start += 1;
|
||||||
|
continue;
|
||||||
|
} else if (self.path[end] == '/') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start == end) {
|
||||||
|
self.path = self.path[end..end];
|
||||||
|
self.is_first = false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = self.path[start..end];
|
||||||
|
self.path = self.path[end..];
|
||||||
|
self.is_first = false;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
fn RouteWithContext(comptime Context: type) type {
|
fn RouteWithContext(comptime Context: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
@ -14,6 +57,22 @@ fn RouteWithContext(comptime Context: type) type {
|
||||||
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 = path, .handler = handler };
|
return .{ .method = method, .path = path, .handler = handler };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn matchesPath(self: *const Self, path: []const u8) bool {
|
||||||
|
var route_segs = PathIter.from(self.path);
|
||||||
|
var path_segs = PathIter.from(path);
|
||||||
|
|
||||||
|
var r = route_segs.next();
|
||||||
|
var p = path_segs.next();
|
||||||
|
while (r != null and p != null) : ({
|
||||||
|
r = route_segs.next();
|
||||||
|
p = path_segs.next();
|
||||||
|
}) {
|
||||||
|
if (!ciutf8.eql(r.?, p.?)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r == null and p == null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +87,7 @@ pub fn Router(comptime Context: type) type {
|
||||||
|
|
||||||
pub fn dispatch(self: *const Self, method: http.Method, path: []const u8, ctx: Context) void {
|
pub fn dispatch(self: *const Self, method: http.Method, path: []const u8, ctx: Context) void {
|
||||||
for (self.routes) |r| {
|
for (self.routes) |r| {
|
||||||
if (method == r.method and ciutf8.eql(path, r.path)) {
|
if (method == r.method and r.matchesPath(path)) {
|
||||||
return r.handler(ctx);
|
return r.handler(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,17 +101,60 @@ test {
|
||||||
_ = _tests;
|
_ = _tests;
|
||||||
}
|
}
|
||||||
const _tests = struct {
|
const _tests = struct {
|
||||||
|
test "PathIter" {
|
||||||
|
const path = "/ab/cd/";
|
||||||
|
var it = PathIter.from(path);
|
||||||
|
try std.testing.expectEqualStrings("ab", it.next().?);
|
||||||
|
try std.testing.expectEqualStrings("cd", it.next().?);
|
||||||
|
try std.testing.expectEqual(@as(?[]const u8, null), it.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "PathIter empty" {
|
||||||
|
const path = "";
|
||||||
|
var it = PathIter.from(path);
|
||||||
|
try std.testing.expectEqualStrings("", it.next().?);
|
||||||
|
try std.testing.expectEqual(@as(?[]const u8, null), it.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "PathIter complex" {
|
||||||
|
const path = "ab/c//defg/";
|
||||||
|
var it = PathIter.from(path);
|
||||||
|
try std.testing.expectEqualStrings("ab", it.next().?);
|
||||||
|
try std.testing.expectEqualStrings("c", it.next().?);
|
||||||
|
try std.testing.expectEqualStrings("defg", it.next().?);
|
||||||
|
try std.testing.expectEqual(@as(?[]const u8, null), it.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
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 std.testing.expectEqualStrings(expected.path, actual.path);
|
||||||
|
}
|
||||||
|
|
||||||
test "RouteWithContext(T).bind" {
|
test "RouteWithContext(T).bind" {
|
||||||
const R = RouteWithContext(Context);
|
const R = RouteWithContext(Context);
|
||||||
const r = R.bind(.GET, "//ab//cd", dummyHandler);
|
const r = R.bind(.GET, "//ab//cd", dummyHandler);
|
||||||
|
|
||||||
try std.testing.expectEqual(R{
|
try expectEqualRoute(R{
|
||||||
.method = .GET,
|
.method = .GET,
|
||||||
.path = "/ab/cd",
|
.path = "//ab//cd",
|
||||||
.handler = dummyHandler,
|
.handler = dummyHandler,
|
||||||
}, r);
|
}, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "RouteWithContext(T).matchesPath" {
|
||||||
|
const R = RouteWithContext(Context);
|
||||||
|
const r = R.bind(.GET, "/ab/cd", dummyHandler);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(true, r.matchesPath("ab///cd////"));
|
||||||
|
try std.testing.expectEqual(true, r.matchesPath("//ab///cd"));
|
||||||
|
try std.testing.expectEqual(true, r.matchesPath("ab/cd"));
|
||||||
|
try std.testing.expectEqual(true, r.matchesPath("/ab/cd"));
|
||||||
|
try std.testing.expectEqual(false, r.matchesPath("/a/b/c/d"));
|
||||||
|
try std.testing.expectEqual(false, r.matchesPath("/aa/aa"));
|
||||||
|
try std.testing.expectEqual(false, r.matchesPath(""));
|
||||||
|
}
|
||||||
|
|
||||||
fn CallTracker(comptime _uniq: anytype, comptime next: fn (Context) void) type {
|
fn CallTracker(comptime _uniq: anytype, comptime next: fn (Context) void) type {
|
||||||
_ = _uniq;
|
_ = _uniq;
|
||||||
return struct {
|
return struct {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue