1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
const std = @import("std");
const Route = @import("Route.zig");
const handler = @import("handler.zig");
const Interface = handler.Interface;
const Context = handler.Context;
literals: std.StaticStringMap(@This()),
identifiers: []const @This(),
interface: ?Interface,
pub inline fn init(comptime interfaces: []const Interface) @This() {
return initPrefix(.index, interfaces);
}
pub inline fn initPrefix(
comptime prefix: Route,
comptime interfaces: []const Interface,
) @This() {
return comptime {
@setEvalBranchQuota(interfaces.len * interfaces.len * 1000);
const KV = struct { []const u8, @This() };
var literals: []const KV = &.{};
var identifiers: []const @This() = &.{};
var current_interface: ?Interface = null;
for (interfaces) |interface| {
if (prefix.equals(&interface.route)) {
if (current_interface != null) {
@compileError("re-assignment of interface");
}
current_interface = interface;
} else if (prefix.isPrefixOf(&interface.route)) {
const inner_prefix: Route = .{
.segments = interface.route.segments[0 .. prefix.segments.len + 1],
};
switch (interface.route.segments[prefix.segments.len]) {
.literal => |l| {
const lit: []const KV = &.{.{ l, .initPrefix(inner_prefix, interfaces) }};
literals = literals ++ lit;
},
.identifier => {
const ident: []const @This() = &.{.initPrefix(inner_prefix, interfaces)};
identifiers = identifiers ++ ident;
},
}
}
}
return .{
.literals = .initComptime(literals),
.identifiers = identifiers,
.interface = current_interface,
};
};
}
pub fn getInterface(self: *const @This(), url: []const u8) ?Interface {
var current = std.mem.trimStart(u8, url, "/");
const index = std.mem.indexOfScalar(u8, current, '/') orelse current.len;
const next = current[index..];
current = current[0..index];
if (current.len == 0) {
return self.interface;
}
if (self.literals.get(current)) |child| {
if (child.getInterface(next)) |interface| {
return interface;
}
}
for (self.identifiers) |id| {
if (id.getInterface(next)) |interface| {
return interface;
}
}
return null;
}
test "init" {
const ctx1: Context(.{ "api", "images", .id, "metadata" }) = .{};
const ctx2: Context(.{ "api", "users", "settings", "overview" }) = .{};
const ctx3: Context(.{ "api", "users", .id, "settings", .key }) = .{};
const ctx4: Context(.{ "api", "users" }) = .{};
const routes = init(&.{
ctx1.interface(),
ctx2.interface(),
ctx3.interface(),
ctx4.interface(),
});
{
const interface = routes.getInterface("/api/images/123456/metadata") orelse return error.TestFailed;
try std.testing.expectEqual(@as(*const anyopaque, @ptrCast(&ctx1)), interface.base);
}
{
const interface = routes.getInterface("/api/users/settings/overview") orelse return error.TestFailed;
try std.testing.expectEqual(@as(*const anyopaque, @ptrCast(&ctx2)), interface.base);
}
{
const interface = routes.getInterface("/api/users/1234/settings/name") orelse return error.TestFailed;
try std.testing.expectEqual(@as(*const anyopaque, @ptrCast(&ctx3)), interface.base);
}
{
const interface = routes.getInterface("/////api//users///") orelse return error.TestFailed;
try std.testing.expectEqual(@as(*const anyopaque, @ptrCast(&ctx4)), interface.base);
}
{
try std.testing.expectEqual(null, routes.getInterface("/api/images/1234/"));
}
}
|