aboutsummaryrefslogtreecommitdiff
path: root/src/http/RouteSet.zig
blob: 623a2a951798b04625b47d1363078c2a538d4502 (plain)
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/"));
    }
}