Skip to content

Commit c26c69a

Browse files
AkhilaIllaakhilailla
andauthored
Add new rule ValidQueryParametersForPointOperations (#746)
Co-authored-by: akhilailla <[email protected]>
1 parent a954384 commit c26c69a

9 files changed

+615
-3
lines changed

docs/rules.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1250,7 +1250,7 @@ Per [ARM guidelines](https://github.com/Azure/azure-resource-manager-rpc/blob/ma
12501250

12511251
Please refer to [top-level-resources-list-by-subscription.md](./top-level-resources-list-by-subscription.md) for details.
12521252

1253-
### trackedExtensionResourcesAreNotAllowed
1253+
### TrackedExtensionResourcesAreNotAllowed
12541254

12551255
Extension resources are always considered to be proxy and must not be of the type tracked.
12561256

@@ -1350,6 +1350,12 @@ Only valid types are allowed for properties.
13501350

13511351
Please refer to [valid-formats.md](./valid-formats.md) for details.
13521352

1353+
### ValidQueryParametersForPointOperations
1354+
1355+
Point operations (GET, PUT, PATCH, DELETE) must not include any query parameters other than api-version.
1356+
1357+
Please refer to [valid-query-parameters-for-point-operations.md](./valid-query-parameters-for-point-operations.md) for details.
1358+
13531359
### ValidResponseCodeRequired
13541360

13551361
Every operation response must contain a valid code like "200","201","202" or "204" which indicates the operation is succeed and it's not allowed that a response schema just contains a "default" code.

docs/tracked-extension-resources-are-not-allowed.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# trackedExtensionResourcesAreNotAllowed
1+
# TrackedExtensionResourcesAreNotAllowed
22

33
## Category
44

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# ValidQueryParametersForPointOperations
2+
3+
## Category
4+
5+
ARM Error
6+
7+
## Applies to
8+
9+
ARM OpenAPI(swagger) specs
10+
11+
## Related ARM Guideline Code
12+
13+
- RPC-Uri-V1-13
14+
15+
## Description
16+
17+
Point operations (GET, PUT, PATCH, DELETE) must not include any query parameters other than api-version.
18+
19+
## How to fix the violation
20+
21+
Remove all query params other than api-version for point operations (GET, PUT, PATCH, DELETE).
22+
23+
## Good Examples
24+
25+
```json
26+
"Microsoft.Music/Songs": {
27+
"get": {
28+
"operationId": "Foo_Get",
29+
"description": "Test Description",
30+
"parameters": [
31+
{
32+
"name": "api-version",
33+
"in": "query"
34+
},
35+
],
36+
},
37+
"put": {
38+
"operationId": "Foo_Update",
39+
"description": "Test Description",
40+
"parameters": [
41+
{
42+
"name": "api-version",
43+
"in": "query"
44+
},
45+
],
46+
},
47+
"patch": {
48+
"operationId": "Foo_Update",
49+
"description": "Test Description",
50+
"parameters": [
51+
{
52+
"name": "api-version",
53+
"in": "query"
54+
},
55+
],
56+
},
57+
"delete": {
58+
"operationId": "Foo_Update",
59+
"description": "Test Description",
60+
"parameters": [
61+
{
62+
"name": "api-version",
63+
"in": "query"
64+
},
65+
],
66+
},
67+
},
68+
```
69+
70+
## Bad Examples
71+
72+
```json
73+
"Microsoft.Music/Songs": {
74+
"get": {
75+
"operationId": "Foo_get",
76+
"description": "Test Description",
77+
"parameters": [
78+
{
79+
"name": "name",
80+
"in": "query",
81+
"required": false,
82+
"type": "string",
83+
},
84+
],
85+
},
86+
"put": {
87+
"operationId": "Foo_Create",
88+
"description": "Test Description",
89+
"parameters": [
90+
{
91+
"name": "$filter",
92+
"in": "query"
93+
},
94+
],
95+
},
96+
"patch": {
97+
"operationId": "Foo_Update",
98+
"description": "Test Description",
99+
"parameters": [
100+
{
101+
"name": "name",
102+
"in": "query"
103+
},
104+
],
105+
},
106+
},
107+
```

packages/rulesets/generated/spectral/az-arm.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,18 @@ const providerAndNamespace = "/providers/[^/]+";
385385
const resourceTypeAndResourceName = "(?:/\\w+/default|/\\w+/{[^/]+})";
386386
const queryParam = "(?:\\?\\w+)";
387387
const resourcePathRegEx = new RegExp(`${providerAndNamespace}${resourceTypeAndResourceName}+${queryParam}?$`, "gi");
388+
function isPointOperation(path) {
389+
const index = path.lastIndexOf("/providers/");
390+
if (index === -1) {
391+
return false;
392+
}
393+
const lastProvider = path.substr(index);
394+
const matches = lastProvider.match(resourcePathRegEx);
395+
if (matches) {
396+
return true;
397+
}
398+
return false;
399+
}
388400
function getResourcesPathHierarchyBasedOnResourceType(path) {
389401
const index = path.lastIndexOf("/providers/");
390402
if (index === -1) {
@@ -3013,6 +3025,37 @@ const trackedResourceTagsPropertyInRequest = (pathItem, _opts, paths) => {
30133025
return errors;
30143026
};
30153027

3028+
const validQueryParametersForPointOperations = (pathItem, _opts, ctx) => {
3029+
if (pathItem === null || typeof pathItem !== "object") {
3030+
return [];
3031+
}
3032+
const path = ctx.path || [];
3033+
const uris = Object.keys(pathItem);
3034+
if (uris.length < 1) {
3035+
return [];
3036+
}
3037+
const pointOperations = new Set(["get", "put", "patch", "delete"]);
3038+
const errors = [];
3039+
for (const uri of uris) {
3040+
if (isPointOperation(uri)) {
3041+
const verbs = Object.keys(pathItem[uri]);
3042+
for (const verb of verbs) {
3043+
if (pointOperations.has(verb)) {
3044+
const params = pathItem[uri][verb]["parameters"];
3045+
const queryParams = params === null || params === void 0 ? void 0 : params.filter((param) => param.in === "query" && param.name !== "api-version");
3046+
queryParams === null || queryParams === void 0 ? void 0 : queryParams.map((param) => {
3047+
errors.push({
3048+
message: `Query parameter ${param.name} should be removed. Point operation '${verb}' MUST not have query parameters other than api-version.`,
3049+
path: [path, uri, verb, "parameters"],
3050+
});
3051+
});
3052+
}
3053+
}
3054+
}
3055+
}
3056+
return errors;
3057+
};
3058+
30163059
const validatePatchBodyParamProperties = createRulesetFunction({
30173060
input: null,
30183061
options: {
@@ -3917,6 +3960,19 @@ const ruleset = {
39173960
function: trackedExtensionResourcesAreNotAllowed,
39183961
},
39193962
},
3963+
ValidQueryParametersForPointOperations: {
3964+
rpcGuidelineCode: "RPC-Uri-V1-13",
3965+
description: "Point operations (GET, PUT, PATCH, DELETE) must not include any query parameters other than api-version.",
3966+
message: "{{error}}",
3967+
stagingOnly: true,
3968+
severity: "error",
3969+
resolved: true,
3970+
formats: [oas2],
3971+
given: "$[paths,'x-ms-paths']",
3972+
then: {
3973+
function: validQueryParametersForPointOperations,
3974+
},
3975+
},
39203976
SystemDataDefinitionsCommonTypes: {
39213977
rpcGuidelineCode: "RPC-SystemData-V1-01, RPC-SystemData-V1-02",
39223978
description: "System data references must utilize common types.",

packages/rulesets/src/spectral/az-arm.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import { tagsAreNotAllowedForProxyResources } from "./functions/tags-are-not-all
5252
import { tenantLevelAPIsNotAllowed } from "./functions/tenant-level-apis-not-allowed"
5353
import { trackedExtensionResourcesAreNotAllowed } from "./functions/tracked-extension-resources-are-not-allowed"
5454
import trackedResourceTagsPropertyInRequest from "./functions/trackedresource-tags-property-in-request"
55+
import { validQueryParametersForPointOperations } from "./functions/valid-query-parameters-for-point-operations"
5556
import { validatePatchBodyParamProperties } from "./functions/validate-patch-body-param-properties"
5657
import withXmsResource from "./functions/with-xms-resource"
5758
import verifyXMSLongRunningOperationProperty from "./functions/xms-long-running-operation-property"
@@ -958,6 +959,20 @@ const ruleset: any = {
958959
function: trackedExtensionResourcesAreNotAllowed,
959960
},
960961
},
962+
// RPC Code: RPC-Uri-V1-13
963+
ValidQueryParametersForPointOperations: {
964+
rpcGuidelineCode: "RPC-Uri-V1-13",
965+
description: "Point operations (GET, PUT, PATCH, DELETE) must not include any query parameters other than api-version.",
966+
message: "{{error}}",
967+
stagingOnly: true,
968+
severity: "error",
969+
resolved: true,
970+
formats: [oas2],
971+
given: "$[paths,'x-ms-paths']",
972+
then: {
973+
function: validQueryParametersForPointOperations,
974+
},
975+
},
961976

962977
///
963978
/// ARM RPC rules for SystemData

packages/rulesets/src/spectral/functions/utils.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,25 @@ const providerAndNamespace = "/providers/[^/]+"
242242
const resourceTypeAndResourceName = "(?:/\\w+/default|/\\w+/{[^/]+})"
243243
const queryParam = "(?:\\?\\w+)"
244244
const resourcePathRegEx = new RegExp(`${providerAndNamespace}${resourceTypeAndResourceName}+${queryParam}?$`, "gi")
245+
/**
246+
* Checks if the provided path is a point operation
247+
* i.e, if its a path that can have point GET, PUT, PATCH, DELETE
248+
* @param path path/uri
249+
* @returns true or false
250+
*/
251+
export function isPointOperation(path: string) {
252+
const index = path.lastIndexOf("/providers/")
253+
if (index === -1) {
254+
return false
255+
}
256+
const lastProvider = path.substr(index)
257+
const matches = lastProvider.match(resourcePathRegEx)
258+
if(matches){
259+
return true
260+
}
261+
return false
262+
}
263+
245264
export function getResourcesPathHierarchyBasedOnResourceType(path: string) {
246265
const index = path.lastIndexOf("/providers/")
247266
if (index === -1) {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { isPointOperation } from "./utils"
2+
3+
export const validQueryParametersForPointOperations = (pathItem: any, _opts: any, ctx: any) => {
4+
if (pathItem === null || typeof pathItem !== "object") {
5+
return []
6+
}
7+
8+
const path = ctx.path || []
9+
const uris = Object.keys(pathItem)
10+
if (uris.length < 1) {
11+
return []
12+
}
13+
const pointOperations = new Set(["get", "put", "patch", "delete"])
14+
const errors: any[] = []
15+
16+
for (const uri of uris) {
17+
//check if the path is a point operation
18+
if (isPointOperation(uri)) {
19+
const verbs = Object.keys(pathItem[uri])
20+
for (const verb of verbs) {
21+
//check query params only for point operations/verbs
22+
if (pointOperations.has(verb)) {
23+
const params = pathItem[uri][verb]["parameters"]
24+
const queryParams = params?.filter((param: { in: string; name: string }) => param.in === "query" && param.name !== "api-version")
25+
queryParams?.map((param: { name: any }) => {
26+
errors.push({
27+
message: `Query parameter ${param.name} should be removed. Point operation '${verb}' MUST not have query parameters other than api-version.`,
28+
path: [path, uri, verb, "parameters"],
29+
})
30+
})
31+
}
32+
}
33+
}
34+
}
35+
36+
return errors
37+
}

packages/rulesets/src/spectral/test/latest-version-of-common-types-must-be-used.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ test("LatestVersionOfCommonTypesMustBeUsed should find no errors", async () => {
116116
expect(results.length).toBe(0)
117117
})
118118
})
119-
test("ParametersInPointGet should find no errors when common-types ref is not present", async () => {
119+
test("LatestVersionOfCommonTypesMustBeUsed should find no errors when common-types ref is not present", async () => {
120120
const myOpenApiDocument = {
121121
swagger: "2.0",
122122
paths: {

0 commit comments

Comments
 (0)