@azure-tools/typespec-azure-resource-manager
Version:
TypeSpec Azure Resource Manager library
97 lines • 4.51 kB
JavaScript
import { createRule } from "@typespec/compiler";
import { getLroMetadata } from "@azure-tools/typespec-azure-core";
import { getArmResources } from "../resource.js";
/**
* Verify that a post operation has the correct response codes.
*/
export const armPostResponseCodesRule = createRule({
name: "arm-post-operation-response-codes",
severity: "warning",
url: "https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/post-operation-response-codes",
description: "Ensure post operations have the appropriate status codes.",
messages: {
sync: `Synchronous post operations must have a 200 or 204 response and a default response. They must not have any other responses.`,
async: `Long-running post operations must have 202 and default responses. They must also have a 200 response if the final response has a schema. They must not have any other responses.`,
},
create(context) {
function getResponseBody(response) {
if (response === undefined)
return undefined;
if (response.responses.length > 1) {
throw new Error("Multiple responses are not supported.");
}
if (response.responses[0].body !== undefined) {
return response.responses[0].body;
}
return undefined;
}
function validateAsyncPost(op) {
const statusCodes = new Set([
...op.httpOperation.responses.map((r) => r.statusCodes.toString()),
]);
// validate that there are 202 and * status codes, and maybe 200
const expected = statusCodes.size === 2 ? new Set(["202", "*"]) : new Set(["202", "200", "*"]);
if (statusCodes.size !== expected.size || ![...statusCodes].every((v) => expected.has(v))) {
context.reportDiagnostic({
target: op.operation,
messageId: "async",
});
}
// validate that 202 does not have a schema
const response202 = op.httpOperation.responses.find((r) => r.statusCodes === 202);
const body202 = getResponseBody(response202);
if (body202 !== undefined) {
context.reportDiagnostic({
target: op.operation.returnType,
messageId: "async",
});
}
// validate that a 200 response does have a schema
const response200 = op.httpOperation.responses.find((r) => r.statusCodes === 200);
const body200 = getResponseBody(response200);
if (response200 && body200 === undefined) {
context.reportDiagnostic({
target: op.operation.returnType,
messageId: "async",
});
}
}
function validateSyncPost(op) {
const allowed = [new Set(["200", "*"]), new Set(["204", "*"])];
const statusCodes = new Set([
...op.httpOperation.responses.map((r) => r.statusCodes.toString()),
]);
if (!allowed.some((expected) => statusCodes.size === expected.size && [...statusCodes].every((v) => expected.has(v)))) {
context.reportDiagnostic({
target: op.operation,
messageId: "sync",
});
}
}
return {
root: (program) => {
const resources = getArmResources(program);
for (const resource of resources) {
const operations = [
resource.operations.lifecycle.createOrUpdate,
resource.operations.lifecycle.update,
...Object.values(resource.operations.actions),
];
for (const op of operations) {
if (op === undefined || op.httpOperation.verb !== "post") {
continue;
}
const isAsync = getLroMetadata(context.program, op.operation) !== undefined;
if (isAsync) {
validateAsyncPost(op);
}
else {
validateSyncPost(op);
}
}
}
},
};
},
});
//# sourceMappingURL=arm-post-response-codes.js.map