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 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].responses[*]"
123
+ given: "$.paths[*][get,post,put,patch,delete]"
114
124
  then:
115
- field: "content['application/json']"
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: "OAR014: The number of parts of the path must be less than 4."
155
- severity: warn
156
- given: "$.paths.*~"
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: pattern
168
+ function: apq-path-depth
159
169
  functionOptions:
160
- notMatch: "^(?:\\/[^\\/]+){4,}"
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 all operations."
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[*].get.parameters"
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 all operations."
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[*].get.parameters"
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 all operations."
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[*].get.parameters"
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[*].get.parameters"
300
+ given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/))].get"
275
301
  then:
276
- field: "$[?(@.name == '$orderby' && @.in == 'query')]"
277
- function: truthy
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[*].get.parameters"
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[*].get.parameters"
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[*].get.parameters"
343
+ given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/) && !@property.match(/status|health|ping/))].get"
299
344
  then:
300
- field: "$[?(@.name == '$limit' && @.in == 'query')]"
301
- function: truthy
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[*].get.parameters"
391
+ given: "$.paths[?(!@property.match(/\\/me(\\/|$)/) && !@property.match(/\\/\\{[^}]+\\}$/) && !@property.match(/status|health|ping/))].get"
323
392
  then:
324
- field: "$[?(@.name == '$filter' && @.in == 'query')]"
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 path '/status' must be declared."
362
- message: "OAR030: The path '/status' must be declared."
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
- field: "/status"
367
- function: truthy
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
- field: "name"
385
- function: pattern
454
+ function: apq-check-ambiguous-path
386
455
  functionOptions:
387
- notMatch: "^.*elements|instances|resources|values|terms|objects|items|elements|instances|resources|values.*$"
456
+ ambiguous-words: "elements,instances,resources,values,terms,objects,items"
388
457
  apiq:OAR033:
389
- description: "Headers 'Accept', 'Content-Type', and 'Authorization' are not allowed."
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 not compliant with the standard may cause application issues."
400
- message: "OAR034: Paged response schema must be compliant with the standard."
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[*][*].responses[?(@property != '204' && @property != '304' && @property != 'default')]"
471
+ given: "$.paths[*][get].responses[?(@property != '204' && @property != '304' && @property != 'default')]..schema"
403
472
  then:
404
- field: content
405
- function: truthy
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[*][*].responses"
480
+ given: "$.paths[*][*]"
411
481
  then:
412
- function: truthy
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 ipv6)."
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['/pets'].get"
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: "Provide a different summary and description for each operation."
600
- message: "OAR051: Provide a different summary and description for each operation."
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: "There are mandatory response headers and others that are not allowed."
620
- message: "{{error}}"
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,x-request-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,/prueba"
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[?(!(@property === '/status' || @property === '/another'))].get.responses"
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[?(!( @property == '/status' || @property == '/another' ))].post.responses"
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[?(!( @property == '/status' || @property == '/another' ))].put.responses"
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[?(!( @property == '/status' || @property == '/another' ))].patch.responses"
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[?(!( @property == '/status' || @property == '/another' ))].delete.responses"
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: "$.paths.*.*[responses,requestBody]..content..schema..properties.*~"
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: "Any param in PATH or QUERY should have a Bad Request (400) response."
750
- message: "OAR069: Any param in PATH or QUERY should have a Bad Request (400) response."
751
- severity: "warn"
752
- given: "$.paths[*][*].responses"
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: truthy
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 the /status path."
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[?(@ !== '/status')][*].responses"
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: "{{error}}"
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[?(@.type == 'string' && @.format == 'number')]"
971
+ given: "$..properties"
901
972
  then:
902
- field: "format"
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: The string properties must define a 'byte' or 'binary' format."
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
- field: format
913
- function: pattern
980
+ function: apq-binary-format-check
914
981
  functionOptions:
915
- match: "^(byte|binary)$"
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['/sample'].get.responses['200'].$ref"
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: "$.servers[*].url"
1132
+ given:
1133
+ - "$.servers[*].url"
1134
+ - "$.basePath"
1066
1135
  then:
1067
1136
  function: pattern
1068
1137
  functionOptions:
1069
- match: "^https?://[^/]+/.*/v[0-9]+$"
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: "{{error}}"
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: "There are mandatory response headers and others that are not allowed."
1177
- message: "{{error}}"
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,x-trace-id"
1185
- allowed-headers: "x-api-key,x-trace-id,traceId,dateTime"
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: "{{error}}"
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
+ };