@beignet/core
Version:
Core framework primitives for Beignet
88 lines (77 loc) • 2.23 kB
text/typescript
import { parsePathTemplate } from "../contracts/index.js";
export type CompiledPath = {
keys: string[];
pattern: RegExp;
segments: ReturnType<typeof parsePathTemplate>["segments"];
normalizedPath: string;
shapeKey: string;
};
export class PathDecodeError extends Error {
constructor() {
super("Malformed URL path");
this.name = "PathDecodeError";
}
}
function encodeStaticSegment(value: string): string {
return encodeURI(value).replace(
/[?#]/g,
(character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,
);
}
function matchPercentEncodingCase(value: string): string {
return value.replace(/%([0-9A-F]{2})/g, (_match, encoded: string) => {
const digits = [...encoded]
.map((digit) =>
/[A-F]/.test(digit) ? `[${digit}${digit.toLowerCase()}]` : digit,
)
.join("");
return `%${digits}`;
});
}
export function compilePath(path: string): CompiledPath {
const parsed = parsePathTemplate(path);
const regexParts = parsed.segments.map((segment) =>
segment.kind === "dynamic"
? "([^/]+)"
: matchPercentEncodingCase(
encodeStaticSegment(segment.value).replace(
/[.*+?^${}()|[\]\\]/g,
"\\$&",
),
),
);
const pattern = new RegExp(`^/${regexParts.join("/")}$`);
return { ...parsed, pattern };
}
export function decodeMatchedParams(
keys: string[],
match: RegExpExecArray,
): Record<string, string> {
const params: Record<string, string> = {};
try {
keys.forEach((key, index) => {
params[key] = decodeURIComponent(match[index + 1]);
});
} catch (error) {
if (error instanceof URIError) {
throw new PathDecodeError();
}
throw error;
}
return params;
}
export function compareRouteSpecificity(
a: CompiledPath,
b: CompiledPath,
): number {
const maxLength = Math.max(a.segments.length, b.segments.length);
for (let index = 0; index < maxLength; index++) {
const aSegment = a.segments[index];
const bSegment = b.segments[index];
if (!aSegment) return 1;
if (!bSegment) return -1;
if (aSegment.kind === bSegment.kind) continue;
return aSegment.kind === "static" ? -1 : 1;
}
return 0;
}