From b7e45f008ff7db2f2a2a2ce91c3d139bc596652e Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 22 Apr 2024 18:48:07 -0700 Subject: [PATCH] Fix `@path` param mapping when spreading a record in operation parameters (#3190) fix #3051 --- ...-param-mapping-record-2024-3-18-16-17-3.md | 8 ++++++ packages/http/src/lib.ts | 2 +- packages/http/src/metadata.ts | 2 +- packages/http/test/routes.test.ts | 27 +++++++++++++++++++ packages/http/test/test-host.ts | 8 ++++++ 5 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 .chronus/changes/fix-path-param-mapping-record-2024-3-18-16-17-3.md diff --git a/.chronus/changes/fix-path-param-mapping-record-2024-3-18-16-17-3.md b/.chronus/changes/fix-path-param-mapping-record-2024-3-18-16-17-3.md new file mode 100644 index 0000000000..288304c562 --- /dev/null +++ b/.chronus/changes/fix-path-param-mapping-record-2024-3-18-16-17-3.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: fix +packages: + - "@typespec/http" +--- + +Fix `@path` param mapping when spreading a record in operation parameters diff --git a/packages/http/src/lib.ts b/packages/http/src/lib.ts index fa1ff2f18a..8d8ed02b7a 100644 --- a/packages/http/src/lib.ts +++ b/packages/http/src/lib.ts @@ -18,7 +18,7 @@ export const $lib = createTypeSpecLibrary({ "missing-path-param": { severity: "error", messages: { - default: paramMessage`Path contains parameter ${"param"} but wasn't found in given parameters`, + default: paramMessage`Route reference parameter '${"param"}' but wasn't found in operation parameters`, }, }, "optional-path-param": { diff --git a/packages/http/src/metadata.ts b/packages/http/src/metadata.ts index 2108d89a23..2732016353 100644 --- a/packages/http/src/metadata.ts +++ b/packages/http/src/metadata.ts @@ -235,7 +235,7 @@ export function gatherMetadata( rootMapOut?: Map ): Set { const metadata = new Map(); - if (type.kind !== "Model" || type.indexer || type.properties.size === 0) { + if (type.kind !== "Model" || type.properties.size === 0) { return new Set(); } diff --git a/packages/http/test/routes.test.ts b/packages/http/test/routes.test.ts index 055c05688f..d5bdd454c4 100644 --- a/packages/http/test/routes.test.ts +++ b/packages/http/test/routes.test.ts @@ -6,6 +6,7 @@ import { HttpOperation, getRoutePath } from "../src/index.js"; import { compileOperations, createHttpTestRunner, + diagnoseOperations, getOperations, getRoutesFor, } from "./test-host.js"; @@ -150,6 +151,32 @@ describe("http: routes", () => { }); }); + describe("@route path parameters mapping", () => { + it("maps route interpolated params to the operation param", async () => { + const routes = await getRoutesFor( + `@route("/foo/{myParam}/") op test(@path myParam: string): void;` + ); + deepStrictEqual(routes, [{ verb: "get", path: "/foo/{myParam}", params: ["myParam"] }]); + }); + + it("maps route interpolated params to the operation param when operation spread items", async () => { + const routes = await getRoutesFor( + `@route("/foo/{myParam}/") op test(@path myParam: string, ...Record): void;` + ); + deepStrictEqual(routes, [{ verb: "post", path: "/foo/{myParam}", params: ["myParam"] }]); + }); + + it("emit diagnostic if interpolated param is missing in param list", async () => { + const diagnostics = await diagnoseOperations( + `@route("/foo/{myParam}/") op test(@path other: string): void;` + ); + expectDiagnostics(diagnostics, { + code: "@typespec/http/missing-path-param", + message: "Route reference parameter 'myParam' but wasn't found in operation parameters", + }); + }); + }); + describe("path parameters when no explicit @route", () => { it("uses the name of the parameter by default and wraps in {}", async () => { const routes = await getRoutesFor(`op test(@path myParam: string): void;`); diff --git a/packages/http/test/test-host.ts b/packages/http/test/test-host.ts index 04416003cd..0280b7588a 100644 --- a/packages/http/test/test-host.ts +++ b/packages/http/test/test-host.ts @@ -81,6 +81,14 @@ export async function compileOperations( return [details, diagnostics]; } +export async function diagnoseOperations( + code: string, + routeOptions?: RouteResolutionOptions +): Promise { + const [_, diagnostics] = await compileOperations(code, routeOptions); + return diagnostics; +} + export async function getOperationsWithServiceNamespace( code: string, routeOptions?: RouteResolutionOptions