Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions docs/post-response-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,21 @@ ARM OpenAPI (swagger) specs

## Related ARM Guideline Code

- RPC-Async-V1-11, RPC-Async-V1-14

## Output Message

Synchronous POST operations must have one of the following combinations of responses - 200 and default ; 204 and default. They also must not have other response codes.
Long-running POST operations must have responses with 202 and default return codes. They must also have a 200 return code if only if the final response is intended to have a schema, if not the 200 return code must not be specified. They also must not have other response codes.
- RPC-Async-V1-11, RPC-Async-V1-14, RPC-POST-V1-02, RPC-POST-V1-03

## Description

Synchronous POST operations must have one of the following combinations of responses - 200 and default ; 204 and default. They also must not have other response codes.
Long-running POST operations must have responses with 202 and default return codes. They must also have a 200 return code if only if the final response is intended to have a schema, if not the 200 return code must not be specified. They also must not have other response codes.
202 response for a LRO POST operation must not have a response schema specified.
Synchronous POST operations must only use 200 with a default response when a response schema is required, or 204 with a default response when no schema is needed. No other response codes are allowed.

Long-running POST (LRO) operations must initially return 202 with a default response and no schema. The final response must be 200 with a schema if one is required, or 204 with no schema if not. No other response codes are permitted.

## How to fix the violation

Synchronous POST operations must have one of the following combinations of responses - 200 and default ; 204 and default. They also must not have other response codes.

For Long-running POST operations:
1. Add responses with 202 and default return codes.
2. Add 200 response code if only if the final response is intended to have a schema.
2. Add 200 response code if only if the final response is intended to have a schema if not add 204 response code.
3. Ensure no other response codes are specified.
4. Make sure to define "x-ms-long-running-operation".
5. 202 response for a LRO POST operation must not have a response schema specified.
Expand Down Expand Up @@ -62,9 +57,6 @@ The following would be a valid SYNC POST:
"responses": {
"204": {
"description": "No Content",
"schema": {
"$ref": "#/definitions/FooResource"
}
},
"default": {
"description": "Error response describing why the operation failed.",
Expand Down Expand Up @@ -107,6 +99,9 @@ The following would be a valid ASYNC POST:
"202": {
"description": "Operation accepted",
},
"204": {
"description": "No Content",
},
"default": {
"description": "Error response describing why the operation failed.",
"schema": {
Expand Down
6 changes: 3 additions & 3 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -938,9 +938,9 @@ Please refer to [post-operation-id-contains-url-verb.md](./post-operation-id-con

### PostResponseCodes

Synchronous POST operations must have one of the following combinations of responses - 200 and default ; 204 and default. They also must not have other response codes.
Long-running POST operations must have responses with 202 and default return codes. They must also have a 200 return code if only if the final response is intended to have a schema, if not the 200 return code must not be specified. They also must not have other response codes.
202 response for a LRO POST operation must not have a response schema specified.
Synchronous POST operations must only use 200 with a default response when a response schema is required, or 204 with a default response when no schema is needed. No other response codes are allowed.

Long-running POST (LRO) operations must initially return 202 with a default response and no schema. The final response must be 200 with a schema if one is required, or 204 with no schema if not. No other response codes are permitted.

Please refer to [post-response-codes.md](./post-response-codes.md) for details.

Expand Down
82 changes: 42 additions & 40 deletions packages/rulesets/generated/spectral/az-arm.js
Original file line number Diff line number Diff line change
Expand Up @@ -2298,14 +2298,19 @@ const pathSegmentCasing = (apiPaths, _opts, paths) => {

const SYNC_POST_RESPONSES_OK = ["200", "default"];
const SYNC_POST_RESPONSES_NO_CONTENT = ["204", "default"];
const LR_POST_RESPONSES_WITH_FINAL_SCHEMA = ["200", "202", "default"];
const LR_POST_RESPONSES_NO_FINAL_SCHEMA = ["202", "default"];
const SYNC_ERROR = "Synchronous POST operations must have one of the following combinations of responses - 200 and default ; 204 and default. They also must not have other response codes.";
const LR_ERROR = "Long-running POST operations must have responses with 202 and default return codes. They must also have a 200 return code if only if the final response is intended to have a schema, if not the 200 return code must not be specified. They also must not have other response codes.";
const LR_NO_SCHEMA_ERROR = "200 return code does not have a schema specified. LRO POST must have a 200 return code if only if the final response is intended to have a schema, if not the 200 return code must not be specified.";
const LR_POST_RESPONSES_OK = ["202", "200", "default"];
const LR_POST_RESPONSES_NO_CONTENT = ["202", "204", "default"];
const HTTP_STATUS_CODE_OK = "200";
const HTTP_STATUS_CODE_ACCEPTED = "202";
const HTTP_STATUS_CODE_NO_CONTENT = "204";
const SYNC_ERROR = "Synchronous POST operations must have one of the following combinations of responses - 200 and default ; 204 and default. No other response codes are permitted.";
const LR_ERROR = "Long-running POST operations must initially return 202 with a default response and no schema. The final response must be 200 with a schema if one is required, or 204 with no schema if not. No other response codes are permitted.";
const LR_NO_SCHEMA_ERROR_OK = "200 return code does not have a schema specified. LRO POST must have a 200 return code if only if the final response is intended to have a schema, if not the 200 return code must not be specified.";
const LR_SCHEMA_ERROR_ACCEPTED = "202 response for a LRO POST operation must not have a response schema specified.";
const LR_SCHEMA_ERROR_NO_CONTENT = "204 response for a Sync/LRO POST operation must not have a response schema specified.";
const EmptyResponse_ERROR$1 = "POST operation response codes must be non-empty. Synchronous POST operation must have response codes 200 and default or 204 and default. LRO POST operations must have response codes 202 and default. They must also have a 200 return code if only if the final response is intended to have a schema, if not the 200 return code must not be specified.";
const PostResponseCodes = (postOp, _opts, ctx) => {
var _a, _b, _c;
var _a, _b, _c, _d;
if (postOp === null || typeof postOp !== "object") {
return [];
}
Expand All @@ -2319,61 +2324,58 @@ const PostResponseCodes = (postOp, _opts, ctx) => {
});
return errors;
}
const isAsyncOperation = postOp.responses["202"] ||
const isAsyncOperation = postOp.responses[HTTP_STATUS_CODE_ACCEPTED] ||
(postOp["x-ms-long-running-operation"] && postOp["x-ms-long-running-operation"] === true) ||
postOp["x-ms-long-running-operation-options"];
Comment thread
AkhilaIlla marked this conversation as resolved.
Comment thread
AkhilaIlla marked this conversation as resolved.
if (isAsyncOperation) {
let wrongResponseCodes = false;
let okResponseCodeNoSchema = false;
if (!postOp["x-ms-long-running-operation"] || postOp["x-ms-long-running-operation"] !== true) {
errors.push({
message: "An async POST operation must set '\"x-ms-long-running-operation\" : true'.",
path: path,
});
return errors;
}
if (responses.length === LR_POST_RESPONSES_WITH_FINAL_SCHEMA.length) {
if (!LR_POST_RESPONSES_WITH_FINAL_SCHEMA.every((value) => responses.includes(value))) {
wrongResponseCodes = true;
}
else if (!((_b = postOp.responses["200"]) === null || _b === void 0 ? void 0 : _b.schema)) {
okResponseCodeNoSchema = true;
}
}
else if (responses.length !== LR_POST_RESPONSES_NO_FINAL_SCHEMA.length ||
!LR_POST_RESPONSES_NO_FINAL_SCHEMA.every((value) => responses.includes(value))) {
wrongResponseCodes = true;
}
if ((_c = postOp.responses["202"]) === null || _c === void 0 ? void 0 : _c.schema) {
errors.push({
message: "202 response for a LRO POST operation must not have a response schema specified.",
path: path,
});
}
if (wrongResponseCodes) {
const respSet = new Set(responses);
const setEquals = (target) => target.length === respSet.size && target.every((v) => respSet.has(v));
const matchesOk = setEquals(LR_POST_RESPONSES_OK);
const matchesNoContent = setEquals(LR_POST_RESPONSES_NO_CONTENT);
if (!(matchesOk || matchesNoContent)) {
errors.push({
message: LR_ERROR,
path: path,
});
}
else if (okResponseCodeNoSchema) {
errors.push({
message: LR_NO_SCHEMA_ERROR,
path: path,
});
}
return errors;
}
else {
if (responses.length !== SYNC_POST_RESPONSES_OK.length ||
(!SYNC_POST_RESPONSES_OK.every((value) => responses.includes(value)) &&
!SYNC_POST_RESPONSES_NO_CONTENT.every((value) => responses.includes(value)))) {
const responseSet = new Set(responses);
const setEquals = (target) => target.length === responseSet.size && target.every((v) => responseSet.has(v));
const matchesOk = setEquals(SYNC_POST_RESPONSES_OK);
const matchesNoContent = setEquals(SYNC_POST_RESPONSES_NO_CONTENT);
if (!(matchesOk || matchesNoContent)) {
errors.push({
message: SYNC_ERROR,
path: path,
});
}
}
if (postOp.responses[HTTP_STATUS_CODE_OK] && !((_b = postOp.responses[HTTP_STATUS_CODE_OK]) === null || _b === void 0 ? void 0 : _b.schema)) {
errors.push({
message: LR_NO_SCHEMA_ERROR_OK,
path: path,
});
}
if ((_c = postOp.responses[HTTP_STATUS_CODE_ACCEPTED]) === null || _c === void 0 ? void 0 : _c.schema) {
errors.push({
message: LR_SCHEMA_ERROR_ACCEPTED,
path: path,
});
}
if ((_d = postOp.responses[HTTP_STATUS_CODE_NO_CONTENT]) === null || _d === void 0 ? void 0 : _d.schema) {
errors.push({
message: LR_SCHEMA_ERROR_NO_CONTENT,
path: path,
});
}
return errors;
};

Expand Down Expand Up @@ -3333,8 +3335,8 @@ const ruleset = {
},
},
PostResponseCodes: {
rpcGuidelineCode: "RPC-Async-V1-11, RPC-Async-V1-14",
description: "Synchronous POST must have either 200 or 204 return codes and LRO POST must have 202 return code. LRO POST should also have a 200 return code only if the final response is intended to have a schema",
rpcGuidelineCode: "RPC-Async-V1-11, RPC-Async-V1-14, RPC-POST-V1-02, RPC-POST-V1-03",
description: "Synchronous POST must return 200 when a response body is required or 204 when no body is needed; LRO POST must initially return 202, with the final response returning 200 if a body is expected or 204 if not.",
severity: "error",
disableForTypeSpecDataPlane: true,
disableForTypeSpecDataPlaneReason: "Covered by TSP's '@azure-tools/typespec-azure-resource-manager/arm-post-operation-response-codes' rule.",
Expand Down
5 changes: 2 additions & 3 deletions packages/rulesets/src/spectral/az-arm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,8 @@ const ruleset: any = {

// RPC Code: RPC-Async-V1-11, RPC-Async-V1-14
PostResponseCodes: {
rpcGuidelineCode: "RPC-Async-V1-11, RPC-Async-V1-14",
description:
"Synchronous POST must have either 200 or 204 return codes and LRO POST must have 202 return code. LRO POST should also have a 200 return code only if the final response is intended to have a schema",
rpcGuidelineCode: "RPC-Async-V1-11, RPC-Async-V1-14, RPC-POST-V1-02, RPC-POST-V1-03",
description: "Synchronous POST must return 200 when a response body is required or 204 when no body is needed; LRO POST must initially return 202, with the final response returning 200 if a body is expected or 204 if not.",
severity: "error",
disableForTypeSpecDataPlane: true,
disableForTypeSpecDataPlaneReason:
Expand Down
88 changes: 45 additions & 43 deletions packages/rulesets/src/spectral/functions/post-response-codes.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
// Synchronous POST must have 200 & 204 return codes and LRO POST must have 200 & 202 return codes.
// RPC Code: RPC-Async-V1-11
// Synchronous POST must have 200 & 204 return codes and LRO POST must have 202 & 200 or 202 & 204 or 202, 200 & 204 return codes.

const SYNC_POST_RESPONSES_OK = ["200", "default"]
const SYNC_POST_RESPONSES_NO_CONTENT = ["204", "default"]
const LR_POST_RESPONSES_WITH_FINAL_SCHEMA = ["200", "202", "default"]
const LR_POST_RESPONSES_NO_FINAL_SCHEMA = ["202", "default"]
const LR_POST_RESPONSES_OK = ["202", "200", "default"]
const LR_POST_RESPONSES_NO_CONTENT = ["202", "204", "default"]
const HTTP_STATUS_CODE_OK = "200"
const HTTP_STATUS_CODE_ACCEPTED = "202"
const HTTP_STATUS_CODE_NO_CONTENT = "204"

const SYNC_ERROR =
"Synchronous POST operations must have one of the following combinations of responses - 200 and default ; 204 and default. They also must not have other response codes."
"Synchronous POST operations must have one of the following combinations of responses - 200 and default ; 204 and default. No other response codes are permitted."
const LR_ERROR =
"Long-running POST operations must have responses with 202 and default return codes. They must also have a 200 return code if only if the final response is intended to have a schema, if not the 200 return code must not be specified. They also must not have other response codes."
const LR_NO_SCHEMA_ERROR =
"Long-running POST operations must initially return 202 with a default response and no schema. The final response must be 200 with a schema if one is required, or 204 with no schema if not. No other response codes are permitted."
const LR_NO_SCHEMA_ERROR_OK =
"200 return code does not have a schema specified. LRO POST must have a 200 return code if only if the final response is intended to have a schema, if not the 200 return code must not be specified."
const LR_SCHEMA_ERROR_ACCEPTED = "202 response for a LRO POST operation must not have a response schema specified."
const LR_SCHEMA_ERROR_NO_CONTENT = "204 response for a Sync/LRO POST operation must not have a response schema specified."
Comment thread
AkhilaIlla marked this conversation as resolved.
const EmptyResponse_ERROR =
"POST operation response codes must be non-empty. Synchronous POST operation must have response codes 200 and default or 204 and default. LRO POST operations must have response codes 202 and default. They must also have a 200 return code if only if the final response is intended to have a schema, if not the 200 return code must not be specified."

Expand All @@ -33,14 +37,11 @@ export const PostResponseCodes = (postOp: any, _opts: any, ctx: any) => {
}

const isAsyncOperation =
postOp.responses["202"] ||
postOp.responses[HTTP_STATUS_CODE_ACCEPTED] ||
(postOp["x-ms-long-running-operation"] && postOp["x-ms-long-running-operation"] === true) ||
postOp["x-ms-long-running-operation-options"]

if (isAsyncOperation) {
let wrongResponseCodes = false
let okResponseCodeNoSchema = false

if (!postOp["x-ms-long-running-operation"] || postOp["x-ms-long-running-operation"] !== true) {
errors.push({
message: "An async POST operation must set '\"x-ms-long-running-operation\" : true'.",
Expand All @@ -49,51 +50,52 @@ export const PostResponseCodes = (postOp: any, _opts: any, ctx: any) => {
return errors
}

if (responses.length === LR_POST_RESPONSES_WITH_FINAL_SCHEMA.length) {
if (!LR_POST_RESPONSES_WITH_FINAL_SCHEMA.every((value) => responses.includes(value))) {
wrongResponseCodes = true
} else if (!postOp.responses["200"]?.schema) {
okResponseCodeNoSchema = true
}
} else if (
responses.length !== LR_POST_RESPONSES_NO_FINAL_SCHEMA.length ||
!LR_POST_RESPONSES_NO_FINAL_SCHEMA.every((value) => responses.includes(value))
) {
wrongResponseCodes = true
}

if (postOp.responses["202"]?.schema) {
errors.push({
message: "202 response for a LRO POST operation must not have a response schema specified.",
path: path,
})
}
const respSet = new Set(responses)
const setEquals = (target: string[]) => target.length === respSet.size && target.every((v) => respSet.has(v))
const matchesOk = setEquals(LR_POST_RESPONSES_OK)
const matchesNoContent = setEquals(LR_POST_RESPONSES_NO_CONTENT)

if (wrongResponseCodes) {
if (!(matchesOk || matchesNoContent)) {
errors.push({
message: LR_ERROR,
path: path,
})
} else if (okResponseCodeNoSchema) {
errors.push({
message: LR_NO_SCHEMA_ERROR,
path: path,
})
}

return errors
} else {
if (
responses.length !== SYNC_POST_RESPONSES_OK.length ||
(!SYNC_POST_RESPONSES_OK.every((value) => responses.includes(value)) &&
!SYNC_POST_RESPONSES_NO_CONTENT.every((value) => responses.includes(value)))
) {
const responseSet = new Set(responses)
const setEquals = (target: string[]) => target.length === responseSet.size && target.every((v) => responseSet.has(v))

const matchesOk = setEquals(SYNC_POST_RESPONSES_OK)
const matchesNoContent = setEquals(SYNC_POST_RESPONSES_NO_CONTENT)

if (!(matchesOk || matchesNoContent)) {
errors.push({
message: SYNC_ERROR,
path: path,
})
}
}

if (postOp.responses[HTTP_STATUS_CODE_OK] && !postOp.responses[HTTP_STATUS_CODE_OK]?.schema) {
errors.push({
message: LR_NO_SCHEMA_ERROR_OK,
path: path,
})
}

if (postOp.responses[HTTP_STATUS_CODE_ACCEPTED]?.schema) {
errors.push({
message: LR_SCHEMA_ERROR_ACCEPTED,
path: path,
})
}

if (postOp.responses[HTTP_STATUS_CODE_NO_CONTENT]?.schema) {
errors.push({
message: LR_SCHEMA_ERROR_NO_CONTENT,
path: path,
})
}

return errors
}
Loading
Loading