api-quality-spectral-ruleset 1.4.1 → 1.5.0-beta.2

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
@@ -30,6 +30,10 @@ functions:
30
30
  - apq-status-endpoint-check
31
31
  - apq-validate-structure
32
32
  - apq-numeric-parameter-integrity
33
+ - apq-path-pattern
34
+ - apq-query-params-optional
35
+ - apq-wso2-scopes-valid
36
+ - apq-numeric-path-param
33
37
  extends:
34
38
  - spectral:asyncapi
35
39
  - spectral:oas
@@ -46,14 +50,12 @@ rules:
46
50
  match: "^https://"
47
51
  apiq:OAR002:
48
52
  description: "A wrong scope definition may cause problems to import the API definition into WSO2."
49
- message: "OAR002: Scope definition must include the 'key' attribute along with 'name' and 'roles'."
53
+ message: "{{error}}"
50
54
  documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR002.md"
51
55
  severity: "error"
52
- given: "$.x-wso2-security.apim.x-wso2-scopes[*]"
56
+ given: "$.x-wso2-security.apim.x-wso2-scopes"
53
57
  then:
54
- - function: "truthy"
55
- field: "key"
56
- message: "Scope must define a 'key' attribute."
58
+ function: apq-wso2-scopes-valid
57
59
  apiq:OAR003:
58
60
  description: "A description can help other developers to understand the correct use of the scope."
59
61
  message: "OAR003: Scope must define a 'description' attribute."
@@ -194,7 +196,7 @@ rules:
194
196
  message: "OAR016: Numeric types must use a valid format for their type."
195
197
  documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR016.md"
196
198
  severity: error
197
- given: "$..[?(@ && (@.type=='number' || @.type=='integer'))]"
199
+ given: "$..[?(@ && (@.type=='number' || @.type=='integer' || (@.type && @.type.indexOf && (@.type.indexOf('number') > -1 || @.type.indexOf('integer') > -1))))]"
198
200
  then:
199
201
  function: schema
200
202
  functionOptions:
@@ -485,7 +487,7 @@ rules:
485
487
  message: "OAR037: String schemas must specify a valid format (date, date-time, password, byte, binary, email, uuid, uri, hostname, ipv4, ipv6, HEX, HEX(16), json, xml, or base64), or a valid pattern when no format is defined."
486
488
  severity: error
487
489
  resolved: false
488
- given: "$..[?(@ && @.type=='string')]"
490
+ given: "$..[?(@ && @.type)]"
489
491
  then:
490
492
  function: apq-schema-format
491
493
  apiq:OAR038:
@@ -686,7 +688,7 @@ rules:
686
688
  message: "OAR052: Numeric types requires a format"
687
689
  documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR052.md"
688
690
  severity: warn
689
- given: "$..[?(@ && (@.type == 'integer' || @.type == 'number'))]"
691
+ given: "$..[?(@ && (@.type == 'integer' || @.type == 'number' || (@.type && @.type.indexOf && (@.type.indexOf('integer') > -1 || @.type.indexOf('number') > -1))))]"
690
692
  then:
691
693
  field: format
692
694
  function: truthy
@@ -716,12 +718,18 @@ rules:
716
718
  match: ^(http(s)?:\/\/.)[-a-zA-Z0-9@:%._\+~#=]{2,256}\.apiquality.io\b([-a-zA-Z0-9@:%_\+.~#?&\/=]*)$
717
719
  apiq:OAR060:
718
720
  description: "All query parameters must be defined as optional."
719
- message: "OAR060: All query parameter must be optional (required: false)."
721
+ message: "{{error}}"
720
722
  severity: error
721
- given: "$.paths[*][get,post,put,patch,delete].parameters[?(@.in == 'query')]"
723
+ given:
724
+ - "$.paths[*][get,put,post,delete,options,head,patch,trace].parameters[?(@.in == 'query')]"
725
+ - "$.paths[*].parameters[?(@.in == 'query')]"
726
+ - "$.components.parameters[?(@.in == 'query')]"
727
+ - "$.parameters[?(@.in == 'query')]"
722
728
  then:
723
729
  field: "required"
724
- function: falsy
730
+ function: apq-query-params-optional
731
+ functionOptions:
732
+ path-exclusions: "/status"
725
733
  apiq:OAR061:
726
734
  description: "Ensure get have mandatory response codes"
727
735
  message: "OAR061: Ensure get have the mandatory response codes"
@@ -836,10 +844,7 @@ rules:
836
844
  severity: error
837
845
  given: "$.paths[*][get,post,put,patch,delete].parameters[?(@.in == 'path')]"
838
846
  then:
839
- field: "schema.type"
840
- function: pattern
841
- functionOptions:
842
- match: "^(?!integer|number)"
847
+ function: apq-numeric-path-param
843
848
  apiq:OAR071:
844
849
  description: "Query parameters 'param1', 'param2', and 'param3' must be defined in the operation."
845
850
  message: "OAR071: Query parameters 'param1', 'param2', and 'param3' must be defined."
@@ -876,15 +881,15 @@ rules:
876
881
  severity: error
877
882
  resolved: false
878
883
  given:
879
- - "$..[?(@ && @.in && @.schema && (@.schema.type == 'integer' || @.schema.type == 'number'))].schema"
880
- - "$..[?(@ && @.in && (@.type == 'integer' || @.type == 'number'))]"
884
+ - "$..[?(@ && @.in && @.schema && (@.schema.type == 'integer' || @.schema.type == 'number' || (@.schema.type && @.schema.type.indexOf && (@.schema.type.indexOf('integer') > -1 || @.schema.type.indexOf('number') > -1))))].schema"
885
+ - "$..[?(@ && @.in && (@.type == 'integer' || @.type == 'number' || (@.type && @.type.indexOf && (@.type.indexOf('integer') > -1 || @.type.indexOf('number') > -1))))]"
881
886
  then:
882
887
  function: apq-numeric-parameter-integrity
883
888
  apiq:OAR075:
884
889
  description: "String parameters should have minLength, maxLength, pattern (regular expression), or enum restriction."
885
890
  message: "OAR075: String parameters should have minLength, maxLength, pattern, or enum restriction."
886
891
  severity: error
887
- given: "$.paths[*][get,post,put,patch,delete].parameters[?(@ && @.schema && @.schema.type == 'string' && @.in == 'path')]"
892
+ given: "$.paths[*][get,post,put,patch,delete].parameters[?(@ && @.schema && (@.schema.type == 'string' || (@.schema.type && @.schema.type.indexOf && @.schema.type.indexOf('string') > -1)) && @.in == 'path')]"
888
893
  then:
889
894
  function: schema
890
895
  functionOptions:
@@ -902,7 +907,7 @@ rules:
902
907
  message: "OAR076: Schema should use well-defined type and format."
903
908
  documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR076.md"
904
909
  severity: error
905
- given: "$..[?(@ && (@.type=='number' || @.type=='integer'))]"
910
+ given: "$..[?(@ && (@.type=='number' || @.type=='integer' || (@.type && @.type.indexOf && (@.type.indexOf('number') > -1 || @.type.indexOf('integer') > -1))))]"
906
911
  then:
907
912
  function: schema
908
913
  functionOptions:
@@ -1000,7 +1005,7 @@ rules:
1000
1005
  functionOptions:
1001
1006
  notMatch: "^password$"
1002
1007
  apiq:OAR085:
1003
- description: "The OpenAPI version must be one of: 2.0, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.1.0, 3.2.0."
1008
+ description: "The OpenAPI version must be one of: 2.0, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.0.4, 3.1.0, 3.1.1, 3.1.2, 3.2.0."
1004
1009
  message: "{{error}}"
1005
1010
  severity: warn
1006
1011
  given:
@@ -1009,7 +1014,7 @@ rules:
1009
1014
  then:
1010
1015
  function: apq-valid-openapi-version
1011
1016
  functionOptions:
1012
- valid-versions: "2.0,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.2.0"
1017
+ valid-versions: "2.0,3.0.0,3.0.1,3.0.2,3.0.3,3.0.4,3.1.0,3.1.1,3.1.2,3.2.0"
1013
1018
  apiq:OAR086:
1014
1019
  description: "Descriptions must begin with a capital letter, end with a period, and not be empty."
1015
1020
  message: "OAR086: Descriptions must begin with a capital letter, end with a period, and not be empty."
@@ -1264,4 +1269,14 @@ rules:
1264
1269
  - "$.components.schemas[*]"
1265
1270
  - "$.definitions[*]"
1266
1271
  then:
1267
- function: apq-required-fields-exist
1272
+ function: apq-required-fields-exist
1273
+ apiq:OAR116:
1274
+ description: "Every API path must match the configured regular expression."
1275
+ message: "{{error}}"
1276
+ documentationUrl: "https://github.com/apiaddicts/apquality-spectral/blob/main/docs/resources/OAR116.md"
1277
+ severity: error
1278
+ given: "$.paths.*~"
1279
+ then:
1280
+ function: apq-path-pattern
1281
+ functionOptions:
1282
+ pattern: "^/"
@@ -5,7 +5,6 @@ module.exports = (schema, options = {}, context) => {
5
5
  return results;
6
6
  }
7
7
 
8
- // Fields that must have byte or binary format
9
8
  const fieldsToCheck = (options['fields-to-apply'] || 'product,line,price')
10
9
  .split(',')
11
10
  .map(f => f.trim().toLowerCase());
@@ -13,7 +12,6 @@ module.exports = (schema, options = {}, context) => {
13
12
  const properties = schema.properties || {};
14
13
 
15
14
  Object.entries(properties).forEach(([propName, propSchema]) => {
16
- // Only check if this property is in our list
17
15
  if (!fieldsToCheck.includes(propName.toLowerCase())) {
18
16
  return;
19
17
  }
@@ -22,13 +20,17 @@ module.exports = (schema, options = {}, context) => {
22
20
  return;
23
21
  }
24
22
 
25
- // Check if it's a string type
26
- if (propSchema.type !== 'string') {
23
+ const type = propSchema.type;
24
+ const isStringType = type === 'string' || (Array.isArray(type) && type.includes('string'));
25
+ if (!isStringType) {
27
26
  return;
28
27
  }
29
28
 
30
29
  const format = propSchema.format || '';
31
- if (format !== 'byte' && format !== 'binary') {
30
+ const hasBinaryFormat = format === 'byte' || format === 'binary';
31
+ const hasContentEncoding = propSchema.contentEncoding !== undefined
32
+ || propSchema.contentMediaType !== undefined;
33
+ if (!hasBinaryFormat && !hasContentEncoding) {
32
34
  results.push({
33
35
  message: `OAR082: Property '${propName}' must define a 'byte' or 'binary' format (currently: ${format || 'missing'}).`,
34
36
  path: [...context.path, 'properties', propName, 'type'],
@@ -9,7 +9,9 @@ module.exports = (node, options, context) => {
9
9
  if (!node || typeof node !== 'object') return results;
10
10
 
11
11
  const { type } = node;
12
- if (type !== 'integer' && type !== 'number') return results;
12
+ const types = Array.isArray(type) ? type : [type];
13
+ const isNumeric = types.indexOf('integer') > -1 || types.indexOf('number') > -1;
14
+ if (!isNumeric) return results;
13
15
 
14
16
  const has = (field) => Object.prototype.hasOwnProperty.call(node, field);
15
17
  const hasMin = has('minimum');
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @param {object} param the parameter object (given selects params with `in: path`)
3
+ * @param {object} options
4
+ * @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
5
+ */
6
+ module.exports = (param, options, context) => {
7
+ const results = [];
8
+ if (!param || typeof param !== 'object' || param.in !== 'path') return results;
9
+
10
+ let typeVal;
11
+ let typePath;
12
+ if (param.schema && param.schema.type !== undefined) {
13
+ typeVal = param.schema.type;
14
+ typePath = [...context.path, 'schema', 'type'];
15
+ } else if (param.type !== undefined) {
16
+ typeVal = param.type;
17
+ typePath = [...context.path, 'type'];
18
+ } else {
19
+ return results;
20
+ }
21
+
22
+ const types = Array.isArray(typeVal) ? typeVal : [typeVal];
23
+ const isNumeric = types.indexOf('integer') > -1
24
+ || types.indexOf('number') > -1
25
+ || types.indexOf('float') > -1;
26
+ if (!isNumeric) return results;
27
+
28
+ const ruleCode = context.rule.name.split(':').pop();
29
+ results.push({
30
+ message: `${ruleCode}: Parameters in path should not be numeric.`,
31
+ path: typePath,
32
+ });
33
+ return results;
34
+ };
@@ -0,0 +1,14 @@
1
+ module.exports = (targetVal, options = {}) => {
2
+ const patternStr = (options && options.pattern) || '^/';
3
+
4
+ if (typeof targetVal !== 'string') {
5
+ return [];
6
+ }
7
+
8
+ const regex = new RegExp(patternStr);
9
+ if (!regex.test(targetVal)) {
10
+ return [{ message: `OAR116: Path does not match the required pattern: ${patternStr}` }];
11
+ }
12
+
13
+ return [];
14
+ };
@@ -0,0 +1,182 @@
1
+ const HTTP_VERBS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
2
+
3
+ // Guards against a cycle in a `$ref` chain (A -> B -> A).
4
+ const MAX_REF_DEPTH = 10;
5
+
6
+ // The `$ref` usage map depends only on the source document, so cache it per document instead of
7
+ // rebuilding it on every matched parameter.
8
+ const usageCache = new WeakMap();
9
+
10
+ const issue = () => [{ message: 'OAR060: All query parameter must be optional (required: false).' }];
11
+
12
+ function parseExclusions(value) {
13
+ return new Set(
14
+ String(value === undefined || value === null ? '' : value)
15
+ .split(',')
16
+ .map((p) => p.trim())
17
+ .filter(Boolean),
18
+ );
19
+ }
20
+
21
+ /**
22
+ * Split a local JSON pointer (`#/components/parameters/Foo`) into its decoded segments.
23
+ * Returns null for external refs and for anything that is not a document-local pointer.
24
+ */
25
+ function refToSegments(ref) {
26
+ if (typeof ref !== 'string' || !ref.startsWith('#/')) {
27
+ return null;
28
+ }
29
+ return ref.slice(2).split('/').map((raw) => {
30
+ let segment = raw;
31
+ try {
32
+ segment = decodeURIComponent(raw);
33
+ } catch (e) {
34
+ segment = raw;
35
+ }
36
+ return segment.replace(/~1/g, '/').replace(/~0/g, '~');
37
+ });
38
+ }
39
+
40
+ function getAt(doc, segments) {
41
+ return segments.reduce(
42
+ (node, segment) => (node !== null && typeof node === 'object' ? node[segment] : undefined),
43
+ doc,
44
+ );
45
+ }
46
+
47
+ const keyOf = (segments) => JSON.stringify(segments);
48
+
49
+ /**
50
+ * The two containers the rule's `given` scans for shared parameter definitions:
51
+ * `components.parameters.<name>` (OpenAPI 3) and `parameters.<name>` (OpenAPI 2).
52
+ */
53
+ function definitionSegments(path) {
54
+ if (path[0] === 'components' && path[1] === 'parameters' && path.length >= 3) {
55
+ return path.slice(0, 3);
56
+ }
57
+ if (path[0] === 'parameters' && path.length >= 2) {
58
+ return path.slice(0, 2);
59
+ }
60
+ return null;
61
+ }
62
+
63
+ const isSharedDefinitionRef = (segments) => segments !== null
64
+ && ((segments.length === 3 && segments[0] === 'components' && segments[1] === 'parameters')
65
+ || (segments.length === 2 && segments[0] === 'parameters'));
66
+
67
+ /** Map of shared-definition pointer -> set of API paths that reference it through a `$ref`. */
68
+ function buildRefUsages(doc) {
69
+ const usages = new Map();
70
+ const paths = doc && typeof doc === 'object' ? doc.paths : undefined;
71
+ if (!paths || typeof paths !== 'object') {
72
+ return usages;
73
+ }
74
+
75
+ Object.keys(paths).forEach((apiPath) => {
76
+ const pathItem = paths[apiPath];
77
+ if (!pathItem || typeof pathItem !== 'object') {
78
+ return;
79
+ }
80
+
81
+ const parameterLists = [pathItem.parameters];
82
+ HTTP_VERBS.forEach((verb) => {
83
+ const operation = pathItem[verb];
84
+ if (operation && typeof operation === 'object') {
85
+ parameterLists.push(operation.parameters);
86
+ }
87
+ });
88
+
89
+ parameterLists.forEach((list) => {
90
+ if (!Array.isArray(list)) {
91
+ return;
92
+ }
93
+ list.forEach((parameter) => {
94
+ let current = parameter;
95
+ for (let depth = 0; depth < MAX_REF_DEPTH; depth += 1) {
96
+ if (!current || typeof current !== 'object' || typeof current.$ref !== 'string') {
97
+ break;
98
+ }
99
+ const segments = refToSegments(current.$ref);
100
+ if (segments === null) {
101
+ break;
102
+ }
103
+ const key = keyOf(segments);
104
+ if (!usages.has(key)) {
105
+ usages.set(key, new Set());
106
+ }
107
+ usages.get(key).add(apiPath);
108
+ const next = getAt(doc, segments);
109
+ if (!next || next === current) {
110
+ break;
111
+ }
112
+ current = next;
113
+ }
114
+ });
115
+ });
116
+ });
117
+
118
+ return usages;
119
+ }
120
+
121
+ function refUsagesFor(doc) {
122
+ if (!doc || typeof doc !== 'object') {
123
+ return new Map();
124
+ }
125
+ let cached = usageCache.get(doc);
126
+ if (cached === undefined) {
127
+ cached = buildRefUsages(doc);
128
+ usageCache.set(doc, cached);
129
+ }
130
+ return cached;
131
+ }
132
+
133
+ /**
134
+ * True when the match landed on a `$ref` to a shared definition the rule also scans on its own —
135
+ * either a use site under `paths`, or one shared definition aliasing another.
136
+ *
137
+ * Spectral runs the rule over the resolved document, so it matches such a parameter once here and
138
+ * once at the definition it points to. Reporting is left to the definition-site match, which is
139
+ * also the only place Sonar reports it (its AST visits the definition, never the use sites).
140
+ */
141
+ function isRefToSharedDefinition(source, path) {
142
+ const parameterPath = path[path.length - 1] === 'required' ? path.slice(0, -1) : path;
143
+ const node = getAt(source, parameterPath);
144
+ if (!node || typeof node !== 'object' || typeof node.$ref !== 'string') {
145
+ return false;
146
+ }
147
+ return isSharedDefinitionRef(refToSegments(node.$ref));
148
+ }
149
+
150
+ module.exports = (targetVal, options = {}, context = {}) => {
151
+ const exclusions = parseExclusions((options || {})['path-exclusions']);
152
+ const path = (context && context.path) || [];
153
+ const source = context && context.document ? context.document.data : undefined;
154
+
155
+ if (isRefToSharedDefinition(source, path)) {
156
+ return [];
157
+ }
158
+
159
+ if (path[0] === 'paths') {
160
+ const apiPath = path.find((p) => typeof p === 'string' && p.startsWith('/'));
161
+ if (apiPath && exclusions.has(apiPath)) {
162
+ return [];
163
+ }
164
+ } else if (exclusions.size > 0) {
165
+ // A shared definition has no path of its own: the AST/JSONPath match lands on
166
+ // `components.parameters.<name>` (or `parameters.<name>` in OpenAPI 2). Exclude it only when
167
+ // every path that references it is excluded — an unreferenced definition stays in scope.
168
+ const definition = definitionSegments(path);
169
+ const usages = definition === null
170
+ ? undefined
171
+ : refUsagesFor(source).get(keyOf(definition));
172
+ if (usages !== undefined && usages.size > 0 && [...usages].every((u) => exclusions.has(u))) {
173
+ return [];
174
+ }
175
+ }
176
+
177
+ if (targetVal === true || targetVal === 'true') {
178
+ return issue();
179
+ }
180
+
181
+ return [];
182
+ };
@@ -6,7 +6,10 @@
6
6
  module.exports = (given, options, context) => {
7
7
  const results = [];
8
8
 
9
- if (!given || given.type !== 'object') return results;
9
+ if (!given) return results;
10
+ const t = given.type;
11
+ const isObjectType = t === 'object' || (Array.isArray(t) && t.indexOf('object') > -1);
12
+ if (!isObjectType) return results;
10
13
 
11
14
  const { required, properties } = given;
12
15
  if (!Array.isArray(required)) return results;
@@ -16,6 +16,12 @@ function isValidPattern(pattern) {
16
16
  module.exports = (targetVal, _options, context) => {
17
17
  const typePath = [...context.path, 'type'];
18
18
 
19
+ const type = targetVal.type;
20
+ const isStringType = type === 'string' || (Array.isArray(type) && type.includes('string'));
21
+ if (!isStringType) {
22
+ return [];
23
+ }
24
+
19
25
  const format = targetVal.format;
20
26
  if (format !== undefined && format !== null) {
21
27
  if (!VALID_FORMATS.has(String(format).toLowerCase())) {
@@ -1,4 +1,4 @@
1
- const DEFAULT_VALID_VERSIONS = '2.0,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.2.0';
1
+ const DEFAULT_VALID_VERSIONS = '2.0,3.0.0,3.0.1,3.0.2,3.0.3,3.0.4,3.1.0,3.1.1,3.1.2,3.2.0';
2
2
 
3
3
  /**
4
4
  * @param {string} targetVal
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @param {*} targetVal the `x-wso2-scopes` container node value
3
+ * @param {object} _options unused
4
+ * @param {import('@stoplight/spectral-core').RulesetFunctionContext} context
5
+ */
6
+ const DEFINITION_WRONG = "OAR002: WSO2 scopes definition is wrong";
7
+ const REQUIRED_PROP = "OAR002: WSO2 scope '{0}' is required";
8
+ const REQUIRED_PROPERTIES = ['name', 'key', 'roles'];
9
+
10
+ const requiredMessage = (prop) => REQUIRED_PROP.replace('{0}', prop);
11
+
12
+ // A scope property counts as "required but missing" when it is absent, null, a
13
+ // blank string, or an empty collection ([] / {}) — mirrors Sonar's OAR002 check.
14
+ const isEmptyValue = (value) =>
15
+ value === null
16
+ || (typeof value === 'string' && value.trim() === '')
17
+ || (Array.isArray(value) && value.length === 0)
18
+ || (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0);
19
+
20
+ module.exports = (targetVal, _options, context) => {
21
+ const errors = [];
22
+ const basePath = context.path || [];
23
+
24
+ if (targetVal === null || typeof targetVal !== 'object') {
25
+ errors.push({ message: DEFINITION_WRONG, path: basePath });
26
+ return errors;
27
+ }
28
+
29
+ const entries = Array.isArray(targetVal)
30
+ ? targetVal.map((scope, index) => [index, scope])
31
+ : Object.keys(targetVal).map((key) => [key, targetVal[key]]);
32
+
33
+ if (entries.length === 0) {
34
+ errors.push({ message: DEFINITION_WRONG, path: basePath });
35
+ return errors;
36
+ }
37
+
38
+ entries.forEach(([key, scope]) => {
39
+ const scopePath = [...basePath, key];
40
+
41
+ if (scope === null || typeof scope !== 'object' || Array.isArray(scope)) {
42
+ REQUIRED_PROPERTIES.forEach((prop) => {
43
+ errors.push({ message: requiredMessage(prop), path: scopePath });
44
+ });
45
+ return;
46
+ }
47
+
48
+ REQUIRED_PROPERTIES.forEach((prop) => {
49
+ if (!Object.prototype.hasOwnProperty.call(scope, prop)) {
50
+ errors.push({ message: requiredMessage(prop), path: scopePath });
51
+ } else if (isEmptyValue(scope[prop])) {
52
+ errors.push({ message: requiredMessage(prop), path: [...scopePath, prop] });
53
+ }
54
+ });
55
+ });
56
+
57
+ return errors;
58
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-quality-spectral-ruleset",
3
- "version": "1.4.1",
3
+ "version": "1.5.0-beta.2",
4
4
  "description": "Spectral ruleset by API Quality",
5
5
  "main": "apq-spectral.yaml",
6
6
  "files": [