api-quality-spectral-ruleset 1.0.0 → 1.1.1

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
@@ -7,6 +7,12 @@ functions:
7
7
  - apq-resources-by-verb
8
8
  - apq-at-most-one-body-parameter
9
9
  - apq-standard-response-codes
10
+ - apq-response-headers
11
+ - apq-require-response-on-path-params
12
+ - apq-required-fields-exist
13
+ - apq-security-check
14
+ - apq-custom-field
15
+ - apq-check-examples-coverage
10
16
  extends:
11
17
  - spectral:asyncapi
12
18
  - spectral:oas
@@ -111,12 +117,15 @@ rules:
111
117
  apiq:OAR011:
112
118
  description: "URLs should follow the specified naming convention: all literal path segments must be in kebab-case."
113
119
  message: "OAR011: The resource names with more than two words must be compliant with the standard naming convention (kebab-case)."
120
+ documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR011.md"
114
121
  severity: error
115
- given: "$.paths[*]~"
122
+ given:
123
+ - "$.paths[*]~"
124
+ - "$.servers[*].url"
116
125
  then:
117
126
  function: pattern
118
127
  functionOptions:
119
- match: "^(\/|[a-z0-9-]+|{[a-zA-Z0-9_|-]+})+$"
128
+ match: "^(https?:\\/\\/[^\\/]+)?(\\/(?:[a-z0-9]+(?:-[a-z0-9]+)*|\\{[^}]+\\}))*\\/?$"
120
129
  apiq:OAR012:
121
130
  description: "Path params, query params, object names and property names should follow the configured naming convention. You can configure snake_case (default), kebab-case, camelCase or UpperCamelCase"
122
131
  message: "OAR012: Path params, query params, object names and property names must follow the configured naming convention."
@@ -163,24 +172,29 @@ rules:
163
172
  message: "OAR016: Numeric types must use a valid format for their type."
164
173
  documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR016.md"
165
174
  severity: error
166
- given:
167
- - "$..[?(@.type == 'integer' && @.format)]"
168
- - "$..[?(@.type == 'number' && @.format)]"
175
+ given: "$..[?(@.type=='number' || @.type=='integer')]"
169
176
  then:
170
177
  function: schema
171
178
  functionOptions:
172
179
  schema:
173
- oneOf:
174
- - properties:
175
- type:
176
- const: integer
177
- format:
178
- enum: [int32, int64]
179
- - properties:
180
- type:
181
- const: number
182
- format:
183
- enum: [float, double]
180
+ required: [format]
181
+ allOf:
182
+ - if:
183
+ properties:
184
+ type:
185
+ const: integer
186
+ then:
187
+ properties:
188
+ format:
189
+ enum: [int32, int64]
190
+ - if:
191
+ properties:
192
+ type:
193
+ const: number
194
+ then:
195
+ properties:
196
+ format:
197
+ enum: [float, double]
184
198
  apiq:OAR017:
185
199
  description: "Resource path should alternate static and parametrized parts."
186
200
  message: "OAR017: Resource path should alternate static and parametrized parts."
@@ -353,14 +367,14 @@ rules:
353
367
  function: truthy
354
368
  apiq:OAR031:
355
369
  description: The examples can help developers to understand the response data structure and representation."
356
- message: "OAR031: Examples - Parameters, Request Body and Responses must have an examples defined."
370
+ message: "OAR031: Examples - Parameters, Request Body and Responses must have an examples defined."
357
371
  severity: error
358
372
  given:
359
373
  - $.paths.*.*.parameters.*
360
- - $.paths.*.*[responses,requestBody]..content.*
374
+ - $.paths.*.*.requestBody
375
+ - $.paths.*.*.responses[?(@property !== "204")]
361
376
  then:
362
- field: examples
363
- function: truthy
377
+ function: apq-check-examples-coverage
364
378
  apiq:OAR032:
365
379
  description: "Ambiguous path parts not encouraged."
366
380
  message: "OAR032: When reading a URL, it must be possible to interpret which resources it is referring to in a concrete way. Ambiguous names in resources must be avoided."
@@ -481,6 +495,17 @@ rules:
481
495
  then:
482
496
  field: "x-aut-type"
483
497
  function: truthy
498
+ apiq:OAR042:
499
+ description: "Base path must be compliant with the standard."
500
+ message: "OAR042: Base path must follow the '/api-<name>/v<version>' standard."
501
+ severity: "error"
502
+ given:
503
+ - "$.basePath"
504
+ - "$.servers[*].url"
505
+ then:
506
+ function: pattern
507
+ functionOptions:
508
+ match: "^(/api-[^/]+/v[0-9]+|https?:\\/\\/[^/]+\\/api-[^/]+\\/v[0-9]+)$"
484
509
  apiq:OAR044:
485
510
  description: "Declared media type should conform to RFC6838 and RFC7231."
486
511
  message: "OAR044: Declared media type range should conform to RFC7231."
@@ -590,6 +615,20 @@ rules:
590
615
  then:
591
616
  field: format
592
617
  function: truthy
618
+ apiq:OAR053:
619
+ description: "There are mandatory response headers and others that are not allowed."
620
+ message: "{{error}}"
621
+ documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR053.md"
622
+ severity: error
623
+ given: "$.paths[*][*].responses[*]"
624
+ then:
625
+ - function: apq-response-headers
626
+ functionOptions:
627
+ mandatory-headers: "x-trace-id,x-request-id"
628
+ allowed-headers: "idcorrelacion,x-correlacionid,x-global-trasaction-id,x-power-by,x-trace-id,x-request-id"
629
+ included-response-codes: "*"
630
+ excluded-response-codes: "204"
631
+ path-exclusions: "/status,/prueba"
593
632
  apiq:OAR054:
594
633
  description: "Ensure the host matches the specified format"
595
634
  message: "OAR054: Hostname must be a subdomain of the organization's domain name."
@@ -790,16 +829,32 @@ rules:
790
829
  - required: ["enum"]
791
830
  apiq:OAR076:
792
831
  description: "Schema should use well-defined type and format."
793
- message: "OAR076: Invalid combination: type '{{value.type}}' with format '{{value.format}}'"
794
- severity: "error"
795
- given: "$..[?(@.type=='number')]"
832
+ message: "OAR076: Schema should use well-defined type and format."
833
+ documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR076.md"
834
+ severity: error
835
+ given: "$..[?(@.type=='number' || @.type=='integer')]"
796
836
  then:
797
- field: format
798
- function: enumeration
837
+ function: schema
799
838
  functionOptions:
800
- values:
801
- - float
802
- - double
839
+ schema:
840
+ required: [format]
841
+ allOf:
842
+ - if:
843
+ properties:
844
+ type:
845
+ const: integer
846
+ then:
847
+ properties:
848
+ format:
849
+ enum: [int32, int64]
850
+ - if:
851
+ properties:
852
+ type:
853
+ const: number
854
+ then:
855
+ properties:
856
+ format:
857
+ enum: [float, double]
803
858
  apiq:OAR077:
804
859
  description: "All parameters in query must be snake_case."
805
860
  message: "OAR077: All parameters in query must be snake_case."
@@ -812,20 +867,22 @@ rules:
812
867
  match: ^\$?_?[a-z]+(_[a-z]+)*$
813
868
  apiq:OAR078:
814
869
  description: "All API methods must have security defined."
815
- message: "OAR078: The operation '{{property}}' must have security defined."
870
+ message: "{{error}}"
871
+ documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR078.md"
816
872
  severity: error
817
- given: "$.paths[*][get,post,put,patch,delete]"
873
+ given: "$"
818
874
  then:
819
- field: "security"
820
- function: truthy
875
+ function: apq-security-check
821
876
  apiq:OAR079:
822
877
  description: "Operations with path parameters should include a 404 Not Found response."
823
878
  message: "OAR079: Path parameter present, but missing 404 Not Found response."
879
+ documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR079.md"
824
880
  severity: "warn"
825
- given: "$.paths[*][*]"
881
+ given: "$.paths[*][get,post,put,patch,delete]"
826
882
  then:
827
- field: responses.404
828
- function: truthy
883
+ function: apq-require-response-on-path-params
884
+ functionOptions:
885
+ response: "404"
829
886
  apiq:OAR080:
830
887
  description: "The security scheme must be among those allowed by the organization and must be complete."
831
888
  message: "OAR080: The security scheme '{{property}}' must be among those allowed by the organization and must be complete."
@@ -934,9 +991,12 @@ rules:
934
991
  apiq:OAR091:
935
992
  description: "Parameters must contain only $ref references."
936
993
  message: "OAR091: Parameters must contain only $ref references."
994
+ documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR091.md"
937
995
  severity: error
938
- given: "$.paths[*][get,post,put,patch,delete].parameters[*].$ref"
996
+ resolved: false
997
+ given: "$.paths[*][get,post,put,patch,delete].parameters[*]"
939
998
  then:
999
+ field: $ref
940
1000
  function: truthy
941
1001
  apiq:OAR092:
942
1002
  description: "RequestBody must contain a $ref."
@@ -1101,19 +1161,35 @@ rules:
1101
1161
  then:
1102
1162
  field: "contact"
1103
1163
  function: truthy
1164
+ apiq:OAR113:
1165
+ description: "Field or extension must be at the assigned location"
1166
+ message: "{{error}}"
1167
+ documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR113.md"
1168
+ severity: warn
1169
+ given: "$"
1170
+ then:
1171
+ function: apq-custom-field
1172
+ functionOptions:
1173
+ fieldName: "x-custom-example"
1174
+ fieldLocation: "path,operation_get,response_200"
1104
1175
  apiq:OAR114:
1105
- description: There are mandatory response headers and others that are not allowed.
1106
- message: "OAR114: Missing mandatory response x-api-key and x-trace-id headers, or forbidden headers are present."
1107
- documentationUrl: https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR114.md
1108
- severity: error
1109
- given: "$.paths[*][*].responses[*].headers"
1176
+ description: "There are mandatory response headers and others that are not allowed."
1177
+ message: "{{error}}"
1178
+ documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR114.md"
1179
+ severity: "error"
1180
+ given: "$.paths[*][*].responses[*]"
1110
1181
  then:
1111
- - function: schema
1182
+ - function: apq-response-headers
1112
1183
  functionOptions:
1113
- schema:
1114
- type: object
1115
- required:
1116
- - x-api-key
1117
- - x-trace-id
1118
- propertyNames:
1119
- pattern: "^(x-api-key|x-trace-id|traceId|dateTime)$"
1184
+ mandatory-headers: "x-api-key,x-trace-id"
1185
+ allowed-headers: "x-api-key,x-trace-id,traceId,dateTime"
1186
+ apiq:OAR115:
1187
+ description: "All fields listed in the required array must be defined in the schema properties."
1188
+ message: "{{error}}"
1189
+ documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR115.md"
1190
+ severity: warn
1191
+ given:
1192
+ - "$.components.schemas[*]"
1193
+ - "$.definitions[*]"
1194
+ then:
1195
+ function: apq-required-fields-exist
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @param {object} given
3
+ * @param {object} options
4
+ * @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
5
+ */
6
+ module.exports = (given, options, context) => {
7
+ if (!given || typeof given !== 'object') return [];
8
+
9
+ const hasExample = (node) => {
10
+ if (!node || typeof node !== 'object') return false;
11
+
12
+ if (node.example !== undefined || node.examples !== undefined) return true;
13
+
14
+ if (node.content && typeof node.content === 'object') {
15
+ return Object.values(node.content).some(mediaType => hasExample(mediaType));
16
+ }
17
+
18
+ if (node.schema) {
19
+ return checkSchema(node.schema);
20
+ }
21
+
22
+ if (node.properties || node.items || node.type) {
23
+ return checkSchema(node);
24
+ }
25
+
26
+ return false;
27
+ };
28
+
29
+ const checkSchema = (schema) => {
30
+ if (!schema || typeof schema !== 'object') return false;
31
+
32
+ if (schema.example !== undefined || schema.examples !== undefined) return true;
33
+
34
+ if (schema.properties && typeof schema.properties === 'object') {
35
+ return Object.values(schema.properties).some(prop => checkSchema(prop));
36
+ }
37
+
38
+ if (schema.items) {
39
+ return checkSchema(schema.items);
40
+ }
41
+
42
+ return false;
43
+ };
44
+
45
+ if (!hasExample(given)) {
46
+ return [
47
+ {
48
+ message: context.rule.message
49
+ },
50
+ ];
51
+ }
52
+
53
+ return [];
54
+ };
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @param {object} given
3
+ * @param {object} options
4
+ * @param {string} options.fieldName
5
+ * @param {string} options.fieldLocation
6
+ * @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
7
+ */
8
+ module.exports = (given, options, context) => {
9
+ const { fieldName, fieldLocation } = options || {};
10
+ const ruleCode = context.rule.name.split(':').pop();
11
+
12
+ if (!given || !fieldName || !fieldLocation) return [];
13
+
14
+ const locations = fieldLocation.split(',').map(l => l.trim());
15
+ const results = [];
16
+
17
+ const hasField = node =>
18
+ node && typeof node === 'object' && Object.prototype.hasOwnProperty.call(node, fieldName);
19
+
20
+ const addError = path =>
21
+ results.push({
22
+ message: `${ruleCode}: Field or extension ${fieldName} must be at the assigned location`,
23
+ path,
24
+ });
25
+
26
+ if (hasField(given)) {
27
+ return [];
28
+ }
29
+
30
+ const rootLocations = ['path', 'components'];
31
+
32
+ const missingRoot = rootLocations.find(
33
+ loc => locations.includes(loc) && !given[loc === 'path' ? 'paths' : loc]
34
+ );
35
+
36
+ if (missingRoot) {
37
+ return [
38
+ {
39
+ message: `${ruleCode}: The location ${missingRoot} is not present.`,
40
+ path: [],
41
+ },
42
+ ];
43
+ }
44
+
45
+ const opLoc = locations.find(l => l.startsWith('operation_'));
46
+ const respLoc = locations.find(l => l.startsWith('response_'));
47
+
48
+ const verb = opLoc?.split('_')[1];
49
+ const status = respLoc?.split('_')[1];
50
+
51
+ Object.entries(given.paths || {}).forEach(([pathKey, pathItem]) => {
52
+ if (hasField(pathItem)) return;
53
+
54
+ if (!verb || !pathItem[verb]) return;
55
+
56
+ const operation = pathItem[verb];
57
+
58
+ if (hasField(operation)) return;
59
+
60
+ if (!operation.responses || !operation.responses[status]) return;
61
+
62
+ const response = operation.responses[status];
63
+
64
+ if (!hasField(response)) {
65
+ addError(['paths', pathKey, verb, 'responses', status]);
66
+ }
67
+ });
68
+
69
+ return results;
70
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Requires one or more HTTP response codes when path parameters are present.
3
+ *
4
+ * @param {object} given
5
+ * @param {object} options
6
+ * @param {string} options.response Comma-separated HTTP status codes (e.g. "400,404,200")
7
+ * @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
8
+ */
9
+ module.exports = (given, options, context) => {
10
+ if (!given || typeof given !== 'object') {
11
+ return [];
12
+ }
13
+
14
+ const ruleCode = context.rule?.name?.split(':').pop();
15
+ const responseOpt = options?.response;
16
+
17
+ if (typeof responseOpt !== 'string') {
18
+ throw new TypeError(`${ruleCode}: "response" option must be a comma-separated string of HTTP status codes`);
19
+ }
20
+
21
+ const requiredResponses = responseOpt
22
+ .split(',')
23
+ .map(r => r.trim())
24
+ .filter(r => /^\d{3}$/.test(r));
25
+
26
+ if (requiredResponses.length === 0) {
27
+ throw new Error(`${ruleCode}: No valid HTTP status codes found in "response" option`);
28
+ }
29
+
30
+ const pathItemKeyIndex = context.path?.length - 2;
31
+ const pathItem = typeof pathItemKeyIndex === 'number'
32
+ ? context.path?.[pathItemKeyIndex]
33
+ : null;
34
+
35
+ const pathParams = (pathItem?.parameters || []).filter(
36
+ p => p?.in === 'path'
37
+ );
38
+
39
+ const operationParams = (given.parameters || []).filter(
40
+ p => p?.in === 'path'
41
+ );
42
+
43
+ if (pathParams.length === 0 && operationParams.length === 0) {
44
+ return [];
45
+ }
46
+
47
+ const responses = given?.responses || {};
48
+
49
+ const hasRequiredResponse = requiredResponses.some(
50
+ code => responses[code]
51
+ );
52
+
53
+ if (!hasRequiredResponse) {
54
+ return [
55
+ {
56
+ message: context.rule.message,
57
+ }
58
+ ];
59
+ }
60
+
61
+ return [];
62
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @param {object} given
3
+ * @param {object} options
4
+ * @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
5
+ */
6
+ module.exports = (given, options, context) => {
7
+ const results = [];
8
+
9
+ if (!given || given.type !== 'object') return results;
10
+
11
+ const { required, properties } = given;
12
+ if (!Array.isArray(required)) return results;
13
+
14
+ const propertyNames = new Set(Object.keys(properties || {}));
15
+ const ruleCode = context.rule.name.split(':').pop();
16
+
17
+ required.forEach((field, index) => {
18
+ if (!propertyNames.has(field)) {
19
+ results.push({
20
+ message: `${ruleCode}: This value does not exist, '${field}' must be defined in the schema properties.`,
21
+ path: [...context.path, 'required', index]
22
+ });
23
+ }
24
+ });
25
+
26
+ return results;
27
+ };
@@ -0,0 +1,94 @@
1
+ module.exports = (responseNode, options = {}, context) => {
2
+ if (!responseNode || typeof responseNode !== 'object') {
3
+ return [];
4
+ }
5
+
6
+ const results = [];
7
+
8
+ const {
9
+ 'mandatory-headers': mandatoryHeadersOpt = '',
10
+ 'allowed-headers': allowedHeadersOpt = '',
11
+ 'included-response-codes': includedCodesOpt = '*',
12
+ 'excluded-response-codes': excludedCodesOpt = '',
13
+ 'path-exclusions': pathExclusionsOpt = '',
14
+ } = options;
15
+
16
+ const mandatoryHeaders = mandatoryHeadersOpt
17
+ .split(',')
18
+ .map(h => h.toLowerCase().trim())
19
+ .filter(Boolean);
20
+
21
+ const allowedHeaders = allowedHeadersOpt
22
+ .split(',')
23
+ .map(h => h.toLowerCase().trim())
24
+ .filter(Boolean);
25
+
26
+ const includedCodes = includedCodesOpt
27
+ .split(',')
28
+ .map(c => c.trim())
29
+ .filter(Boolean);
30
+
31
+ const excludedCodes = excludedCodesOpt
32
+ .split(',')
33
+ .map(c => c.trim())
34
+ .filter(Boolean);
35
+
36
+ const pathExclusions = pathExclusionsOpt
37
+ .split(',')
38
+ .map(p => p.trim())
39
+ .filter(Boolean);
40
+
41
+ const path = context.path?.find(p => typeof p === 'string' && p.startsWith('/'));
42
+ const responseCode = context.path?.[context.path.length - 1];
43
+
44
+ if (path && pathExclusions.includes(path)) {
45
+ return [];
46
+ }
47
+
48
+ if (excludedCodes.includes(String(responseCode))) {
49
+ return [];
50
+ }
51
+
52
+ if (!includedCodes.includes('*') && !includedCodes.includes(String(responseCode))) {
53
+ return [];
54
+ }
55
+
56
+ let headers = responseNode.headers;
57
+
58
+ if (!headers && responseNode.$ref && context.document?.resolved) {
59
+ try {
60
+ const ref = responseNode.$ref.replace(/^#\//, '').split('/');
61
+ let resolved = context.document.resolved;
62
+ for (const part of ref) {
63
+ resolved = resolved?.[part];
64
+ }
65
+ headers = resolved?.headers;
66
+ } catch (_) {
67
+ }
68
+ }
69
+
70
+ const headerNames = headers && typeof headers === 'object'
71
+ ? Object.keys(headers).map(h => h.toLowerCase().trim())
72
+ : [];
73
+ const ruleCode = context.rule.name.split(':').pop();
74
+
75
+ if (mandatoryHeaders.length > 0) {
76
+ const missing = mandatoryHeaders.filter(h => !headerNames.includes(h));
77
+ if (missing.length > 0) {
78
+ results.push({
79
+ message: `${ruleCode}: Headers [${missing.join(', ')}] are required`,
80
+ });
81
+ }
82
+ }
83
+
84
+ if (allowedHeaders.length > 0 && headerNames.length > 0) {
85
+ const forbidden = headerNames.filter(h => !allowedHeaders.includes(h));
86
+ if (forbidden.length > 0) {
87
+ results.push({
88
+ message: `${ruleCode}: Header not allowed`,
89
+ });
90
+ }
91
+ }
92
+
93
+ return results;
94
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @param {object} given
3
+ * @param {object} options
4
+ * @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
5
+ */
6
+ module.exports = (given, options, context) => {
7
+ if (!given) return [];
8
+
9
+ const results = [];
10
+ const ruleCode = context.rule.name.split(':').pop();
11
+
12
+ const globalSecurity = given.security ?? given.swagger?.security;
13
+ if (globalSecurity) {
14
+ return results;
15
+ }
16
+
17
+ const methods = ['get', 'post', 'put', 'patch', 'delete'];
18
+
19
+ const pathsObj = given.paths || {};
20
+ for (const [pathName, pathItem] of Object.entries(pathsObj)) {
21
+ for (const method of methods) {
22
+ const operation = pathItem[method];
23
+ if (!operation) continue;
24
+
25
+ if (!operation.security || operation.security.length === 0) {
26
+ results.push({
27
+ message: `${ruleCode}: The operation '${method} ${pathName}' must have security defined.`,
28
+ path: ['paths', pathName, method, 'security']
29
+ });
30
+ }
31
+ }
32
+ }
33
+
34
+ return results;
35
+ };
@@ -1,9 +1,14 @@
1
- module.exports = function apqStandardResponseCodes(targetVal, options, context) {
1
+ /**
2
+ * @param {object} given
3
+ * @param {object} options
4
+ * @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
5
+ */
6
+ module.exports = function apqStandardResponseCodes(given, options, context) {
2
7
  const results = [];
3
8
 
4
- if (!targetVal || !context?.path) return results;
9
+ if (!given || !context?.path) return results;
5
10
 
6
- const responses = targetVal.responses;
11
+ const responses = given.responses;
7
12
  if (!responses || typeof responses !== 'object') return results;
8
13
 
9
14
  const definedCodes = Object.keys(responses);
@@ -17,15 +22,15 @@ module.exports = function apqStandardResponseCodes(targetVal, options, context)
17
22
 
18
23
  const exclusions = options?.['resources-exclusions'] || [];
19
24
  if (exclusions.some(ex => {
20
- const [exVerb, exPath] = ex.split(':');
21
- return exVerb.toLowerCase() === verb.toLowerCase() && new RegExp(`^${exPath}$`).test(resourcePath);
22
- })) return results;
25
+ const [exVerb, exPath] = ex.split(':');
26
+ return exVerb.toLowerCase() === verb.toLowerCase() && new RegExp(`^${exPath}$`).test(resourcePath);
27
+ })) return results;
23
28
 
24
29
  const rulesConfig = options?.['required-codes-by-resources-paths'];
25
30
  if (!rulesConfig) return results;
26
31
 
27
32
  const rules = rulesConfig
28
- .split(/[\n;]/)
33
+ .split(/[\n;]/)
29
34
  .map(r => r.trim())
30
35
  .filter(Boolean)
31
36
  .map(rule => {
@@ -49,19 +54,19 @@ module.exports = function apqStandardResponseCodes(targetVal, options, context)
49
54
 
50
55
  if (!matchedRule) return results;
51
56
 
52
- matchedRule.requiredCodes.forEach(ruleCode => {
57
+ matchedRule.requiredCodes.forEach(respCode => {
53
58
  let missing = false;
54
59
  let msg = "";
55
60
 
56
- if (ruleCode.includes('|')) {
57
- const alternatives = ruleCode.split('|');
61
+ if (respCode.includes('|')) {
62
+ const alternatives = respCode.split('|');
58
63
  if (!alternatives.some(c => definedCodes.includes(c))) {
59
64
  missing = true;
60
65
  msg = `OAR039: Response code ${alternatives.join(' or ')} must be defined.`;
61
66
  }
62
- } else if (!definedCodes.includes(ruleCode)) {
67
+ } else if (!definedCodes.includes(respCode)) {
63
68
  missing = true;
64
- msg = `OAR039: Response code ${ruleCode} must be defined.`;
69
+ msg = `OAR039: Response code ${respCode} must be defined.`;
65
70
  }
66
71
 
67
72
  if (missing) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-quality-spectral-ruleset",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Spectral ruleset by API Quality",
5
5
  "main": "apq-spectral.yaml",
6
6
  "files": [