const std = @import("std"); const Route = @import("Route.zig"); repr: []const u8, pub fn as(self: @This(), T: type) (if (T == []const u8) T else anyerror!T) { if (T == []const u8) { return self.repr; } else if (@typeInfo(T) == .int) { return std.fmt.parseInt(T, self.repr, 10); } @compileError(std.fmt.comptimePrint("unsupported default type '{}'", .{T})); } pub fn convert(self: @This(), func: anytype) (@typeInfo(@TypeOf(func)).@"fn".return_type orelse void) { return func(self.repr); } pub fn Struct(comptime route: Route) type { var names: []const []const u8 = &.{}; inline for (route.segments) |segment| { switch (segment) { .literal => {}, .identifier => |id| { const name: []const []const u8 = &.{id}; names = names ++ name; }, } } var types: [names.len]type = undefined; var attrs: [names.len]std.builtin.Type.StructField.Attributes = undefined; @memset(&types, @This()); @memset(&attrs, std.builtin.Type.StructField.Attributes{}); return @Struct(.auto, null, names, &types, &attrs); } pub fn parse(P: type, comptime route: *const Route, url: []const u8) P { var params: P = undefined; var buffer = url; inline for (route.segments) |segment| { buffer = std.mem.trimStart(u8, buffer, "/"); const index = std.mem.indexOfScalar(u8, buffer, '/') orelse buffer.len; const value = buffer[0..index]; buffer = buffer[index..]; switch (segment) { .literal => {}, .identifier => |id| { @field(params, id) = .{ .repr = value }; }, } } return params; } test "parse" { const route: Route = comptime .fromAny(.{ "api", "users", .name, "settings", .id }); const Params = Struct(route); const url = "/api/users/nathan.reiner/settings/123456789"; const params = parse(Params, &route, url); try std.testing.expectEqualStrings("nathan.reiner", params.name.as([]const u8)); try std.testing.expectEqual(123456789, try params.id.as(usize)); }