Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions packages/fresh/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,26 @@ export class UrlPatternRouter<T> implements Router<T> {
pattern: null,
};

const staticMatch = this.#statics.get(url.pathname);
let pathname = url.pathname;
let staticMatch = this.#statics.get(pathname);

// Try alternate trailing slash form if no exact match found.
// Routes may be registered with or without trailing slashes,
// and requests may arrive in either form (e.g. when using
// trailingSlashes("always")).
if (staticMatch === undefined && pathname !== "/") {
const alt = pathname.endsWith("/")
? pathname.slice(0, -1)
: pathname + "/";
const altMatch = this.#statics.get(alt);
if (altMatch !== undefined) {
staticMatch = altMatch;
pathname = alt;
}
}

if (staticMatch !== undefined) {
result.pattern = url.pathname;
result.pattern = pathname;

let handlers = staticMatch.byMethod[method];
if (method === "HEAD" && handlers.length === 0) {
Expand Down
72 changes: 72 additions & 0 deletions packages/fresh/src/router_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,78 @@ Deno.test("UrlPatternRouter - wrong + correct method", () => {
});
});

Deno.test("UrlPatternRouter - trailing slash matches route without slash", () => {
const router = new UrlPatternRouter();
const A = () => {};
router.add("GET", "/wissen", [A]);

const res = router.match("GET", new URL("/wissen/", "http://localhost"));
expect(res).toEqual({
params: Object.create(null),
handlers: [A],
methodMatch: true,
pattern: "/wissen",
});
});

Deno.test("UrlPatternRouter - no trailing slash matches route with slash", () => {
const router = new UrlPatternRouter();
const A = () => {};
router.add("GET", "/wissen/", [A]);

const res = router.match("GET", new URL("/wissen", "http://localhost"));
expect(res).toEqual({
params: Object.create(null),
handlers: [A],
methodMatch: true,
pattern: "/wissen/",
});
});

Deno.test("UrlPatternRouter - exact match takes priority over trailing slash fallback", () => {
const router = new UrlPatternRouter();
const A = () => {};
const B = () => {};
router.add("GET", "/wissen", [A]);
router.add("GET", "/wissen/", [B]);

const withSlash = router.match(
"GET",
new URL("/wissen/", "http://localhost"),
);
expect(withSlash).toEqual({
params: Object.create(null),
handlers: [B],
methodMatch: true,
pattern: "/wissen/",
});

const withoutSlash = router.match(
"GET",
new URL("/wissen", "http://localhost"),
);
expect(withoutSlash).toEqual({
params: Object.create(null),
handlers: [A],
methodMatch: true,
pattern: "/wissen",
});
});

Deno.test("UrlPatternRouter - root trailing slash does not double-match", () => {
const router = new UrlPatternRouter();
const A = () => {};
router.add("GET", "/", [A]);

const res = router.match("GET", new URL("/", "http://localhost"));
expect(res).toEqual({
params: Object.create(null),
handlers: [A],
methodMatch: true,
pattern: "/",
});
});

Deno.test("UrlPatternRouter - convert patterns automatically", () => {
const router = new UrlPatternRouter();
const A = () => {};
Expand Down
Loading