From 4fb3ef7af5e6ff262a7e942288510e78a9288ef5 Mon Sep 17 00:00:00 2001 From: akhilailla Date: Fri, 15 Aug 2025 14:48:28 -0500 Subject: [PATCH 1/4] Tweak post response codes rule to honor ROC --- docs/post-response-codes.md | 27 +++---- docs/rules.md | 6 +- .../rulesets/generated/spectral/az-arm.js | 65 ++++++++--------- packages/rulesets/src/spectral/az-arm.ts | 5 +- .../spectral/functions/post-response-codes.ts | 71 +++++++++---------- .../spectral/test/post-response-codes.test.ts | 28 ++++---- 6 files changed, 95 insertions(+), 107 deletions(-) diff --git a/docs/post-response-codes.md b/docs/post-response-codes.md index 7d306fd76..75ddd81f1 100644 --- a/docs/post-response-codes.md +++ b/docs/post-response-codes.md @@ -10,18 +10,13 @@ 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 @@ -29,10 +24,10 @@ Synchronous POST operations must have one of the following combinations of respo 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. -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. +2. Add 200 response code if only if the final response is intended to have a schema if not add 204 response code. +1. Ensure no other response codes are specified. +1. Make sure to define "x-ms-long-running-operation". +1. 202 response for a LRO POST operation must not have a response schema specified. ### Example @@ -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.", @@ -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": { diff --git a/docs/rules.md b/docs/rules.md index 2213a7444..908136c13 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -956,9 +956,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. diff --git a/packages/rulesets/generated/spectral/az-arm.js b/packages/rulesets/generated/spectral/az-arm.js index d0a7ac15f..7f6aaeb47 100644 --- a/packages/rulesets/generated/spectral/az-arm.js +++ b/packages/rulesets/generated/spectral/az-arm.js @@ -2310,11 +2310,14 @@ 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 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 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; @@ -2331,12 +2334,10 @@ 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"]; 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'.", @@ -2344,42 +2345,38 @@ const PostResponseCodes = (postOp, _opts, ctx) => { }); 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) { + if (matchesOk) { + if (!((_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_NO_SCHEMA_ERROR, + message: LR_SCHEMA_ERROR_ACCEPTED, 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, @@ -3356,8 +3353,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.", diff --git a/packages/rulesets/src/spectral/az-arm.ts b/packages/rulesets/src/spectral/az-arm.ts index 209503627..acde3835f 100644 --- a/packages/rulesets/src/spectral/az-arm.ts +++ b/packages/rulesets/src/spectral/az-arm.ts @@ -192,9 +192,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: diff --git a/packages/rulesets/src/spectral/functions/post-response-codes.ts b/packages/rulesets/src/spectral/functions/post-response-codes.ts index eae39695e..a475fea86 100644 --- a/packages/rulesets/src/spectral/functions/post-response-codes.ts +++ b/packages/rulesets/src/spectral/functions/post-response-codes.ts @@ -1,17 +1,19 @@ -// 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 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 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." @@ -33,14 +35,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'.", @@ -49,45 +48,43 @@ 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 - } + 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 (postOp.responses["202"]?.schema) { + if (!(matchesOk || matchesNoContent)) { errors.push({ - message: "202 response for a LRO POST operation must not have a response schema specified.", + message: LR_ERROR, path: path, }) } - if (wrongResponseCodes) { - errors.push({ - message: LR_ERROR, - path: path, - }) - } else if (okResponseCodeNoSchema) { + if (matchesOk) { + if (!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_NO_SCHEMA_ERROR, + message: LR_SCHEMA_ERROR_ACCEPTED, 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, diff --git a/packages/rulesets/src/spectral/test/post-response-codes.test.ts b/packages/rulesets/src/spectral/test/post-response-codes.test.ts index a2afa45f1..84ffdc4c3 100644 --- a/packages/rulesets/src/spectral/test/post-response-codes.test.ts +++ b/packages/rulesets/src/spectral/test/post-response-codes.test.ts @@ -2,12 +2,12 @@ import { Spectral } from "@stoplight/spectral-core" import linterForRule from "./utils" 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 LRO_SCHEMA_ERROR = "202 response for a LRO POST operation must not have a response schema specified." +const LR_SCHEMA_ERROR_ACCEPTED = "202 response for a LRO POST operation must not have a response schema specified." let linter: Spectral @@ -105,9 +105,6 @@ test("PostResponseCodes should find errors for sync post with only 204", () => { responses: { "204": { description: "No content", - schema: { - $ref: "#/definitions/FooResource", - }, }, }, }, @@ -630,9 +627,9 @@ test("PostResponseCodes should find errors for lro post with only 202", () => { return linter.run(myOpenApiDocument).then((results) => { expect(results.length).toBe(2) expect(results[0].path.join(".")).toBe("paths./foo.post") + expect(results[0].message).toContain(LR_ERROR) expect(results[1].path.join(".")).toBe("paths./foo.post") - expect(results[0].message).toContain(LRO_SCHEMA_ERROR) - expect(results[1].message).toContain(LR_ERROR) + expect(results[1].message).toContain(LR_SCHEMA_ERROR_ACCEPTED) }) }) @@ -776,9 +773,9 @@ test("PostResponseCodes should find errors for lro post without default response return linter.run(myOpenApiDocument).then((results) => { expect(results.length).toBe(2) expect(results[0].path.join(".")).toBe("paths./foo.post") + expect(results[0].message).toContain(LR_ERROR) expect(results[1].path.join(".")).toBe("paths./foo.post") - expect(results[0].message).toContain(LRO_SCHEMA_ERROR) - expect(results[1].message).toContain(LR_ERROR) + expect(results[1].message).toContain(LR_SCHEMA_ERROR_ACCEPTED) }) }) @@ -933,9 +930,9 @@ test("PostResponseCodes should find errors for lro post with empty schema in 200 return linter.run(myOpenApiDocument).then((results) => { expect(results.length).toBe(2) expect(results[0].path.join(".")).toBe("paths./foo.post") - expect(results[0].message).toContain(LRO_SCHEMA_ERROR) + expect(results[0].message).toContain(LR_NO_SCHEMA_ERROR_OK) expect(results[1].path.join(".")).toBe("paths./foo.post") - expect(results[1].message).toContain(LR_NO_SCHEMA_ERROR) + expect(results[1].message).toContain(LR_SCHEMA_ERROR_ACCEPTED) }) }) @@ -1091,7 +1088,7 @@ test("PostResponseCodes should find no errors for lro post when 200 with schema, }) }) -test("PostResponseCodes should find no errors for lro post when 202, default codes are provided", () => { +test("PostResponseCodes should find no errors for lro post when 202, 204 no content, default codes are provided", () => { const myOpenApiDocument = { swagger: "2.0", paths: { @@ -1113,6 +1110,9 @@ test("PostResponseCodes should find no errors for lro post when 202, default cod "202": { description: "accepted", }, + "204": { + description: "No content", + }, default: { description: "Error", schema: {}, From 73c09a0e70edd586f544a5e1fcb674985462f0c1 Mon Sep 17 00:00:00 2001 From: akhilailla Date: Tue, 19 Aug 2025 23:52:10 -0500 Subject: [PATCH 2/4] Add schema check for sync request --- .../spectral/functions/post-response-codes.ts | 41 +++++++++++-------- .../spectral/test/post-response-codes.test.ts | 10 ++--- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/packages/rulesets/src/spectral/functions/post-response-codes.ts b/packages/rulesets/src/spectral/functions/post-response-codes.ts index a475fea86..94e9a7bc3 100644 --- a/packages/rulesets/src/spectral/functions/post-response-codes.ts +++ b/packages/rulesets/src/spectral/functions/post-response-codes.ts @@ -6,6 +6,7 @@ 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." @@ -14,6 +15,7 @@ const LR_ERROR = 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 = "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." @@ -59,24 +61,6 @@ export const PostResponseCodes = (postOp: any, _opts: any, ctx: any) => { path: path, }) } - - if (matchesOk) { - if (!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, - }) - } - - return errors } else { const responseSet = new Set(responses) const setEquals = (target: string[]) => target.length === responseSet.size && target.every((v) => responseSet.has(v)) @@ -92,5 +76,26 @@ export const PostResponseCodes = (postOp: any, _opts: any, ctx: any) => { } } + 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 } diff --git a/packages/rulesets/src/spectral/test/post-response-codes.test.ts b/packages/rulesets/src/spectral/test/post-response-codes.test.ts index 84ffdc4c3..fb36b50a8 100644 --- a/packages/rulesets/src/spectral/test/post-response-codes.test.ts +++ b/packages/rulesets/src/spectral/test/post-response-codes.test.ts @@ -8,6 +8,7 @@ const LR_ERROR = 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." let linter: Spectral @@ -217,9 +218,10 @@ test("PostResponseCodes should find errors for sync post without default respons }, } return linter.run(myOpenApiDocument).then((results) => { - expect(results.length).toBe(1) + expect(results.length).toBe(2) expect(results[0].path.join(".")).toBe("paths./foo.post") expect(results[0].message).toContain(SYNC_ERROR) + expect(results[1].message).toContain(LR_SCHEMA_ERROR_NO_CONTENT) }) }) @@ -295,9 +297,10 @@ test("PostResponseCodes should find errors for sync post with extra response cod }, } return linter.run(myOpenApiDocument).then((results) => { - expect(results.length).toBe(1) + expect(results.length).toBe(2) expect(results[0].path.join(".")).toBe("paths./foo.post") expect(results[0].message).toContain(SYNC_ERROR) + expect(results[1].message).toContain(LR_SCHEMA_ERROR_NO_CONTENT) }) }) @@ -513,9 +516,6 @@ test("PostResponseCodes should find no errors for sync post when 204, default re responses: { "204": { description: "No-Content", - schema: { - $ref: "#/definitions/FooResource", - }, }, default: { description: "Error", From 08ce8d353d94bda41b60d074f21536c4ce54de88 Mon Sep 17 00:00:00 2001 From: akhilailla Date: Thu, 21 Aug 2025 11:06:07 -0500 Subject: [PATCH 3/4] updated az-arm.js --- .../rulesets/generated/spectral/az-arm.js | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/rulesets/generated/spectral/az-arm.js b/packages/rulesets/generated/spectral/az-arm.js index ece4bbe28..27c046523 100644 --- a/packages/rulesets/generated/spectral/az-arm.js +++ b/packages/rulesets/generated/spectral/az-arm.js @@ -2314,13 +2314,15 @@ 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 []; } @@ -2355,21 +2357,6 @@ const PostResponseCodes = (postOp, _opts, ctx) => { path: path, }); } - if (matchesOk) { - if (!((_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, - }); - } - return errors; } else { const responseSet = new Set(responses); @@ -2383,6 +2370,24 @@ const PostResponseCodes = (postOp, _opts, ctx) => { }); } } + 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; }; From eca69b83a990eee8dc98fff59c3d8025ff1bdcda Mon Sep 17 00:00:00 2001 From: akhilailla Date: Thu, 4 Sep 2025 10:48:57 -0500 Subject: [PATCH 4/4] Update doc --- docs/post-response-codes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/post-response-codes.md b/docs/post-response-codes.md index 75ddd81f1..c1d129e85 100644 --- a/docs/post-response-codes.md +++ b/docs/post-response-codes.md @@ -25,9 +25,9 @@ Synchronous POST operations must have one of the following combinations of respo 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 if not add 204 response code. -1. Ensure no other response codes are specified. -1. Make sure to define "x-ms-long-running-operation". -1. 202 response for a LRO POST operation must not have a response schema specified. +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. ### Example