Skip to content

Commit 9e1937c

Browse files
HotFix: Uri template not correctly built when using @autoRoute (#4155)
fix #4153
1 parent dda9db6 commit 9e1937c

File tree

5 files changed

+60
-13
lines changed

5 files changed

+60
-13
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
3+
changeKind: fix
4+
packages:
5+
- "@typespec/http"
6+
- "@typespec/rest"
7+
---
8+
9+
HotFix: Uri template not correctly built when using `@autoRoute`

eng/common/pipelines/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pr:
77
branches:
88
include:
99
- main
10+
- release/*
1011

1112
extends:
1213
template: /eng/common/pipelines/templates/1es-redirect.yml

packages/http/src/route.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
HttpOperation,
1616
HttpOperationParameter,
1717
HttpOperationParameters,
18+
HttpOperationPathParameter,
1819
PathParameterOptions,
1920
RouteOptions,
2021
RoutePath,
@@ -223,24 +224,30 @@ const styleToOperator: Record<PathParameterOptions["style"], string> = {
223224
fragment: "#",
224225
};
225226

226-
function addOperationTemplateToUriTemplate(uriTemplate: string, params: HttpOperationParameter[]) {
227-
const pathParams = params
228-
.filter((x) => x.type === "path")
229-
.map((param) => {
230-
const operator = param.allowReserved ? "+" : styleToOperator[param.style];
231-
return `{${operator}${param.name}${param.explode ? "*" : ""}}`;
232-
});
227+
export function getUriTemplatePathParam(param: HttpOperationPathParameter) {
228+
const operator = param.allowReserved ? "+" : styleToOperator[param.style];
229+
return `{${operator}${param.name}${param.explode ? "*" : ""}}`;
230+
}
231+
232+
export function addQueryParamsToUriTemplate(uriTemplate: string, params: HttpOperationParameter[]) {
233233
const queryParams = params.filter((x) => x.type === "query");
234234

235-
const pathPart = joinPathSegments([uriTemplate, ...pathParams]);
236235
return (
237-
pathPart +
236+
uriTemplate +
238237
(queryParams.length > 0
239238
? `{?${queryParams.map((x) => escapeUriTemplateParamName(x.name)).join(",")}}`
240239
: "")
241240
);
242241
}
243242

243+
function addOperationTemplateToUriTemplate(uriTemplate: string, params: HttpOperationParameter[]) {
244+
const pathParams = params.filter((x) => x.type === "path").map(getUriTemplatePathParam);
245+
const queryParams = params.filter((x) => x.type === "query");
246+
247+
const pathPart = joinPathSegments([uriTemplate, ...pathParams]);
248+
return addQueryParamsToUriTemplate(pathPart, queryParams);
249+
}
250+
244251
function escapeUriTemplateParamName(name: string) {
245252
return name.replaceAll(":", "%3A");
246253
}

packages/rest/src/rest.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ import {
1212
Type,
1313
} from "@typespec/compiler";
1414
import {
15+
addQueryParamsToUriTemplate,
1516
DefaultRouteProducer,
1617
getOperationParameters,
1718
getOperationVerb,
1819
getRoutePath,
1920
getRouteProducer,
21+
getUriTemplatePathParam,
2022
HttpOperation,
2123
HttpOperationParameter,
2224
HttpOperationParameters,
@@ -119,7 +121,7 @@ function autoRouteProducer(
119121
);
120122

121123
for (const httpParam of parameters.parameters) {
122-
const { type, param, name } = httpParam;
124+
const { type, param } = httpParam;
123125
if (type === "path") {
124126
addSegmentFragment(program, param, segments);
125127

@@ -137,7 +139,7 @@ function autoRouteProducer(
137139
segments.push(`/${param.type.value}`);
138140
continue; // Skip adding to the parameter list
139141
} else {
140-
segments.push(`/{${name}}`);
142+
segments.push(`/${getUriTemplatePathParam(httpParam)}`);
141143
}
142144
}
143145
}
@@ -155,8 +157,10 @@ function autoRouteProducer(
155157
// Add the operation's action segment if present
156158
addActionFragment(program, operation, segments);
157159

160+
const pathPart = joinPathSegments(segments);
161+
158162
return diagnostics.wrap({
159-
uriTemplate: joinPathSegments(segments),
163+
uriTemplate: addQueryParamsToUriTemplate(pathPart, filteredParameters),
160164
parameters: {
161165
...parameters,
162166
parameters: filteredParameters,

packages/rest/test/routes.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ModelProperty, Operation } from "@typespec/compiler";
22
import { expectDiagnostics } from "@typespec/compiler/testing";
33
import { isSharedRoute } from "@typespec/http";
44
import { deepStrictEqual, strictEqual } from "assert";
5-
import { describe, it } from "vitest";
5+
import { describe, expect, it } from "vitest";
66
import {
77
compileOperations,
88
createRestTestRunner,
@@ -521,3 +521,29 @@ describe("rest: routes", () => {
521521
]);
522522
});
523523
});
524+
525+
describe("uri template", () => {
526+
async function getOp(code: string) {
527+
const ops = await getOperations(code);
528+
return ops[0];
529+
}
530+
531+
describe("build uriTemplate from parameter", () => {
532+
it.each([
533+
["@path one: string", "/foo/{one}"],
534+
["@path(#{allowReserved: true}) one: string", "/foo/{+one}"],
535+
["@path(#{explode: true}) one: string", "/foo/{one*}"],
536+
[`@path(#{style: "matrix"}) one: string`, "/foo/{;one}"],
537+
[`@path(#{style: "label"}) one: string`, "/foo/{.one}"],
538+
[`@path(#{style: "fragment"}) one: string`, "/foo/{#one}"],
539+
[`@path(#{style: "path"}) one: string`, "/foo/{/one}"],
540+
["@path(#{allowReserved: true, explode: true}) one: string", "/foo/{+one*}"],
541+
["@query one: string", "/foo{?one}"],
542+
// cspell:ignore Atwo
543+
[`@query("one:two") one: string`, "/foo{?one%3Atwo}"],
544+
])("%s -> %s", async (param, expectedUri) => {
545+
const op = await getOp(`@route("/foo") interface Test {@autoRoute op foo(${param}): void;}`);
546+
expect(op.uriTemplate).toEqual(expectedUri);
547+
});
548+
});
549+
});

0 commit comments

Comments
 (0)