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:
Родитель
c9c1f3e442
Коммит
9d8cfb016e
|
@ -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;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче