Skip to content

Commit

Permalink
fix: segment parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
lifeiscontent committed Apr 1, 2024
1 parent 32c50bb commit 93ffc83
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 29 deletions.
24 changes: 18 additions & 6 deletions packages/routes-gen/src/route.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import { route } from "./route";

it("supports optional params (issue 31)", () => {
expect(route("/reports/:type?/:id/", { id: "123" })).toEqual("/reports/123/");
expect(route('/special?/foo')).toEqual('/foo');
expect(route('/special?/foo', undefined, true)).toEqual('/special/foo');

expect(route("/reports/:type?/:id/", { id: "123", type: "annual" })).toEqual(
"/reports/annual/123/"
expect(route("/reports/:type?/:id", { id: "123" })).toEqual("/reports/123");

expect(route("/reports/:type?/:id", { id: "123", type: "annual" })).toEqual(
"/reports/annual/123"
);


expect(route("/reports/:id/:type?", { id: "123" })).toEqual("/reports/123");

expect(route("/reports/:id/:type?", { id: "123", type: "annual" })).toEqual(
"/reports/123/annual"
);

expect(route("/reports/:id/:type?/", { id: "123" })).toEqual("/reports/123/");
expect(route("/categories?/:category?/products")).toEqual(
"/products"
);

expect(route("/reports/:id/:type?/", { id: "123", type: "annual" })).toEqual(
"/reports/123/annual/"
expect(route("/categories?/:category?/products", { category: 'tees'})).toEqual(
"/categories/tees/products"
);
});

Expand Down
70 changes: 47 additions & 23 deletions packages/routes-gen/src/route.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,52 @@
export function route<T extends string>(
path: T,
params?: Record<string, any>
): T {
if (params) {
const segments = path.split(/\/+/).map((segment) => {
if (segment.startsWith(":")) {
const key = segment.replace(":", "").replace("?", "");

if (key in params) {
return params[key];
}
export function route<T extends string>(path: T, params: Record<string, any> = {}, includeOptional?: boolean): T {
// If the path doesn't contain any optional segments (marked with '?') or parameter placeholders (marked with ':'),
// return the path as it is.
if (!path.includes('?') && !path.includes(':')) {
return path;
}

// If the segment is optional and it doesn't exist in params, return null to omit it from the resulting path
if (segment.endsWith("?")) {
return null;
}
}
// Initialize an empty string to build the real path.
let realPath = "";
// Start from the end of the path.
let currentIndex = path.length;
// This flag indicates whether the last segment of the path had a parameter.
let lastSegmentHadParam = false;

return segment;
});
// Iterate through each segment of the path from the end to the beginning.
while (currentIndex > 0) {
// Find the index of the start of the current segment.
const startSegmentIndex = path.lastIndexOf('/', currentIndex);
// Extract the current segment from the path.
const segment = path.slice(startSegmentIndex, currentIndex + 1);
// Update the current index to the start of the current segment.
currentIndex = startSegmentIndex - 1;

// Filter out any null/undefined segments and join remaining segments
return segments.filter((value) => value != null).join("/") as T;
// If the segment is a parameter (starts with '/:').
if (segment.startsWith('/:')) {
// Extract the parameter name, considering optional parameters (ending with '?').
const paramName = segment.endsWith('?') ? segment.slice(2, -1) : segment.slice(2);
// Get the value of the parameter from the provided params object.
const paramValue = params[paramName];
// If the parameter value is defined, add it to the real path.
if (paramValue !== undefined) {
lastSegmentHadParam = true;
realPath = `/${paramValue}` + realPath;
}
}
// If the segment is optional (ends with '?').
else if (segment.endsWith('?')) {
// Include the segment in the real path if the last segment had a parameter or if includeOptional is true.
if (lastSegmentHadParam || includeOptional) {
realPath = segment.slice(0, -1) + realPath;
}
}
// If the segment is neither a parameter nor optional.
else {
lastSegmentHadParam = false;
realPath = segment + realPath;
}
}

return path;
}
// Return the constructed real path with the same type as the input path.
return realPath as T;
}

0 comments on commit 93ffc83

Please sign in to comment.