api-quality-spectral-ruleset 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apq-spectral.yaml +60 -16
- package/functions/apq-path-depth.js +20 -0
- package/functions/apq-response-media-type.js +43 -0
- package/package.json +1 -1
- package/functions/apq-default-value.js +0 -23
- package/functions/apq-naming-convention.js +0 -21
- package/functions/apq-properties-schema-format.js +0 -29
package/apq-spectral.yaml
CHANGED
|
@@ -10,9 +10,11 @@ functions:
|
|
|
10
10
|
- apq-response-headers
|
|
11
11
|
- apq-require-response-on-path-params
|
|
12
12
|
- apq-required-fields-exist
|
|
13
|
+
- apq-response-media-type
|
|
13
14
|
- apq-security-check
|
|
14
15
|
- apq-custom-field
|
|
15
16
|
- apq-check-examples-coverage
|
|
17
|
+
- apq-path-depth
|
|
16
18
|
extends:
|
|
17
19
|
- spectral:asyncapi
|
|
18
20
|
- spectral:oas
|
|
@@ -110,10 +112,9 @@ rules:
|
|
|
110
112
|
description: "Default response media type should be defined for responses."
|
|
111
113
|
message: "OAR010: Default response media type is mandatory."
|
|
112
114
|
severity: warn
|
|
113
|
-
given: "$.paths[*][get,post,put,patch,delete]
|
|
115
|
+
given: "$.paths[*][get,post,put,patch,delete]"
|
|
114
116
|
then:
|
|
115
|
-
|
|
116
|
-
function: truthy
|
|
117
|
+
function: apq-response-media-type
|
|
117
118
|
apiq:OAR011:
|
|
118
119
|
description: "URLs should follow the specified naming convention: all literal path segments must be in kebab-case."
|
|
119
120
|
message: "OAR011: The resource names with more than two words must be compliant with the standard naming convention (kebab-case)."
|
|
@@ -151,13 +152,16 @@ rules:
|
|
|
151
152
|
function: truthy
|
|
152
153
|
apiq:OAR014:
|
|
153
154
|
description: "Resources depth level should be below the non-suggested range."
|
|
154
|
-
message:
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
message: "OAR014: The number of parts of the path must be less than 4."
|
|
156
|
+
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR014.md"
|
|
157
|
+
severity: warn
|
|
158
|
+
given: "$.paths.*~"
|
|
157
159
|
then:
|
|
158
|
-
function:
|
|
160
|
+
function: apq-path-depth
|
|
159
161
|
functionOptions:
|
|
160
|
-
|
|
162
|
+
maxDepth: 3
|
|
163
|
+
ignoreSegments:
|
|
164
|
+
- me
|
|
161
165
|
apiq:OAR015:
|
|
162
166
|
description: "Resources depth level should be smaller than 5."
|
|
163
167
|
message: "OAR015: Path exceeds the maximum allowed depth level of 5."
|
|
@@ -270,11 +274,30 @@ rules:
|
|
|
270
274
|
apiq:OAR022:
|
|
271
275
|
description: "$orderby must be defined as a query parameter in all operations."
|
|
272
276
|
message: "OAR022: $orderby must be defined as a query parameter in this operation."
|
|
277
|
+
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR022.md"
|
|
273
278
|
severity: warn
|
|
274
|
-
given: "$.paths[
|
|
279
|
+
given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/))].get"
|
|
275
280
|
then:
|
|
276
|
-
|
|
277
|
-
|
|
281
|
+
function: schema
|
|
282
|
+
functionOptions:
|
|
283
|
+
schema:
|
|
284
|
+
if:
|
|
285
|
+
properties:
|
|
286
|
+
responses:
|
|
287
|
+
required: ["206"]
|
|
288
|
+
then:
|
|
289
|
+
required: ["parameters"]
|
|
290
|
+
properties:
|
|
291
|
+
parameters:
|
|
292
|
+
type: array
|
|
293
|
+
contains:
|
|
294
|
+
type: object
|
|
295
|
+
properties:
|
|
296
|
+
name:
|
|
297
|
+
const: "$orderby"
|
|
298
|
+
in:
|
|
299
|
+
const: query
|
|
300
|
+
required: [name, in]
|
|
278
301
|
apiq:OAR023:
|
|
279
302
|
description: "$total must be defined as a query parameter in all operations."
|
|
280
303
|
message: "OAR023: $total must be defined as a query parameter in this operation."
|
|
@@ -294,11 +317,30 @@ rules:
|
|
|
294
317
|
apiq:OAR025:
|
|
295
318
|
description: "$limit must be defined as a query parameter in all operations."
|
|
296
319
|
message: "OAR025: $limit must be defined as a query parameter in this operation."
|
|
320
|
+
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR025.md"
|
|
297
321
|
severity: error
|
|
298
|
-
given: "$.paths[
|
|
322
|
+
given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/))].get"
|
|
299
323
|
then:
|
|
300
|
-
|
|
301
|
-
|
|
324
|
+
function: schema
|
|
325
|
+
functionOptions:
|
|
326
|
+
schema:
|
|
327
|
+
if:
|
|
328
|
+
properties:
|
|
329
|
+
responses:
|
|
330
|
+
required: ["206"]
|
|
331
|
+
then:
|
|
332
|
+
required: ["parameters"]
|
|
333
|
+
properties:
|
|
334
|
+
parameters:
|
|
335
|
+
type: array
|
|
336
|
+
contains:
|
|
337
|
+
type: object
|
|
338
|
+
properties:
|
|
339
|
+
name:
|
|
340
|
+
const: "$limit"
|
|
341
|
+
in:
|
|
342
|
+
const: query
|
|
343
|
+
required: [name, in]
|
|
302
344
|
apiq:OAR026:
|
|
303
345
|
description: "The $total parameter default value should be false."
|
|
304
346
|
message: "OAR026: The $total parameter default value should be false."
|
|
@@ -1062,11 +1104,13 @@ rules:
|
|
|
1062
1104
|
description: "Last path part must be the API version, indicated with the prefix 'v' and the version number as integer."
|
|
1063
1105
|
message: "OAR100: Last path part must be the API version, indicated with the prefix 'v' and the version number as integer."
|
|
1064
1106
|
severity: "error"
|
|
1065
|
-
given:
|
|
1107
|
+
given:
|
|
1108
|
+
- "$.servers[*].url"
|
|
1109
|
+
- "$.basePath"
|
|
1066
1110
|
then:
|
|
1067
1111
|
function: pattern
|
|
1068
1112
|
functionOptions:
|
|
1069
|
-
match: "^https?://[^/]
|
|
1113
|
+
match: "^(https?://[^/]+)?(?:/[^/]+)*/v[0-9]+$"
|
|
1070
1114
|
apiq:OAR101:
|
|
1071
1115
|
description: "The first part of the path should be one of the allowed paths (e.g., '/hello')."
|
|
1072
1116
|
message: "OAR101: The first part of the path should be one of the allowed paths."
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} targetVal
|
|
3
|
+
* @param {object} options
|
|
4
|
+
* @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
|
|
5
|
+
*/
|
|
6
|
+
module.exports = (targetVal, options, context) => {
|
|
7
|
+
const maxDepth = options.maxDepth ?? 3
|
|
8
|
+
const ignore = options.ignoreSegments ?? []
|
|
9
|
+
|
|
10
|
+
const segments = targetVal
|
|
11
|
+
.split('/')
|
|
12
|
+
.filter(Boolean)
|
|
13
|
+
.filter(segment => !ignore.includes(segment))
|
|
14
|
+
|
|
15
|
+
if (segments.length > maxDepth) {
|
|
16
|
+
return [
|
|
17
|
+
{ message: context.rule.message }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {object} given
|
|
3
|
+
* @param {object} options
|
|
4
|
+
* @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const NO_BODY_CODES = new Set(['204']);
|
|
8
|
+
|
|
9
|
+
module.exports = (given, options, context) => {
|
|
10
|
+
const errors = [];
|
|
11
|
+
if (!given) return errors;
|
|
12
|
+
|
|
13
|
+
const root = context.document.parserResult.data;
|
|
14
|
+
|
|
15
|
+
if (root.swagger) {
|
|
16
|
+
const produces = given.produces ?? root.produces;
|
|
17
|
+
|
|
18
|
+
if (!produces || !Array.isArray(produces) || !produces.includes('application/json')) {
|
|
19
|
+
errors.push({
|
|
20
|
+
message: context.rule.message,
|
|
21
|
+
path: [...context.path]
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return errors;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const responses = given.responses;
|
|
29
|
+
if (!responses) return errors;
|
|
30
|
+
|
|
31
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
|
32
|
+
if (NO_BODY_CODES.has(statusCode)) continue;
|
|
33
|
+
|
|
34
|
+
if (!response?.content?.['application/json']) {
|
|
35
|
+
errors.push({
|
|
36
|
+
message: context.rule.message,
|
|
37
|
+
path: [...context.path, 'responses', statusCode]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return errors;
|
|
43
|
+
};
|
package/package.json
CHANGED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @param {object} given
|
|
3
|
-
* @param {object} options
|
|
4
|
-
* @param {string} options.type
|
|
5
|
-
* @param {string} options.match
|
|
6
|
-
* @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
|
|
7
|
-
*/
|
|
8
|
-
module.exports = (given, options, context) => {
|
|
9
|
-
const errors = [];
|
|
10
|
-
if (!given) return errors;
|
|
11
|
-
|
|
12
|
-
if (!given.type || given.type.toString() !== options.type.toString()) {
|
|
13
|
-
errors.push({
|
|
14
|
-
message: context.rule.message,
|
|
15
|
-
});
|
|
16
|
-
} else if (!given.default || given.default.toString() !== options.match.toString()) {
|
|
17
|
-
errors.push({
|
|
18
|
-
message: context.rule.message,
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return errors;
|
|
23
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const { pattern } = require("@stoplight/spectral-functions");
|
|
2
|
-
|
|
3
|
-
const patterns = {
|
|
4
|
-
kebabCase: /^(\/|[a-z0-9-.]+|{[a-zA-Z0-9_]+})+$/,
|
|
5
|
-
camelCase: /^[a-z]+([A-Z][a-z0-9]+)*$/,
|
|
6
|
-
snakeCase: /^[a-z]+(_[a-z0-9]+)*$/,
|
|
7
|
-
pascalCase: /^[A-Z]+([A-Z][a-z0-9]+)*$/
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @param {object} given
|
|
12
|
-
* @param {object} options
|
|
13
|
-
* @param {string} options.pattern
|
|
14
|
-
* @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
|
|
15
|
-
*/
|
|
16
|
-
module.exports = (given, options, context) => {
|
|
17
|
-
const errors = [];
|
|
18
|
-
if (!given) return errors;
|
|
19
|
-
|
|
20
|
-
return pattern(given, { match: patterns[options.pattern] }, context);
|
|
21
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// Check that format is valid for a schema type.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @param {object} given
|
|
5
|
-
* @param {object} options
|
|
6
|
-
* @param {Array<string>} options.formats
|
|
7
|
-
* @param {Array<string>} options.properties
|
|
8
|
-
* @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
|
|
9
|
-
*/
|
|
10
|
-
module.exports = function checkTypeAndFormat(given, options, context) {
|
|
11
|
-
if (given === null || typeof given !== "object") {
|
|
12
|
-
return [];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const errors = [];
|
|
16
|
-
const path = context.path || [];
|
|
17
|
-
const name = path.slice(-1)[0].toString();
|
|
18
|
-
|
|
19
|
-
if (options.properties.includes(name)) {
|
|
20
|
-
if (!given.format || !options.formats.includes(given.format)) {
|
|
21
|
-
errors.push({
|
|
22
|
-
message: context.rule.message,
|
|
23
|
-
path: [...path, name]
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return errors;
|
|
29
|
-
};
|