Skip to content

Commit

Permalink
HotFix: Uri template not correctly built when using @autoRoute (#4155)
Browse files Browse the repository at this point in the history
fix #4153
  • Loading branch information
timotheeguerin authored Aug 13, 2024
1 parent dda9db6 commit 9e1937c
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: fix
packages:
- "@typespec/http"
- "@typespec/rest"
---

HotFix: Uri template not correctly built when using `@autoRoute`
1 change: 1 addition & 0 deletions eng/common/pipelines/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pr:
branches:
include:
- main
- release/*

extends:
template: /eng/common/pipelines/templates/1es-redirect.yml
Expand Down
25 changes: 16 additions & 9 deletions packages/http/src/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
HttpOperation,
HttpOperationParameter,
HttpOperationParameters,
HttpOperationPathParameter,
PathParameterOptions,
RouteOptions,
RoutePath,
Expand Down Expand Up @@ -223,24 +224,30 @@ const styleToOperator: Record<PathParameterOptions["style"], string> = {
fragment: "#",
};

function addOperationTemplateToUriTemplate(uriTemplate: string, params: HttpOperationParameter[]) {
const pathParams = params
.filter((x) => x.type === "path")
.map((param) => {
const operator = param.allowReserved ? "+" : styleToOperator[param.style];
return `{${operator}${param.name}${param.explode ? "*" : ""}}`;
});
export function getUriTemplatePathParam(param: HttpOperationPathParameter) {
const operator = param.allowReserved ? "+" : styleToOperator[param.style];
return `{${operator}${param.name}${param.explode ? "*" : ""}}`;
}

export function addQueryParamsToUriTemplate(uriTemplate: string, params: HttpOperationParameter[]) {
const queryParams = params.filter((x) => x.type === "query");

const pathPart = joinPathSegments([uriTemplate, ...pathParams]);
return (
pathPart +
uriTemplate +
(queryParams.length > 0
? `{?${queryParams.map((x) => escapeUriTemplateParamName(x.name)).join(",")}}`
: "")
);
}

function addOperationTemplateToUriTemplate(uriTemplate: string, params: HttpOperationParameter[]) {
const pathParams = params.filter((x) => x.type === "path").map(getUriTemplatePathParam);
const queryParams = params.filter((x) => x.type === "query");

const pathPart = joinPathSegments([uriTemplate, ...pathParams]);
return addQueryParamsToUriTemplate(pathPart, queryParams);
}

function escapeUriTemplateParamName(name: string) {
return name.replaceAll(":", "%3A");
}
Expand Down
10 changes: 7 additions & 3 deletions packages/rest/src/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import {
Type,
} from "@typespec/compiler";
import {
addQueryParamsToUriTemplate,
DefaultRouteProducer,
getOperationParameters,
getOperationVerb,
getRoutePath,
getRouteProducer,
getUriTemplatePathParam,
HttpOperation,
HttpOperationParameter,
HttpOperationParameters,
Expand Down Expand Up @@ -119,7 +121,7 @@ function autoRouteProducer(
);

for (const httpParam of parameters.parameters) {
const { type, param, name } = httpParam;
const { type, param } = httpParam;
if (type === "path") {
addSegmentFragment(program, param, segments);

Expand All @@ -137,7 +139,7 @@ function autoRouteProducer(
segments.push(`/${param.type.value}`);
continue; // Skip adding to the parameter list
} else {
segments.push(`/{${name}}`);
segments.push(`/${getUriTemplatePathParam(httpParam)}`);
}
}
}
Expand All @@ -155,8 +157,10 @@ function autoRouteProducer(
// Add the operation's action segment if present
addActionFragment(program, operation, segments);

const pathPart = joinPathSegments(segments);

return diagnostics.wrap({
uriTemplate: joinPathSegments(segments),
uriTemplate: addQueryParamsToUriTemplate(pathPart, filteredParameters),
parameters: {
...parameters,
parameters: filteredParameters,
Expand Down
28 changes: 27 additions & 1 deletion packages/rest/test/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ModelProperty, Operation } from "@typespec/compiler";
import { expectDiagnostics } from "@typespec/compiler/testing";
import { isSharedRoute } from "@typespec/http";
import { deepStrictEqual, strictEqual } from "assert";
import { describe, it } from "vitest";
import { describe, expect, it } from "vitest";
import {
compileOperations,
createRestTestRunner,
Expand Down Expand Up @@ -521,3 +521,29 @@ describe("rest: routes", () => {
]);
});
});

describe("uri template", () => {
async function getOp(code: string) {
const ops = await getOperations(code);
return ops[0];
}

describe("build uriTemplate from parameter", () => {
it.each([
["@path one: string", "/foo/{one}"],
["@path(#{allowReserved: true}) one: string", "/foo/{+one}"],
["@path(#{explode: true}) one: string", "/foo/{one*}"],
[`@path(#{style: "matrix"}) one: string`, "/foo/{;one}"],
[`@path(#{style: "label"}) one: string`, "/foo/{.one}"],
[`@path(#{style: "fragment"}) one: string`, "/foo/{#one}"],
[`@path(#{style: "path"}) one: string`, "/foo/{/one}"],
["@path(#{allowReserved: true, explode: true}) one: string", "/foo/{+one*}"],
["@query one: string", "/foo{?one}"],
// cspell:ignore Atwo
[`@query("one:two") one: string`, "/foo{?one%3Atwo}"],
])("%s -> %s", async (param, expectedUri) => {
const op = await getOp(`@route("/foo") interface Test {@autoRoute op foo(${param}): void;}`);
expect(op.uriTemplate).toEqual(expectedUri);
});
});
});

0 comments on commit 9e1937c

Please sign in to comment.