diff --git a/.chronus/changes/copilot-fix-regression-http-1-10-1-2026-2-18-18-33-49.md b/.chronus/changes/copilot-fix-regression-http-1-10-1-2026-2-18-18-33-49.md new file mode 100644 index 00000000000..0d220f1033f --- /dev/null +++ b/.chronus/changes/copilot-fix-regression-http-1-10-1-2026-2-18-18-33-49.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/http" +--- + +Fix route joining to preserve trailing `/` when it would not result in `//` \ No newline at end of file diff --git a/packages/http/src/route.ts b/packages/http/src/route.ts index 6a49a332498..2360d77312c 100644 --- a/packages/http/src/route.ts +++ b/packages/http/src/route.ts @@ -36,22 +36,25 @@ function needsSlashPrefix(fragment: string) { ); } -function normalizeFragment(fragment: string, trimLast = false) { +function normalizeFragment(fragment: string) { if (needsSlashPrefix(fragment)) { // Insert the default separator fragment = `/${fragment}`; } - if (trimLast && fragment[fragment.length - 1] === "/") { - return fragment.slice(0, -1); - } return fragment; } export function joinPathSegments(rest: string[]) { let current = ""; - for (const [index, segment] of rest.entries()) { - current += normalizeFragment(segment, index < rest.length - 1); + for (const segment of rest) { + const normalized = normalizeFragment(segment); + // Merge trailing and leading slashes to avoid double slashes + if (current.endsWith("/") && normalized.startsWith("/")) { + current += normalized.slice(1); + } else { + current += normalized; + } } return current; } diff --git a/packages/http/test/routes.test.ts b/packages/http/test/routes.test.ts index bdb1e60ac5f..39cec8801f5 100644 --- a/packages/http/test/routes.test.ts +++ b/packages/http/test/routes.test.ts @@ -346,6 +346,25 @@ describe("http: routes", () => { deepStrictEqual(routes, [{ verb: "get", path: `/abc${sep}restype=container`, params: [] }]); }); + + it("keeps trailing / on parent route when joining with child route", async () => { + const routes = await getRoutesFor( + ` + @route("{blobName}/") + interface Container { + @put @route("${sep}some-query=true") op foo(blobName: string): void; + } + `, + ); + + deepStrictEqual(routes, [ + { + verb: "put", + path: `/{blobName}/${sep}some-query=true`, + params: ["blobName"], + }, + ]); + }); }); describe("joinPathSegments", () => {