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
- parameter-name: "$expand"
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
- parameter-name: "$exclude"
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 all operations."
310
- message: "OAR022: $orderby must be defined as a query parameter in this operation."
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[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/))].get"
317
+ given: "$.paths"
314
318
  then:
315
- function: schema
319
+ function: apq-collection-query-param-required
316
320
  functionOptions:
317
- schema:
318
- if:
319
- properties:
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 all collection operations (excluding detail endpoints and health checks)."
353
- message: "OAR025: $limit must be defined as a query parameter in this operation."
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[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/) && !@property.match(/status|health|ping/))].get"
345
+ given: "$.paths"
357
346
  then:
358
- function: schema
347
+ function: apq-collection-query-param-required
359
348
  functionOptions:
360
- schema:
361
- if:
362
- properties:
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}: "parameter-name" functionOption is required.`,
50
+ message: `${ruleCode}: "parameterName" functionOption is required.`,
38
51
  path: context.path,
39
52
  }];
40
53
  }
41
54
 
42
- const strategy = options.pathValidationStrategy === STRATEGY_INCLUDE
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-quality-spectral-ruleset",
3
- "version": "1.4.1-beta.2",
3
+ "version": "1.4.1-beta.4",
4
4
  "description": "Spectral ruleset by API Quality",
5
5
  "main": "apq-spectral.yaml",
6
6
  "files": [