api-quality-spectral-ruleset 1.1.1 → 1.2.0
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 +168 -97
- package/functions/apq-alternate-paths.js +2 -1
- package/functions/apq-binary-format-check.js +39 -0
- package/functions/apq-check-ambiguous-path.js +52 -0
- package/functions/apq-check-examples-coverage.js +12 -0
- package/functions/apq-compare-insensitive.js +110 -13
- package/functions/apq-has-filter-query-param.js +26 -0
- package/functions/apq-paged-response-check.js +124 -0
- package/functions/apq-password-format.js +35 -0
- package/functions/apq-path-depth.js +20 -0
- package/functions/apq-path-param-query-conflict.js +54 -0
- package/functions/apq-resources-by-verb.js +1 -1
- package/functions/apq-response-headers.js +3 -2
- package/functions/apq-response-media-type.js +43 -0
- package/functions/apq-security-required-response.js +46 -0
- package/functions/apq-standard-response-codes.js +4 -1
- package/functions/apq-status-endpoint-check.js +32 -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
|
@@ -3,6 +3,7 @@ functions:
|
|
|
3
3
|
- apq-alternate-paths
|
|
4
4
|
- apq-custom-schema
|
|
5
5
|
- apq-compare-insensitive
|
|
6
|
+
- apq-has-filter-query-param
|
|
6
7
|
- apq-parameter-naming-convention
|
|
7
8
|
- apq-resources-by-verb
|
|
8
9
|
- apq-at-most-one-body-parameter
|
|
@@ -10,9 +11,18 @@ functions:
|
|
|
10
11
|
- apq-response-headers
|
|
11
12
|
- apq-require-response-on-path-params
|
|
12
13
|
- apq-required-fields-exist
|
|
14
|
+
- apq-response-media-type
|
|
13
15
|
- apq-security-check
|
|
14
16
|
- apq-custom-field
|
|
15
17
|
- apq-check-examples-coverage
|
|
18
|
+
- apq-path-depth
|
|
19
|
+
- apq-path-param-query-conflict
|
|
20
|
+
- apq-security-required-response
|
|
21
|
+
- apq-check-ambiguous-path
|
|
22
|
+
- apq-password-format
|
|
23
|
+
- apq-binary-format-check
|
|
24
|
+
- apq-paged-response-check
|
|
25
|
+
- apq-status-endpoint-check
|
|
16
26
|
extends:
|
|
17
27
|
- spectral:asyncapi
|
|
18
28
|
- spectral:oas
|
|
@@ -110,10 +120,9 @@ rules:
|
|
|
110
120
|
description: "Default response media type should be defined for responses."
|
|
111
121
|
message: "OAR010: Default response media type is mandatory."
|
|
112
122
|
severity: warn
|
|
113
|
-
given: "$.paths[*][get,post,put,patch,delete]
|
|
123
|
+
given: "$.paths[*][get,post,put,patch,delete]"
|
|
114
124
|
then:
|
|
115
|
-
|
|
116
|
-
function: truthy
|
|
125
|
+
function: apq-response-media-type
|
|
117
126
|
apiq:OAR011:
|
|
118
127
|
description: "URLs should follow the specified naming convention: all literal path segments must be in kebab-case."
|
|
119
128
|
message: "OAR011: The resource names with more than two words must be compliant with the standard naming convention (kebab-case)."
|
|
@@ -151,13 +160,16 @@ rules:
|
|
|
151
160
|
function: truthy
|
|
152
161
|
apiq:OAR014:
|
|
153
162
|
description: "Resources depth level should be below the non-suggested range."
|
|
154
|
-
message:
|
|
155
|
-
|
|
156
|
-
|
|
163
|
+
message: "OAR014: The number of parts of the path must be less than 4."
|
|
164
|
+
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR014.md"
|
|
165
|
+
severity: warn
|
|
166
|
+
given: "$.paths.*~"
|
|
157
167
|
then:
|
|
158
|
-
function:
|
|
168
|
+
function: apq-path-depth
|
|
159
169
|
functionOptions:
|
|
160
|
-
|
|
170
|
+
maxDepth: 3
|
|
171
|
+
ignoreSegments:
|
|
172
|
+
- me
|
|
161
173
|
apiq:OAR015:
|
|
162
174
|
description: "Resources depth level should be smaller than 5."
|
|
163
175
|
message: "OAR015: Path exceeds the maximum allowed depth level of 5."
|
|
@@ -186,7 +198,7 @@ rules:
|
|
|
186
198
|
then:
|
|
187
199
|
properties:
|
|
188
200
|
format:
|
|
189
|
-
enum: [int32, int64]
|
|
201
|
+
enum: [int32, int64, uint32, uint64]
|
|
190
202
|
- if:
|
|
191
203
|
properties:
|
|
192
204
|
type:
|
|
@@ -194,7 +206,7 @@ rules:
|
|
|
194
206
|
then:
|
|
195
207
|
properties:
|
|
196
208
|
format:
|
|
197
|
-
enum: [float, double]
|
|
209
|
+
enum: [float, double, decimal]
|
|
198
210
|
apiq:OAR017:
|
|
199
211
|
description: "Resource path should alternate static and parametrized parts."
|
|
200
212
|
message: "OAR017: Resource path should alternate static and parametrized parts."
|
|
@@ -243,62 +255,119 @@ rules:
|
|
|
243
255
|
;delete:^/[^/{}]+/(\{[^/{}]+\}|me)$
|
|
244
256
|
;delete:^/[^/{}]+/(\{[^/{}]+\}|me)/[^/{}]+/(\{[^/{}]+\}|me)$
|
|
245
257
|
;delete:^/[^/{}]+/(\{[^/{}]+\}|me)/[^/{}]+/(\{[^/{}]+\}|me)/[^/{}]+/(\{[^/{}]+\}|me)$
|
|
258
|
+
|
|
259
|
+
;post:^/[^/{}]+/archive$
|
|
260
|
+
;post:^/[^/{}]+/(\{[^/{}]+\}|me)/[^/{}]+/archive$
|
|
261
|
+
;post:^/[^/{}]+/(\{[^/{}]+\}|me)/[^/{}]+/(\{[^/{}]+\}|me)/[^/{}]+/archive$
|
|
262
|
+
;post:^/[^/{}]+/clone$
|
|
263
|
+
;post:^/[^/{}]+/(\{[^/{}]+\}|me)/[^/{}]+/clone$
|
|
264
|
+
;post:^/[^/{}]+/(\{[^/{}]+\}|me)/[^/{}]+/(\{[^/{}]+\}|me)/[^/{}]+/clone$
|
|
265
|
+
;post:^/[^/{}]+/restore$
|
|
266
|
+
;post:^/[^/{}]+/(\{[^/{}]+\}|me)/[^/{}]+/restore$
|
|
267
|
+
;post:^/[^/{}]+/(\{[^/{}]+\}|me)/[^/{}]+/(\{[^/{}]+\}|me)/[^/{}]+/restore$
|
|
268
|
+
|
|
269
|
+
;head:.*
|
|
270
|
+
;options:.*
|
|
246
271
|
apiq:OAR019:
|
|
247
|
-
description: "$select must be defined as a query parameter in
|
|
272
|
+
description: "$select must be defined as a query parameter in collection operations (excluding detail endpoints and health checks)."
|
|
248
273
|
message: "OAR019: $select must be defined as a query parameter in this operation."
|
|
249
274
|
severity: warn
|
|
250
|
-
given: "$.paths[
|
|
275
|
+
given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/) && !@property.match(/status|health|ping/))].get.parameters"
|
|
251
276
|
then:
|
|
252
277
|
field: "$[?(@.name == '$select' && @.in == 'query')]"
|
|
253
278
|
function: truthy
|
|
254
279
|
apiq:OAR020:
|
|
255
|
-
description: "$expand must be defined as a query parameter in
|
|
280
|
+
description: "$expand must be defined as a query parameter in collection operations (excluding detail endpoints and health checks)."
|
|
256
281
|
message: "OAR020: $expand must be defined as a query parameter in this operation."
|
|
257
282
|
severity: warn
|
|
258
|
-
given: "$.paths[
|
|
283
|
+
given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/) && !@property.match(/status|health|ping/))].get.parameters"
|
|
259
284
|
then:
|
|
260
285
|
field: "$[?(@.name == '$expand' && @.in == 'query')]"
|
|
261
286
|
function: truthy
|
|
262
287
|
apiq:OAR021:
|
|
263
|
-
description: "$exclude must be defined as a query parameter in
|
|
288
|
+
description: "$exclude must be defined as a query parameter in collection operations (excluding detail endpoints and health checks)."
|
|
264
289
|
message: "OAR021: $exclude must be defined as a query parameter in this operation."
|
|
265
290
|
severity: warn
|
|
266
|
-
given: "$.paths[
|
|
291
|
+
given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/) && !@property.match(/status|health|ping/))].get.parameters"
|
|
267
292
|
then:
|
|
268
293
|
field: "$[?(@.name == '$exclude' && @.in == 'query')]"
|
|
269
294
|
function: truthy
|
|
270
295
|
apiq:OAR022:
|
|
271
296
|
description: "$orderby must be defined as a query parameter in all operations."
|
|
272
297
|
message: "OAR022: $orderby must be defined as a query parameter in this operation."
|
|
298
|
+
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR022.md"
|
|
273
299
|
severity: warn
|
|
274
|
-
given: "$.paths[
|
|
300
|
+
given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/))].get"
|
|
275
301
|
then:
|
|
276
|
-
|
|
277
|
-
|
|
302
|
+
function: schema
|
|
303
|
+
functionOptions:
|
|
304
|
+
schema:
|
|
305
|
+
if:
|
|
306
|
+
properties:
|
|
307
|
+
responses:
|
|
308
|
+
required: ["206"]
|
|
309
|
+
then:
|
|
310
|
+
required: ["parameters"]
|
|
311
|
+
properties:
|
|
312
|
+
parameters:
|
|
313
|
+
type: array
|
|
314
|
+
contains:
|
|
315
|
+
type: object
|
|
316
|
+
properties:
|
|
317
|
+
name:
|
|
318
|
+
const: "$orderby"
|
|
319
|
+
in:
|
|
320
|
+
const: query
|
|
321
|
+
required: [name, in]
|
|
278
322
|
apiq:OAR023:
|
|
279
|
-
description: "$total must be defined as a query parameter in all operations."
|
|
323
|
+
description: "$total must be defined as a query parameter in all collection operations (excluding detail endpoints and health checks)."
|
|
280
324
|
message: "OAR023: $total must be defined as a query parameter in this operation."
|
|
281
325
|
severity: error
|
|
282
|
-
given: "$.paths[
|
|
326
|
+
given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/) && !@property.match(/status|health|ping/))].get.parameters"
|
|
283
327
|
then:
|
|
284
328
|
field: "$[?(@.name == '$total' && @.in == 'query')]"
|
|
285
329
|
function: truthy
|
|
286
330
|
apiq:OAR024:
|
|
287
|
-
description: "$start must be defined as a query parameter in all operations."
|
|
331
|
+
description: "$start must be defined as a query parameter in all collection operations (excluding detail endpoints and health checks)."
|
|
288
332
|
message: "OAR024: $start must be defined as a query parameter in this operation."
|
|
289
333
|
severity: error
|
|
290
|
-
given: "$.paths[
|
|
334
|
+
given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/) && !@property.match(/status|health|ping/))].get.parameters"
|
|
291
335
|
then:
|
|
292
336
|
field: "$[?(@.name == '$start' && @.in == 'query')]"
|
|
293
337
|
function: truthy
|
|
294
338
|
apiq:OAR025:
|
|
295
|
-
description: "$limit must be defined as a query parameter in all operations."
|
|
339
|
+
description: "$limit must be defined as a query parameter in all collection operations (excluding detail endpoints and health checks)."
|
|
296
340
|
message: "OAR025: $limit must be defined as a query parameter in this operation."
|
|
341
|
+
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR025.md"
|
|
297
342
|
severity: error
|
|
298
|
-
given: "$.paths[
|
|
343
|
+
given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/) && !@property.match(/status|health|ping/))].get"
|
|
299
344
|
then:
|
|
300
|
-
|
|
301
|
-
|
|
345
|
+
function: schema
|
|
346
|
+
functionOptions:
|
|
347
|
+
schema:
|
|
348
|
+
if:
|
|
349
|
+
properties:
|
|
350
|
+
responses:
|
|
351
|
+
required: ["206"]
|
|
352
|
+
then:
|
|
353
|
+
required: ["parameters"]
|
|
354
|
+
properties:
|
|
355
|
+
parameters:
|
|
356
|
+
type: array
|
|
357
|
+
contains:
|
|
358
|
+
type: object
|
|
359
|
+
properties:
|
|
360
|
+
name:
|
|
361
|
+
const: "$limit"
|
|
362
|
+
in:
|
|
363
|
+
const: query
|
|
364
|
+
schema:
|
|
365
|
+
type: object
|
|
366
|
+
properties:
|
|
367
|
+
type:
|
|
368
|
+
const: integer
|
|
369
|
+
required: [type]
|
|
370
|
+
required: [name, in, schema]
|
|
302
371
|
apiq:OAR026:
|
|
303
372
|
description: "The $total parameter default value should be false."
|
|
304
373
|
message: "OAR026: The $total parameter default value should be false."
|
|
@@ -316,13 +385,12 @@ rules:
|
|
|
316
385
|
field: "headers.Location.schema.type"
|
|
317
386
|
function: truthy
|
|
318
387
|
apiq:OAR028:
|
|
319
|
-
description: "$filter must be defined as a query parameter in all operations."
|
|
388
|
+
description: "$filter must be defined as a query parameter in all collection operations (excluding detail endpoints and health checks)."
|
|
320
389
|
message: "OAR028: $filter must be defined as a query parameter in this operation."
|
|
321
390
|
severity: warn
|
|
322
|
-
given: "$.paths[
|
|
391
|
+
given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/) && !@property.match(/status|health|ping/))].get"
|
|
323
392
|
then:
|
|
324
|
-
|
|
325
|
-
function: truthy
|
|
393
|
+
function: apq-has-filter-query-param
|
|
326
394
|
apiq:OAR029:
|
|
327
395
|
description: "A response not compliant with the standard may cause application issues."
|
|
328
396
|
message: "OAR029: A response not compliant with the standard may cause application issues."
|
|
@@ -358,13 +426,15 @@ rules:
|
|
|
358
426
|
required:
|
|
359
427
|
- properties
|
|
360
428
|
apiq:OAR030:
|
|
361
|
-
description: "The
|
|
362
|
-
message: "OAR030: The
|
|
429
|
+
description: "The configured status endpoint must be declared with the configured HTTP method."
|
|
430
|
+
message: "OAR030: The required status endpoint is not declared or does not have the required method."
|
|
363
431
|
severity: error
|
|
364
432
|
given: "$.paths"
|
|
365
433
|
then:
|
|
366
|
-
|
|
367
|
-
|
|
434
|
+
function: apq-status-endpoint-check
|
|
435
|
+
functionOptions:
|
|
436
|
+
status-endpoint: "/status"
|
|
437
|
+
method: "get"
|
|
368
438
|
apiq:OAR031:
|
|
369
439
|
description: The examples can help developers to understand the response data structure and representation."
|
|
370
440
|
message: "OAR031: Examples - Parameters, Request Body and Responses must have an examples defined."
|
|
@@ -381,13 +451,12 @@ rules:
|
|
|
381
451
|
severity: error
|
|
382
452
|
given: "$.paths.*~"
|
|
383
453
|
then:
|
|
384
|
-
|
|
385
|
-
function: pattern
|
|
454
|
+
function: apq-check-ambiguous-path
|
|
386
455
|
functionOptions:
|
|
387
|
-
|
|
456
|
+
ambiguous-words: "elements,instances,resources,values,terms,objects,items"
|
|
388
457
|
apiq:OAR033:
|
|
389
|
-
description: "
|
|
390
|
-
message: "OAR033: The header '{{value}}' is not allowed."
|
|
458
|
+
description: "Request operation parameters must not include forbidden headers (Accept, Content-Type, Authorization). Note: This validates REQUEST headers, not response headers (see OAR053/114 for response header validation)."
|
|
459
|
+
message: "OAR033: The request header parameter '{{value}}' is not allowed in operations."
|
|
391
460
|
severity: error
|
|
392
461
|
given: "$.paths[*][get,post,put,patch,delete].parameters[?(@.in == 'header')]"
|
|
393
462
|
then:
|
|
@@ -396,22 +465,21 @@ rules:
|
|
|
396
465
|
functionOptions:
|
|
397
466
|
notMatch: "^(Accept|Content-Type|Authorization)$"
|
|
398
467
|
apiq:OAR034:
|
|
399
|
-
description: "A paged response
|
|
400
|
-
message: "OAR034: Paged response schema
|
|
468
|
+
description: "A paged response must comply with the standard paging schema (start, limit, links with self/previous/next, numPages, total)."
|
|
469
|
+
message: "OAR034: Paged response schema does not match the required standard."
|
|
401
470
|
severity: error
|
|
402
|
-
given: "$.paths[*][
|
|
471
|
+
given: "$.paths[*][get].responses[?(@property != '204' && @property != '304' && @property != 'default')]..schema"
|
|
403
472
|
then:
|
|
404
|
-
|
|
405
|
-
|
|
473
|
+
function: apq-paged-response-check
|
|
474
|
+
functionOptions:
|
|
475
|
+
paging-schema: '{"type":"object","properties":{"numPages":{"type":"integer"},"total":{"type":"integer"},"start":{"type":"integer"},"limit":{"type":"integer"},"links":{"type":"object","properties":{"next":{"type":"object","properties":{"href":{"type":"string"}}},"previous":{"type":"object","properties":{"href":{"type":"string"}}},"last":{"type":"object","properties":{"href":{"type":"string"}}},"self":{"type":"object","properties":{"href":{"type":"string"}}},"first":{"type":"object","properties":{"href":{"type":"string"}}}},"required":["self","previous","next"]}},"required":["start","limit","links"],"pagingPropertyName":"paging"}'
|
|
406
476
|
apiq:OAR035:
|
|
407
477
|
description: "Response code 401 must be defined for operations with security schemes defined."
|
|
408
478
|
message: "OAR035: Response code 401 must be defined for operations with security schemes defined."
|
|
409
479
|
severity: "error"
|
|
410
|
-
given: "$.paths[*][*]
|
|
480
|
+
given: "$.paths[*][*]"
|
|
411
481
|
then:
|
|
412
|
-
function:
|
|
413
|
-
field: "401"
|
|
414
|
-
message: "Operation must define a 401 Unauthorized response for security schemes."
|
|
482
|
+
function: apq-security-required-response
|
|
415
483
|
apiq:OAR036:
|
|
416
484
|
description: "Cookie use is forbidden as a session mechanism."
|
|
417
485
|
message: "OAR036: Cookie use is forbidden as a session mechanism."
|
|
@@ -426,14 +494,14 @@ rules:
|
|
|
426
494
|
notMatch: "^(Cookie|Set-Cookie)$"
|
|
427
495
|
apiq:OAR037:
|
|
428
496
|
description: "Schema should use well-defined type and format."
|
|
429
|
-
message: "OAR037: String schemas must specify a valid format (date, date-time, password, byte, binary, email, uuid, uri, hostname, ipv4, or
|
|
497
|
+
message: "OAR037: String schemas must specify a valid format (date, date-time, password, byte, binary, email, uuid, uri, hostname, ipv4, ipv6, HEX, json, xml, or base64)."
|
|
430
498
|
severity: error
|
|
431
|
-
given: "$..schema..[?(@.type=='string')]"
|
|
499
|
+
given: "$..schema..[?(@.type=='string' && !@.enum)]"
|
|
432
500
|
then:
|
|
433
501
|
field: format
|
|
434
502
|
function: pattern
|
|
435
503
|
functionOptions:
|
|
436
|
-
match: "^(date|date-time|password|byte|binary|email|uuid|uri|hostname|ipv4|ipv6)$"
|
|
504
|
+
match: "^(date|date-time|password|byte|binary|email|uuid|uri|hostname|ipv4|ipv6|HEX|json|xml|base64)$"
|
|
437
505
|
apiq:OAR038:
|
|
438
506
|
description: "Response schema must be compliant with the standard."
|
|
439
507
|
message: "OAR038: Response property is required for status code '{{property}}'."
|
|
@@ -469,6 +537,8 @@ rules:
|
|
|
469
537
|
;post:^/[^/{}]+/(\{[^/{}]+\}|\bme\b)/[^/{}]+/get$:200,404,400,415,500,503
|
|
470
538
|
;post:^/[^/{}]+/delete$:200,404,400,415,500,503
|
|
471
539
|
;post:^/[^/{}]+/(\{[^/{}]+\}|\bme\b)/[^/{}]+/delete$:200,404,400,415,500,503
|
|
540
|
+
;post:^/[^/{}]+/(\{[^/{}]+\}|\bme\b)/[^/{}]+/(\{[^/{}]+\}|\bme\b)/[^/{}]+/get$:200,404,400,415,500,503
|
|
541
|
+
;post:^/[^/{}]+/(\{[^/{}]+\}|\bme\b)/[^/{}]+/(\{[^/{}]+\}|\bme\b)/[^/{}]+/delete$:200,404,400,415,500,503
|
|
472
542
|
;put:^/[^/{}]+/(\{[^/{}]+\}|\bme\b)$:200,404,400,415,500,503
|
|
473
543
|
;put:^/[^/{}]+/(\{[^/{}]+\}|\bme\b)/[^/{}]+/(\{[^/{}]+\}|\bme\b)$:200,404,400,415,500,503
|
|
474
544
|
;put:^/[^/{}]+/(\{[^/{}]+\}|\bme\b)/[^/{}]+/(\{[^/{}]+\}|\bme\b)/[^/{}]+/(\{[^/{}]+\}|\bme\b)$:200,404,400,415,500,503
|
|
@@ -491,7 +561,7 @@ rules:
|
|
|
491
561
|
description: Available scopes must be defined at the end of the document, at the root level, inside `x-wso2-security`.
|
|
492
562
|
message: "OAR041:Scopes must be defined in `x-wso2-security` at the end of the document, before the paths."
|
|
493
563
|
severity: "error"
|
|
494
|
-
given: "$.paths[
|
|
564
|
+
given: "$.paths[*].get"
|
|
495
565
|
then:
|
|
496
566
|
field: "x-aut-type"
|
|
497
567
|
function: truthy
|
|
@@ -512,7 +582,6 @@ rules:
|
|
|
512
582
|
severity: "error"
|
|
513
583
|
given: "$.paths.*.*.responses.*.content.*~"
|
|
514
584
|
then:
|
|
515
|
-
field: name
|
|
516
585
|
function: pattern
|
|
517
586
|
functionOptions:
|
|
518
587
|
match: "^application\/[a-zA-Z0-9-_]+$"
|
|
@@ -596,16 +665,17 @@ rules:
|
|
|
596
665
|
field: "summary"
|
|
597
666
|
function: truthy
|
|
598
667
|
apiq:OAR051:
|
|
599
|
-
description: "
|
|
600
|
-
message: "OAR051:
|
|
668
|
+
description: "Summary and description must be different - not just case variations or semantic duplicates."
|
|
669
|
+
message: "OAR051: Summary and description must be meaningfully different from each other."
|
|
601
670
|
severity: "error"
|
|
602
|
-
given: "$.paths[*]
|
|
671
|
+
given: "$.paths[*][get,post,put,patch,delete]"
|
|
603
672
|
then:
|
|
604
673
|
function: apq-compare-insensitive
|
|
605
674
|
functionOptions:
|
|
606
675
|
property: summary
|
|
607
676
|
equalTo: description
|
|
608
677
|
result: falsy
|
|
678
|
+
threshold: 0.55
|
|
609
679
|
apiq:OAR052:
|
|
610
680
|
description: "Numeric schema types must define a format."
|
|
611
681
|
message: "OAR052: Numeric types requires a format"
|
|
@@ -616,19 +686,19 @@ rules:
|
|
|
616
686
|
field: format
|
|
617
687
|
function: truthy
|
|
618
688
|
apiq:OAR053:
|
|
619
|
-
description: "
|
|
620
|
-
message: "
|
|
689
|
+
description: "Response headers for API observability and tracing must be defined (excluding 204 responses and health endpoints)."
|
|
690
|
+
message: "OAR053: Response must include mandatory headers and exclude forbidden headers."
|
|
621
691
|
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR053.md"
|
|
622
692
|
severity: error
|
|
623
693
|
given: "$.paths[*][*].responses[*]"
|
|
624
694
|
then:
|
|
625
695
|
- function: apq-response-headers
|
|
626
696
|
functionOptions:
|
|
627
|
-
mandatory-headers: "x-trace-id
|
|
697
|
+
mandatory-headers: "x-trace-id"
|
|
628
698
|
allowed-headers: "idcorrelacion,x-correlacionid,x-global-trasaction-id,x-power-by,x-trace-id,x-request-id"
|
|
629
699
|
included-response-codes: "*"
|
|
630
700
|
excluded-response-codes: "204"
|
|
631
|
-
path-exclusions: "/status
|
|
701
|
+
path-exclusions: "/status"
|
|
632
702
|
apiq:OAR054:
|
|
633
703
|
description: "Ensure the host matches the specified format"
|
|
634
704
|
message: "OAR054: Hostname must be a subdomain of the organization's domain name."
|
|
@@ -651,7 +721,7 @@ rules:
|
|
|
651
721
|
description: "Ensure get have mandatory response codes"
|
|
652
722
|
message: "OAR061: Ensure get have the mandatory response codes"
|
|
653
723
|
severity: error
|
|
654
|
-
given: "$.paths[?(
|
|
724
|
+
given: "$.paths[?(@property !== '/status')].get.responses"
|
|
655
725
|
then:
|
|
656
726
|
function: schema
|
|
657
727
|
functionOptions:
|
|
@@ -666,7 +736,7 @@ rules:
|
|
|
666
736
|
description: "Ensure post have mandatory response codes"
|
|
667
737
|
message: "OAR062: Ensure post have the mandatory response codes"
|
|
668
738
|
severity: error
|
|
669
|
-
given: "$.paths[?(
|
|
739
|
+
given: "$.paths[?(@property !== '/status')].post.responses"
|
|
670
740
|
then:
|
|
671
741
|
function: schema
|
|
672
742
|
functionOptions:
|
|
@@ -681,7 +751,7 @@ rules:
|
|
|
681
751
|
description: "Ensure put have mandatory response codes"
|
|
682
752
|
message: "OAR063: Ensure put have the mandatory response codes"
|
|
683
753
|
severity: error
|
|
684
|
-
given: "$.paths[?(
|
|
754
|
+
given: "$.paths[?(@property !== '/status')].put.responses"
|
|
685
755
|
then:
|
|
686
756
|
function: schema
|
|
687
757
|
functionOptions:
|
|
@@ -695,7 +765,7 @@ rules:
|
|
|
695
765
|
description: "Ensure patch have mandatory response codes"
|
|
696
766
|
message: "OAR064: Ensure patch have the mandatory response codes"
|
|
697
767
|
severity: error
|
|
698
|
-
given: "$.paths[?(
|
|
768
|
+
given: "$.paths[?(@property !== '/status')].patch.responses"
|
|
699
769
|
then:
|
|
700
770
|
function: schema
|
|
701
771
|
functionOptions:
|
|
@@ -709,7 +779,7 @@ rules:
|
|
|
709
779
|
description: "Ensure delete have mandatory response codes"
|
|
710
780
|
message: "OAR065: Ensure delete have the mandatory response codes"
|
|
711
781
|
severity: error
|
|
712
|
-
given: "$.paths[?(
|
|
782
|
+
given: "$.paths[?(@property !== '/status')].delete.responses"
|
|
713
783
|
then:
|
|
714
784
|
function: schema
|
|
715
785
|
functionOptions:
|
|
@@ -722,7 +792,10 @@ rules:
|
|
|
722
792
|
description: "RequestBody and Responses schema property names must be compliant with the snake_case naming convention."
|
|
723
793
|
message: "OAR066: RequestBody and Responses schema property names must be compliant with the snake_case naming convention."
|
|
724
794
|
severity: "warn"
|
|
725
|
-
given:
|
|
795
|
+
given:
|
|
796
|
+
- "$.paths.*.*[responses,requestBody]..content..schema..properties.*~"
|
|
797
|
+
- "$.paths.*.*.parameters[*].schema..properties.*~"
|
|
798
|
+
- "$.paths.*.*.responses[*].schema..properties.*~"
|
|
726
799
|
then:
|
|
727
800
|
function: casing
|
|
728
801
|
functionOptions:
|
|
@@ -746,14 +819,12 @@ rules:
|
|
|
746
819
|
functionOptions:
|
|
747
820
|
type: pascal
|
|
748
821
|
apiq:OAR069:
|
|
749
|
-
description: "
|
|
750
|
-
message: "OAR069:
|
|
751
|
-
severity: "
|
|
752
|
-
given: "$.paths
|
|
822
|
+
description: "Path parameters should not appear as query parameters."
|
|
823
|
+
message: "OAR069: Path parameter should not appear as both path and query parameter."
|
|
824
|
+
severity: "error"
|
|
825
|
+
given: "$.paths"
|
|
753
826
|
then:
|
|
754
|
-
function:
|
|
755
|
-
field: "400"
|
|
756
|
-
message: "Operation must define a 400 Bad Request response for parameters in PATH or QUERY."
|
|
827
|
+
function: apq-path-param-query-conflict
|
|
757
828
|
apiq:OAR070:
|
|
758
829
|
description: "Parameters in path should not be numeric."
|
|
759
830
|
message: "OAR070: Parameters in path should not be numeric."
|
|
@@ -787,10 +858,10 @@ rules:
|
|
|
787
858
|
functionOptions:
|
|
788
859
|
notMatch: "stacktrace"
|
|
789
860
|
apiq:OAR073:
|
|
790
|
-
description: "API should include a 429 response to indicate rate limiting, except for
|
|
861
|
+
description: "API should include a 429 response to indicate rate limiting, except for health check paths like /status, /health, /ping."
|
|
791
862
|
message: "OAR073: API should include a 429 response to indicate rate limiting."
|
|
792
863
|
severity: error
|
|
793
|
-
given: "$.paths[?(@
|
|
864
|
+
given: "$.paths[?(!(@property.match(/\\/status|health|ping|liveness|readiness/)))][*].responses"
|
|
794
865
|
then:
|
|
795
866
|
field: "429"
|
|
796
867
|
function: truthy
|
|
@@ -867,7 +938,7 @@ rules:
|
|
|
867
938
|
match: ^\$?_?[a-z]+(_[a-z]+)*$
|
|
868
939
|
apiq:OAR078:
|
|
869
940
|
description: "All API methods must have security defined."
|
|
870
|
-
message: "
|
|
941
|
+
message: "OAR078: Operation must have security defined."
|
|
871
942
|
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR078.md"
|
|
872
943
|
severity: error
|
|
873
944
|
given: "$"
|
|
@@ -897,22 +968,18 @@ rules:
|
|
|
897
968
|
description: "Fields of type password should be string with format password."
|
|
898
969
|
message: "OAR081: Fields of type password should be string with format password."
|
|
899
970
|
severity: error
|
|
900
|
-
given: "$..properties
|
|
971
|
+
given: "$..properties"
|
|
901
972
|
then:
|
|
902
|
-
|
|
903
|
-
function: pattern
|
|
904
|
-
functionOptions:
|
|
905
|
-
match: "^password$"
|
|
973
|
+
function: apq-password-format
|
|
906
974
|
apiq:OAR082:
|
|
907
|
-
description: "The string properties must define a byte or binary format."
|
|
908
|
-
message: "OAR082:
|
|
975
|
+
description: "The string properties 'product', 'line', and 'price' must define a byte or binary format."
|
|
976
|
+
message: "OAR082: Property '{{property}}' must define a 'byte' or 'binary' format (currently: {{format}})."
|
|
909
977
|
severity: error
|
|
910
|
-
given: "$..properties
|
|
978
|
+
given: "$..[?(@.properties)]"
|
|
911
979
|
then:
|
|
912
|
-
|
|
913
|
-
function: pattern
|
|
980
|
+
function: apq-binary-format-check
|
|
914
981
|
functionOptions:
|
|
915
|
-
|
|
982
|
+
fields-to-apply: "product,line,price"
|
|
916
983
|
apiq:OAR083:
|
|
917
984
|
description: "Certain parameters (e.g., email, password) should not pass through the querystring."
|
|
918
985
|
message: "OAR083: The parameter '{{value}}' should not pass through the querystring."
|
|
@@ -982,7 +1049,7 @@ rules:
|
|
|
982
1049
|
description: "The $ref of a response must end with the suffix Response."
|
|
983
1050
|
message: "OAR090: The $ref of a response must end with the suffix Response"
|
|
984
1051
|
severity: error
|
|
985
|
-
given: "$.paths[
|
|
1052
|
+
given: "$.paths[*][*].responses[*].$ref"
|
|
986
1053
|
then:
|
|
987
1054
|
function: pattern
|
|
988
1055
|
functionOptions:
|
|
@@ -1062,11 +1129,13 @@ rules:
|
|
|
1062
1129
|
description: "Last path part must be the API version, indicated with the prefix 'v' and the version number as integer."
|
|
1063
1130
|
message: "OAR100: Last path part must be the API version, indicated with the prefix 'v' and the version number as integer."
|
|
1064
1131
|
severity: "error"
|
|
1065
|
-
given:
|
|
1132
|
+
given:
|
|
1133
|
+
- "$.servers[*].url"
|
|
1134
|
+
- "$.basePath"
|
|
1066
1135
|
then:
|
|
1067
1136
|
function: pattern
|
|
1068
1137
|
functionOptions:
|
|
1069
|
-
match: "^https?://[^/]
|
|
1138
|
+
match: "^(https?://[^/]+)?(?:/[^/]+)*/v[0-9]+$"
|
|
1070
1139
|
apiq:OAR101:
|
|
1071
1140
|
description: "The first part of the path should be one of the allowed paths (e.g., '/hello')."
|
|
1072
1141
|
message: "OAR101: The first part of the path should be one of the allowed paths."
|
|
@@ -1163,7 +1232,7 @@ rules:
|
|
|
1163
1232
|
function: truthy
|
|
1164
1233
|
apiq:OAR113:
|
|
1165
1234
|
description: "Field or extension must be at the assigned location"
|
|
1166
|
-
message: "
|
|
1235
|
+
message: "OAR113: Field or extension x-custom-example must be at the assigned location"
|
|
1167
1236
|
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR113.md"
|
|
1168
1237
|
severity: warn
|
|
1169
1238
|
given: "$"
|
|
@@ -1173,19 +1242,21 @@ rules:
|
|
|
1173
1242
|
fieldName: "x-custom-example"
|
|
1174
1243
|
fieldLocation: "path,operation_get,response_200"
|
|
1175
1244
|
apiq:OAR114:
|
|
1176
|
-
description: "
|
|
1177
|
-
message: "
|
|
1245
|
+
description: "Response headers for API security and key management must be defined (excluding 204 responses)."
|
|
1246
|
+
message: "OAR114: Response must include mandatory headers and exclude forbidden headers."
|
|
1178
1247
|
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR114.md"
|
|
1179
1248
|
severity: "error"
|
|
1180
1249
|
given: "$.paths[*][*].responses[*]"
|
|
1181
1250
|
then:
|
|
1182
1251
|
- function: apq-response-headers
|
|
1183
1252
|
functionOptions:
|
|
1184
|
-
mandatory-headers: "x-api-key
|
|
1185
|
-
allowed-headers: "x-api-key,
|
|
1253
|
+
mandatory-headers: "x-api-key"
|
|
1254
|
+
allowed-headers: "x-api-key,traceId,dateTime"
|
|
1255
|
+
excluded-response-codes: "204"
|
|
1256
|
+
path-exclusions: "/status"
|
|
1186
1257
|
apiq:OAR115:
|
|
1187
1258
|
description: "All fields listed in the required array must be defined in the schema properties."
|
|
1188
|
-
message: "
|
|
1259
|
+
message: "OAR115: All fields in the required array must be defined in schema properties."
|
|
1189
1260
|
documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR115.md"
|
|
1190
1261
|
severity: warn
|
|
1191
1262
|
given:
|
|
@@ -8,7 +8,8 @@ module.exports = (given, { except }, context) => {
|
|
|
8
8
|
const paths = given || [];
|
|
9
9
|
if (paths.length === 0) return result;
|
|
10
10
|
|
|
11
|
-
const parts = paths.substr(1).split('/');
|
|
11
|
+
const parts = paths.substr(1).split('/').filter(p => p.length > 0);
|
|
12
|
+
if (parts.length === 0) return result;
|
|
12
13
|
let previousIsVar = isVariable(parts.shift());
|
|
13
14
|
if (previousIsVar) {
|
|
14
15
|
return [{
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module.exports = (schema, options = {}, context) => {
|
|
2
|
+
const results = [];
|
|
3
|
+
|
|
4
|
+
if (!schema || typeof schema !== 'object') {
|
|
5
|
+
return results;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Fields that must have byte or binary format
|
|
9
|
+
const fieldsToCheck = (options['fields-to-apply'] || 'product,line,price')
|
|
10
|
+
.split(',')
|
|
11
|
+
.map(f => f.trim().toLowerCase());
|
|
12
|
+
|
|
13
|
+
const properties = schema.properties || {};
|
|
14
|
+
|
|
15
|
+
Object.entries(properties).forEach(([propName, propSchema]) => {
|
|
16
|
+
// Only check if this property is in our list
|
|
17
|
+
if (!fieldsToCheck.includes(propName.toLowerCase())) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Check if it's a string type
|
|
22
|
+
if (propSchema.type !== 'string') {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check if format is byte or binary
|
|
27
|
+
const format = propSchema.format || '';
|
|
28
|
+
if (format && format !== 'byte' && format !== 'binary') {
|
|
29
|
+
results.push({
|
|
30
|
+
message: context.rule.message
|
|
31
|
+
.replace('{{property}}', propName)
|
|
32
|
+
.replace('{{format}}', format || 'undefined'),
|
|
33
|
+
path: [...context.path, 'properties', propName],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return results;
|
|
39
|
+
};
|