const std = @import("std"); pub const Segment = union(enum) { literal: []const u8, identifier: []const u8, pub fn format(self: *const @This(), writer: *std.Io.Writer) !void { switch (self.*) { .literal => |l| try writer.print("{s}", .{l}), .identifier => |l| try writer.print("{{{s}}}", .{l}), } } }; segments: []const Segment, pub const index: @This() = .{ .segments = &.{} }; pub fn fromAny(comptime route: anytype) @This() { var segments: []const Segment = &.{}; if (route == .index) { return index; } inline for (route) |r| { const segment: []const Segment = &.{switch (@TypeOf(r)) { @EnumLiteral() => .{ .identifier = @tagName(r) }, else => .{ .literal = r }, }}; segments = segments ++ segment; } return .{ .segments = segments }; } pub fn format(self: *const @This(), writer: *std.Io.Writer) !void { for (self.segments) |*seg| { try writer.print("/{f}", .{seg}); } } pub fn isPrefixOf(self: *const @This(), other: *const @This()) bool { if (self.segments.len > other.segments.len) { return false; } for (self.segments, other.segments[0..self.segments.len]) |s, o| { if (std.meta.activeTag(s) != std.meta.activeTag(o)) { return false; } if (s == .literal) { if (!std.mem.eql(u8, s.literal, o.literal)) { return false; } } } return true; } pub fn equals(self: *const @This(), other: *const @This()) bool { if (self.segments.len != other.segments.len) { return false; } for (self.segments, other.segments) |s, o| { if (std.meta.activeTag(s) != std.meta.activeTag(o)) { return false; } if (s == .literal) { if (!std.mem.eql(u8, s.literal, o.literal)) { return false; } } } return true; } test "isPrefixOf" { { const parent = comptime fromAny(.{ "api", "images", .id }); const child = comptime fromAny(.{ "api", "images", .id, "metadata" }); try std.testing.expect(parent.isPrefixOf(&child)); } { const parent = comptime fromAny(.{ "api", "videos", .id }); const child = comptime fromAny(.{ "api", "images", .id, "metadata" }); try std.testing.expect(!parent.isPrefixOf(&child)); } }