Fix don't emit shared route error when verb don't match (#2948)

fix [#2925](https://github.com/microsoft/typespec/issues/2925)

Stop emitting the error if there is a shared route and a non shared
route on a different verb.

Also improve the error: 
- change message to be a little more clear
- emit the error on every offending operation not just the first one we
find an duplicate

---------

Co-authored-by: Brian Terlson <brian.terlson@microsoft.com>
This commit is contained in:
Timothee Guerin 2024-02-28 14:17:46 -08:00 коммит произвёл GitHub
Родитель c9c1f3e442
Коммит 9d8cfb016e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
4 изменённых файлов: 75 добавлений и 15 удалений

Просмотреть файл

@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: fix
packages:
- "@typespec/http"
---
Fix don't emit shared route error when verb don't match

Просмотреть файл

@ -102,7 +102,7 @@ export const $lib = createTypeSpecLibrary({
"shared-inconsistency": { "shared-inconsistency": {
severity: "error", severity: "error",
messages: { messages: {
default: "All shared routes must agree on the value of the shared parameter.", default: paramMessage`Each operation routed at "${"verb"} ${"path"}" needs to have the @sharedRoute decorator.`,
}, },
}, },
"write-visibility-not-supported": { "write-visibility-not-supported": {

Просмотреть файл

@ -2,6 +2,7 @@ import { Program } from "@typespec/compiler";
import { reportDiagnostic } from "./lib.js"; import { reportDiagnostic } from "./lib.js";
import { getAllHttpServices } from "./operations.js"; import { getAllHttpServices } from "./operations.js";
import { isSharedRoute } from "./route.js"; import { isSharedRoute } from "./route.js";
import { HttpOperation, HttpService } from "./types.js";
export function $onValidate(program: Program) { export function $onValidate(program: Program) {
// Pass along any diagnostics that might be returned from the HTTP library // Pass along any diagnostics that might be returned from the HTTP library
@ -9,19 +10,53 @@ export function $onValidate(program: Program) {
if (diagnostics.length > 0) { if (diagnostics.length > 0) {
program.reportDiagnostics(diagnostics); program.reportDiagnostics(diagnostics);
} }
validateSharedRouteConsistency(program, services);
}
function groupHttpOperations(
operations: HttpOperation[]
): Map<string, Map<string, HttpOperation[]>> {
const paths = new Map<string, Map<string, HttpOperation[]>>();
for (const operation of operations) {
const { verb, path } = operation;
let pathOps = paths.get(path);
if (pathOps === undefined) {
pathOps = new Map<string, HttpOperation[]>();
paths.set(path, pathOps);
}
const ops = pathOps.get(verb);
if (ops === undefined) {
pathOps.set(verb, [operation]);
} else {
ops.push(operation);
}
}
return paths;
}
function validateSharedRouteConsistency(program: Program, services: HttpService[]) {
for (const service of services) { for (const service of services) {
const paths = new Map<string, boolean>(); const paths = groupHttpOperations(service.operations);
for (const operation of service.operations) { for (const pathOps of paths.values()) {
const path = operation.path; for (const ops of pathOps.values()) {
const shared = isSharedRoute(program, operation.operation); let hasShared = false;
const val = paths.get(path); let hasNonShared = false;
if (shared && val === undefined) { for (const op of ops) {
paths.set(path, shared); if (isSharedRoute(program, op.operation)) {
} else if (val && val !== shared) { hasShared = true;
reportDiagnostic(program, { } else {
code: "shared-inconsistency", hasNonShared = true;
target: operation.operation, }
}); }
if (hasShared && hasNonShared) {
for (const op of ops) {
reportDiagnostic(program, {
code: "shared-inconsistency",
target: op.operation,
format: { verb: op.verb, path: op.path },
});
}
}
} }
} }
} }

Просмотреть файл

@ -260,7 +260,7 @@ describe("http: decorators", () => {
]); ]);
}); });
it("emit diagnostics when not all duplicated routes are declared shared", async () => { it("emit diagnostics when not all duplicated routes are declared shared on each op conflicting", async () => {
const diagnostics = await runner.diagnose(` const diagnostics = await runner.diagnose(`
@route("/test") @sharedRoute op test(): string; @route("/test") @sharedRoute op test(): string;
@route("/test") @sharedRoute op test2(): string; @route("/test") @sharedRoute op test2(): string;
@ -269,7 +269,15 @@ describe("http: decorators", () => {
expectDiagnostics(diagnostics, [ expectDiagnostics(diagnostics, [
{ {
code: "@typespec/http/shared-inconsistency", code: "@typespec/http/shared-inconsistency",
message: `All shared routes must agree on the value of the shared parameter.`, message: `Each operation routed at "get /test" needs to have the @sharedRoute decorator.`,
},
{
code: "@typespec/http/shared-inconsistency",
message: `Each operation routed at "get /test" needs to have the @sharedRoute decorator.`,
},
{
code: "@typespec/http/shared-inconsistency",
message: `Each operation routed at "get /test" needs to have the @sharedRoute decorator.`,
}, },
]); ]);
}); });
@ -283,6 +291,15 @@ describe("http: decorators", () => {
expectDiagnosticEmpty(diagnostics); expectDiagnosticEmpty(diagnostics);
}); });
it("do not emit diagnostics routes sharing path but not same verb", async () => {
const diagnostics = await runner.diagnose(`
@route("/test") @sharedRoute op test(): string;
@route("/test") @sharedRoute op test2(): string;
@route("/test") @post op test3(): string;
`);
expectDiagnosticEmpty(diagnostics);
});
it("emit diagnostic when wrong type for shared is provided", async () => { it("emit diagnostic when wrong type for shared is provided", async () => {
const diagnostics = await runner.diagnose(` const diagnostics = await runner.diagnose(`
@route("/test", {shared: "yes"}) op test(): string; @route("/test", {shared: "yes"}) op test(): string;