api-quality-spectral-ruleset 1.4.1-beta.2 → 1.4.1-beta.4
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
CHANGED
|
@@ -295,7 +295,9 @@ rules:
|
|
|
295
295
|
then:
|
|
296
296
|
function: apq-collection-query-param-required
|
|
297
297
|
functionOptions:
|
|
298
|
-
|
|
298
|
+
parameterName: "$expand"
|
|
299
|
+
paths: "/me,/health,/ping,/status"
|
|
300
|
+
pathValidationStrategy: "/exclude"
|
|
299
301
|
apiq:OAR021:
|
|
300
302
|
description: "$exclude must be defined as a query parameter in collection operations (excluding detail endpoints and health checks)."
|
|
301
303
|
message: "{{error}}"
|
|
@@ -304,34 +306,21 @@ rules:
|
|
|
304
306
|
then:
|
|
305
307
|
function: apq-collection-query-param-required
|
|
306
308
|
functionOptions:
|
|
307
|
-
|
|
309
|
+
parameterName: "$exclude"
|
|
310
|
+
paths: "/me,/health,/ping,/status"
|
|
311
|
+
pathValidationStrategy: "/exclude"
|
|
308
312
|
apiq:OAR022:
|
|
309
|
-
description: "$orderby must be defined as a query parameter in
|
|
310
|
-
message: "
|
|
313
|
+
description: "$orderby must be defined as a query parameter in the collection GET operations selected by the configured paths."
|
|
314
|
+
message: "{{error}}"
|
|
311
315
|
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR022.md"
|
|
312
316
|
severity: warn
|
|
313
|
-
given: "$.paths
|
|
317
|
+
given: "$.paths"
|
|
314
318
|
then:
|
|
315
|
-
function:
|
|
319
|
+
function: apq-collection-query-param-required
|
|
316
320
|
functionOptions:
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
responses:
|
|
321
|
-
required: ["206"]
|
|
322
|
-
then:
|
|
323
|
-
required: ["parameters"]
|
|
324
|
-
properties:
|
|
325
|
-
parameters:
|
|
326
|
-
type: array
|
|
327
|
-
contains:
|
|
328
|
-
type: object
|
|
329
|
-
properties:
|
|
330
|
-
name:
|
|
331
|
-
const: "$orderby"
|
|
332
|
-
in:
|
|
333
|
-
const: query
|
|
334
|
-
required: [name, in]
|
|
321
|
+
parameterName: "$orderby"
|
|
322
|
+
paths: "/examples"
|
|
323
|
+
pathValidationStrategy: "/include"
|
|
335
324
|
apiq:OAR023:
|
|
336
325
|
description: "$total must be defined as a query parameter in all collection operations (excluding detail endpoints and health checks)."
|
|
337
326
|
message: "OAR023: $total must be defined as a query parameter in this operation."
|
|
@@ -349,38 +338,17 @@ rules:
|
|
|
349
338
|
field: "$[?(@.name == '$start' && @.in == 'query')]"
|
|
350
339
|
function: truthy
|
|
351
340
|
apiq:OAR025:
|
|
352
|
-
description: "$limit must be defined as a query parameter in
|
|
353
|
-
message: "
|
|
341
|
+
description: "$limit must be defined as a query parameter in the collection GET operations selected by the configured paths."
|
|
342
|
+
message: "{{error}}"
|
|
354
343
|
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR025.md"
|
|
355
344
|
severity: error
|
|
356
|
-
given: "$.paths
|
|
345
|
+
given: "$.paths"
|
|
357
346
|
then:
|
|
358
|
-
function:
|
|
347
|
+
function: apq-collection-query-param-required
|
|
359
348
|
functionOptions:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
responses:
|
|
364
|
-
required: ["206"]
|
|
365
|
-
then:
|
|
366
|
-
required: ["parameters"]
|
|
367
|
-
properties:
|
|
368
|
-
parameters:
|
|
369
|
-
type: array
|
|
370
|
-
contains:
|
|
371
|
-
type: object
|
|
372
|
-
properties:
|
|
373
|
-
name:
|
|
374
|
-
const: "$limit"
|
|
375
|
-
in:
|
|
376
|
-
const: query
|
|
377
|
-
schema:
|
|
378
|
-
type: object
|
|
379
|
-
properties:
|
|
380
|
-
type:
|
|
381
|
-
const: integer
|
|
382
|
-
required: [type]
|
|
383
|
-
required: [name, in, schema]
|
|
349
|
+
parameterName: "$limit"
|
|
350
|
+
paths: "/examples"
|
|
351
|
+
pathValidationStrategy: "/include"
|
|
384
352
|
apiq:OAR026:
|
|
385
353
|
description: "The $total parameter default value should be false."
|
|
386
354
|
message: "OAR026: The $total parameter default value should be false."
|
|
@@ -6,13 +6,17 @@ const STRATEGY_EXCLUDE = 'Exclude';
|
|
|
6
6
|
|
|
7
7
|
const escapeRegExp = (segment) => segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
const parsePathPatterns = (paths) => paths
|
|
11
|
-
.split(/[\n
|
|
10
|
+
.split(/[\n;,]/)
|
|
12
11
|
.map((p) => p.trim())
|
|
13
12
|
.filter(Boolean)
|
|
14
13
|
.map((segment) => new RegExp(`${escapeRegExp(segment)}(/|$)`));
|
|
15
14
|
|
|
15
|
+
const normalizeStrategy = (value) => {
|
|
16
|
+
const normalized = String(value == null ? '' : value).trim().replace(/^\//, '').toLowerCase();
|
|
17
|
+
return normalized === 'include' ? STRATEGY_INCLUDE : STRATEGY_EXCLUDE;
|
|
18
|
+
};
|
|
19
|
+
|
|
16
20
|
const shouldIncludePath = (path, patterns, strategy) => {
|
|
17
21
|
const matchesList = patterns.some((regex) => regex.test(path));
|
|
18
22
|
return strategy === STRATEGY_INCLUDE ? matchesList : !matchesList;
|
|
@@ -21,6 +25,15 @@ const shouldIncludePath = (path, patterns, strategy) => {
|
|
|
21
25
|
const hasQueryParameter = (parameters, parameterName) => Array.isArray(parameters)
|
|
22
26
|
&& parameters.some((p) => p && !p.$ref && p.name === parameterName && p.in === 'query');
|
|
23
27
|
|
|
28
|
+
const PAGINATED_RESPONSE_CODE = '206';
|
|
29
|
+
const PAGINATED_RULE_CODES = ['OAR022', 'OAR025'];
|
|
30
|
+
|
|
31
|
+
const hasResponseCode = (operation, code) => {
|
|
32
|
+
const responses = operation && operation.responses;
|
|
33
|
+
return !!responses && typeof responses === 'object'
|
|
34
|
+
&& Object.prototype.hasOwnProperty.call(responses, code);
|
|
35
|
+
};
|
|
36
|
+
|
|
24
37
|
/**
|
|
25
38
|
* @param {object} given
|
|
26
39
|
* @param {object} options
|
|
@@ -31,21 +44,21 @@ module.exports = (given, options = {}, context) => {
|
|
|
31
44
|
|
|
32
45
|
const ruleCode = context && context.rule && context.rule.name ? context.rule.name.split(':').pop() : 'apq-collection-query-param-required';
|
|
33
46
|
|
|
34
|
-
const parameterName = options['parameter-name'];
|
|
47
|
+
const parameterName = options.parameterName != null ? options.parameterName : options['parameter-name'];
|
|
35
48
|
if (!parameterName) {
|
|
36
49
|
return [{
|
|
37
|
-
message: `${ruleCode}: "
|
|
50
|
+
message: `${ruleCode}: "parameterName" functionOption is required.`,
|
|
38
51
|
path: context.path,
|
|
39
52
|
}];
|
|
40
53
|
}
|
|
41
54
|
|
|
42
|
-
const strategy = options.pathValidationStrategy
|
|
43
|
-
? STRATEGY_INCLUDE
|
|
44
|
-
: STRATEGY_EXCLUDE;
|
|
55
|
+
const strategy = normalizeStrategy(options.pathValidationStrategy);
|
|
45
56
|
|
|
46
57
|
const rawPaths = typeof options.paths === 'string' ? options.paths : DEFAULT_PATHS;
|
|
47
58
|
const patterns = parsePathPatterns(rawPaths);
|
|
48
59
|
|
|
60
|
+
const requiresPaginated = PAGINATED_RULE_CODES.includes(ruleCode);
|
|
61
|
+
|
|
49
62
|
const results = [];
|
|
50
63
|
|
|
51
64
|
Object.entries(given).forEach(([path, pathItem]) => {
|
|
@@ -56,6 +69,7 @@ module.exports = (given, options = {}, context) => {
|
|
|
56
69
|
|
|
57
70
|
if (PATH_PARAM_SUFFIX_REGEX.test(path)) return;
|
|
58
71
|
if (!shouldIncludePath(path, patterns, strategy)) return;
|
|
72
|
+
if (requiresPaginated && !hasResponseCode(getOperation, PAGINATED_RESPONSE_CODE)) return;
|
|
59
73
|
|
|
60
74
|
if (!hasQueryParameter(getOperation.parameters, parameterName)) {
|
|
61
75
|
results.push({
|
|
@@ -20,10 +20,13 @@ module.exports = (given, options, context) => {
|
|
|
20
20
|
.map((code) => String(code).trim())
|
|
21
21
|
.filter(Boolean);
|
|
22
22
|
|
|
23
|
+
const operationSecurityDefined = Array.isArray(given.security);
|
|
24
|
+
if (operationSecurityDefined && given.security.length === 0) {
|
|
25
|
+
return results;
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
// Check if this operation has security defined
|
|
24
|
-
const operationHasSecurity = given.security
|
|
25
|
-
Array.isArray(given.security) &&
|
|
26
|
-
given.security.length > 0;
|
|
29
|
+
const operationHasSecurity = operationSecurityDefined && given.security.length > 0;
|
|
27
30
|
|
|
28
31
|
// Check if there's global security in the document
|
|
29
32
|
let globalHasSecurity = false;
|