@redocly/openapi-core 1.0.0-beta.109 → 1.0.0-beta.111

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.
Files changed (88) hide show
  1. package/README.md +2 -2
  2. package/lib/config/config-resolvers.js +44 -25
  3. package/lib/config/config.d.ts +1 -0
  4. package/lib/config/config.js +1 -0
  5. package/lib/config/load.d.ts +8 -2
  6. package/lib/config/load.js +4 -2
  7. package/lib/config/types.d.ts +10 -0
  8. package/lib/config/utils.js +2 -2
  9. package/lib/rules/ajv.d.ts +1 -1
  10. package/lib/rules/ajv.js +5 -5
  11. package/lib/rules/common/assertions/asserts.d.ts +3 -5
  12. package/lib/rules/common/assertions/asserts.js +137 -97
  13. package/lib/rules/common/assertions/index.js +2 -6
  14. package/lib/rules/common/assertions/utils.d.ts +12 -6
  15. package/lib/rules/common/assertions/utils.js +33 -20
  16. package/lib/rules/common/no-ambiguous-paths.js +1 -1
  17. package/lib/rules/common/no-identical-paths.js +4 -4
  18. package/lib/rules/common/operation-2xx-response.js +2 -2
  19. package/lib/rules/common/operation-4xx-response.js +2 -2
  20. package/lib/rules/common/path-not-include-query.js +1 -1
  21. package/lib/rules/common/path-params-defined.js +7 -2
  22. package/lib/rules/common/response-contains-header.js +2 -2
  23. package/lib/rules/common/security-defined.js +10 -5
  24. package/lib/rules/common/spec.js +14 -12
  25. package/lib/rules/oas3/request-mime-type.js +1 -1
  26. package/lib/rules/oas3/response-mime-type.js +1 -1
  27. package/lib/rules/other/stats.d.ts +1 -1
  28. package/lib/rules/other/stats.js +1 -1
  29. package/lib/rules/utils.d.ts +1 -0
  30. package/lib/rules/utils.js +18 -2
  31. package/lib/types/oas2.js +6 -6
  32. package/lib/types/oas3.js +11 -11
  33. package/lib/types/oas3_1.js +3 -3
  34. package/lib/types/redocly-yaml.js +30 -5
  35. package/lib/utils.d.ts +1 -0
  36. package/lib/utils.js +13 -1
  37. package/lib/visitors.d.ts +7 -6
  38. package/lib/visitors.js +11 -3
  39. package/package.json +3 -5
  40. package/src/__tests__/__snapshots__/bundle.test.ts.snap +1 -1
  41. package/src/__tests__/lint.test.ts +88 -0
  42. package/src/__tests__/utils.test.ts +11 -0
  43. package/src/__tests__/walk.test.ts +2 -2
  44. package/src/config/__tests__/config-resolvers.test.ts +62 -1
  45. package/src/config/__tests__/config.test.ts +5 -0
  46. package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +16 -0
  47. package/src/config/__tests__/fixtures/resolve-config/local-config-with-wrong-custom-function.yaml +16 -0
  48. package/src/config/__tests__/fixtures/resolve-config/plugin.js +11 -0
  49. package/src/config/__tests__/load.test.ts +1 -1
  50. package/src/config/__tests__/resolve-plugins.test.ts +3 -3
  51. package/src/config/config-resolvers.ts +30 -6
  52. package/src/config/config.ts +2 -0
  53. package/src/config/load.ts +10 -4
  54. package/src/config/types.ts +13 -0
  55. package/src/config/utils.ts +1 -0
  56. package/src/rules/ajv.ts +4 -4
  57. package/src/rules/common/__tests__/operation-2xx-response.test.ts +37 -0
  58. package/src/rules/common/__tests__/operation-4xx-response.test.ts +37 -0
  59. package/src/rules/common/__tests__/path-params-defined.test.ts +69 -0
  60. package/src/rules/common/__tests__/security-defined.test.ts +6 -6
  61. package/src/rules/common/__tests__/spec.test.ts +125 -0
  62. package/src/rules/common/assertions/__tests__/asserts.test.ts +491 -428
  63. package/src/rules/common/assertions/__tests__/utils.test.ts +2 -2
  64. package/src/rules/common/assertions/asserts.ts +155 -97
  65. package/src/rules/common/assertions/index.ts +2 -11
  66. package/src/rules/common/assertions/utils.ts +66 -36
  67. package/src/rules/common/no-ambiguous-paths.ts +1 -1
  68. package/src/rules/common/no-identical-paths.ts +4 -4
  69. package/src/rules/common/operation-2xx-response.ts +2 -2
  70. package/src/rules/common/operation-4xx-response.ts +2 -2
  71. package/src/rules/common/path-not-include-query.ts +1 -1
  72. package/src/rules/common/path-params-defined.ts +9 -2
  73. package/src/rules/common/response-contains-header.ts +6 -1
  74. package/src/rules/common/security-defined.ts +10 -5
  75. package/src/rules/common/spec.ts +15 -11
  76. package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +51 -2
  77. package/src/rules/oas3/__tests__/response-contains-header.test.ts +116 -0
  78. package/src/rules/oas3/request-mime-type.ts +1 -1
  79. package/src/rules/oas3/response-mime-type.ts +1 -1
  80. package/src/rules/other/stats.ts +1 -1
  81. package/src/rules/utils.ts +24 -1
  82. package/src/types/oas2.ts +6 -6
  83. package/src/types/oas3.ts +11 -11
  84. package/src/types/oas3_1.ts +3 -3
  85. package/src/types/redocly-yaml.ts +30 -4
  86. package/src/utils.ts +13 -0
  87. package/src/visitors.ts +25 -10
  88. package/tsconfig.tsbuildinfo +1 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.asserts = exports.runOnValuesSet = exports.runOnKeysSet = void 0;
3
+ exports.buildAssertCustomFunction = exports.asserts = exports.runOnValuesSet = exports.runOnKeysSet = void 0;
4
4
  const utils_1 = require("../../../utils");
5
5
  const utils_2 = require("./utils");
6
6
  exports.runOnKeysSet = new Set([
@@ -32,145 +32,185 @@ exports.runOnValuesSet = new Set([
32
32
  exports.asserts = {
33
33
  pattern: (value, condition, baseLocation) => {
34
34
  if (typeof value === 'undefined')
35
- return { isValid: true }; // property doesn't exist, no need to lint it with this assert
35
+ return []; // property doesn't exist, no need to lint it with this assert
36
36
  const values = utils_1.isString(value) ? [value] : value;
37
37
  const regx = utils_2.regexFromString(condition);
38
- for (const _val of values) {
39
- if (!(regx === null || regx === void 0 ? void 0 : regx.test(_val))) {
40
- return { isValid: false, location: utils_1.isString(value) ? baseLocation : baseLocation.key() };
41
- }
42
- }
43
- return { isValid: true };
38
+ return values
39
+ .map((_val) => !(regx === null || regx === void 0 ? void 0 : regx.test(_val)) && {
40
+ message: `"${_val}" should match a regex ${condition}`,
41
+ location: utils_1.isString(value) ? baseLocation : baseLocation.key(),
42
+ })
43
+ .filter(utils_1.isTruthy);
44
44
  },
45
45
  enum: (value, condition, baseLocation) => {
46
46
  if (typeof value === 'undefined')
47
- return { isValid: true }; // property doesn't exist, no need to lint it with this assert
47
+ return []; // property doesn't exist, no need to lint it with this assert
48
48
  const values = utils_1.isString(value) ? [value] : value;
49
- for (const _val of values) {
50
- if (!condition.includes(_val)) {
51
- return {
52
- isValid: false,
53
- location: utils_1.isString(value) ? baseLocation : baseLocation.child(_val).key(),
54
- };
55
- }
56
- }
57
- return { isValid: true };
49
+ return values
50
+ .map((_val) => !condition.includes(_val) && {
51
+ message: `"${_val}" should be one of the predefined values`,
52
+ location: utils_1.isString(value) ? baseLocation : baseLocation.child(_val).key(),
53
+ })
54
+ .filter(utils_1.isTruthy);
58
55
  },
59
56
  defined: (value, condition = true, baseLocation) => {
60
57
  const isDefined = typeof value !== 'undefined';
61
- return { isValid: condition ? isDefined : !isDefined, location: baseLocation };
58
+ const isValid = condition ? isDefined : !isDefined;
59
+ return isValid
60
+ ? []
61
+ : [
62
+ {
63
+ message: condition ? `Should be defined` : 'Should be not defined',
64
+ location: baseLocation,
65
+ },
66
+ ];
62
67
  },
63
68
  required: (value, keys, baseLocation) => {
64
- for (const requiredKey of keys) {
65
- if (!value.includes(requiredKey)) {
66
- return { isValid: false, location: baseLocation.key() };
67
- }
68
- }
69
- return { isValid: true };
69
+ return keys
70
+ .map((requiredKey) => !value.includes(requiredKey) && {
71
+ message: `${requiredKey} is required`,
72
+ location: baseLocation.key(),
73
+ })
74
+ .filter(utils_1.isTruthy);
70
75
  },
71
76
  disallowed: (value, condition, baseLocation) => {
72
77
  if (typeof value === 'undefined')
73
- return { isValid: true }; // property doesn't exist, no need to lint it with this assert
78
+ return []; // property doesn't exist, no need to lint it with this assert
74
79
  const values = utils_1.isString(value) ? [value] : value;
75
- for (const _val of values) {
76
- if (condition.includes(_val)) {
77
- return {
78
- isValid: false,
79
- location: utils_1.isString(value) ? baseLocation : baseLocation.child(_val).key(),
80
- };
81
- }
82
- }
83
- return { isValid: true };
80
+ return values
81
+ .map((_val) => condition.includes(_val) && {
82
+ message: `"${_val}" is disallowed`,
83
+ location: utils_1.isString(value) ? baseLocation : baseLocation.child(_val).key(),
84
+ })
85
+ .filter(utils_1.isTruthy);
84
86
  },
85
87
  undefined: (value, condition = true, baseLocation) => {
86
88
  const isUndefined = typeof value === 'undefined';
87
- return { isValid: condition ? isUndefined : !isUndefined, location: baseLocation };
89
+ const isValid = condition ? isUndefined : !isUndefined;
90
+ return isValid
91
+ ? []
92
+ : [
93
+ {
94
+ message: condition ? `Should not be defined` : 'Should be defined',
95
+ location: baseLocation,
96
+ },
97
+ ];
88
98
  },
89
99
  nonEmpty: (value, condition = true, baseLocation) => {
90
100
  const isEmpty = typeof value === 'undefined' || value === null || value === '';
91
- return { isValid: condition ? !isEmpty : isEmpty, location: baseLocation };
101
+ const isValid = condition ? !isEmpty : isEmpty;
102
+ return isValid
103
+ ? []
104
+ : [
105
+ {
106
+ message: condition ? `Should not be empty` : 'Should be empty',
107
+ location: baseLocation,
108
+ },
109
+ ];
92
110
  },
93
111
  minLength: (value, condition, baseLocation) => {
94
- if (typeof value === 'undefined')
95
- return { isValid: true }; // property doesn't exist, no need to lint it with this assert
96
- return { isValid: value.length >= condition, location: baseLocation };
112
+ if (typeof value === 'undefined' || value.length >= condition)
113
+ return []; // property doesn't exist, no need to lint it with this assert
114
+ return [{ message: `Should have at least ${condition} characters`, location: baseLocation }];
97
115
  },
98
116
  maxLength: (value, condition, baseLocation) => {
99
- if (typeof value === 'undefined')
100
- return { isValid: true }; // property doesn't exist, no need to lint it with this assert
101
- return { isValid: value.length <= condition, location: baseLocation };
117
+ if (typeof value === 'undefined' || value.length <= condition)
118
+ return []; // property doesn't exist, no need to lint it with this assert
119
+ return [{ message: `Should have at most ${condition} characters`, location: baseLocation }];
102
120
  },
103
121
  casing: (value, condition, baseLocation) => {
104
122
  if (typeof value === 'undefined')
105
- return { isValid: true }; // property doesn't exist, no need to lint it with this assert
123
+ return []; // property doesn't exist, no need to lint it with this assert
106
124
  const values = utils_1.isString(value) ? [value] : value;
107
- for (const _val of values) {
108
- let matchCase = false;
109
- switch (condition) {
110
- case 'camelCase':
111
- matchCase = !!_val.match(/^[a-z][a-zA-Z0-9]+$/g);
112
- break;
113
- case 'kebab-case':
114
- matchCase = !!_val.match(/^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/g);
115
- break;
116
- case 'snake_case':
117
- matchCase = !!_val.match(/^([a-z][a-z0-9]*)(_[a-z0-9]+)*$/g);
118
- break;
119
- case 'PascalCase':
120
- matchCase = !!_val.match(/^[A-Z][a-zA-Z0-9]+$/g);
121
- break;
122
- case 'MACRO_CASE':
123
- matchCase = !!_val.match(/^([A-Z][A-Z0-9]*)(_[A-Z0-9]+)*$/g);
124
- break;
125
- case 'COBOL-CASE':
126
- matchCase = !!_val.match(/^([A-Z][A-Z0-9]*)(-[A-Z0-9]+)*$/g);
127
- break;
128
- case 'flatcase':
129
- matchCase = !!_val.match(/^[a-z][a-z0-9]+$/g);
130
- break;
131
- }
132
- if (!matchCase) {
133
- return {
134
- isValid: false,
135
- location: utils_1.isString(value) ? baseLocation : baseLocation.child(_val).key(),
136
- };
137
- }
138
- }
139
- return { isValid: true };
125
+ const casingRegexes = {
126
+ camelCase: /^[a-z][a-zA-Z0-9]+$/g,
127
+ 'kebab-case': /^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/g,
128
+ snake_case: /^([a-z][a-z0-9]*)(_[a-z0-9]+)*$/g,
129
+ PascalCase: /^[A-Z][a-zA-Z0-9]+$/g,
130
+ MACRO_CASE: /^([A-Z][A-Z0-9]*)(_[A-Z0-9]+)*$/g,
131
+ 'COBOL-CASE': /^([A-Z][A-Z0-9]*)(-[A-Z0-9]+)*$/g,
132
+ flatcase: /^[a-z][a-z0-9]+$/g,
133
+ };
134
+ return values
135
+ .map((_val) => !_val.match(casingRegexes[condition]) && {
136
+ message: `"${_val}" should use ${condition}`,
137
+ location: utils_1.isString(value) ? baseLocation : baseLocation.child(_val).key(),
138
+ })
139
+ .filter(utils_1.isTruthy);
140
140
  },
141
141
  sortOrder: (value, condition, baseLocation) => {
142
- if (typeof value === 'undefined')
143
- return { isValid: true };
144
- return { isValid: utils_2.isOrdered(value, condition), location: baseLocation };
142
+ if (typeof value === 'undefined' || utils_2.isOrdered(value, condition))
143
+ return [];
144
+ const direction = condition.direction || condition;
145
+ const property = condition.property;
146
+ return [
147
+ {
148
+ message: `Should be sorted in ${direction === 'asc' ? 'an ascending' : 'a descending'} order${property ? ` by property ${property}` : ''}`,
149
+ location: baseLocation,
150
+ },
151
+ ];
145
152
  },
146
153
  mutuallyExclusive: (value, condition, baseLocation) => {
147
- return { isValid: utils_2.getIntersectionLength(value, condition) < 2, location: baseLocation.key() };
154
+ if (utils_2.getIntersectionLength(value, condition) < 2)
155
+ return [];
156
+ return [
157
+ {
158
+ message: `${condition.join(', ')} keys should be mutually exclusive`,
159
+ location: baseLocation.key(),
160
+ },
161
+ ];
148
162
  },
149
163
  mutuallyRequired: (value, condition, baseLocation) => {
150
- return {
151
- isValid: utils_2.getIntersectionLength(value, condition) > 0
152
- ? utils_2.getIntersectionLength(value, condition) === condition.length
153
- : true,
154
- location: baseLocation.key(),
155
- };
164
+ const isValid = utils_2.getIntersectionLength(value, condition) > 0
165
+ ? utils_2.getIntersectionLength(value, condition) === condition.length
166
+ : true;
167
+ return isValid
168
+ ? []
169
+ : [
170
+ {
171
+ message: `Properties ${condition.join(', ')} are mutually required`,
172
+ location: baseLocation.key(),
173
+ },
174
+ ];
156
175
  },
157
176
  requireAny: (value, condition, baseLocation) => {
158
- return { isValid: utils_2.getIntersectionLength(value, condition) >= 1, location: baseLocation.key() };
177
+ return utils_2.getIntersectionLength(value, condition) >= 1
178
+ ? []
179
+ : [
180
+ {
181
+ message: `Should have any of ${condition.join(', ')}`,
182
+ location: baseLocation.key(),
183
+ },
184
+ ];
159
185
  },
160
186
  ref: (_value, condition, baseLocation, rawValue) => {
161
187
  if (typeof rawValue === 'undefined')
162
- return { isValid: true }; // property doesn't exist, no need to lint it with this assert
188
+ return []; // property doesn't exist, no need to lint it with this assert
163
189
  const hasRef = rawValue.hasOwnProperty('$ref');
164
190
  if (typeof condition === 'boolean') {
165
- return {
166
- isValid: condition ? hasRef : !hasRef,
167
- location: hasRef ? baseLocation : baseLocation.key(),
168
- };
191
+ const isValid = condition ? hasRef : !hasRef;
192
+ return isValid
193
+ ? []
194
+ : [
195
+ {
196
+ message: condition ? `should use $ref` : 'should not use $ref',
197
+ location: hasRef ? baseLocation : baseLocation.key(),
198
+ },
199
+ ];
169
200
  }
170
201
  const regex = utils_2.regexFromString(condition);
171
- return {
172
- isValid: hasRef && (regex === null || regex === void 0 ? void 0 : regex.test(rawValue['$ref'])),
173
- location: hasRef ? baseLocation : baseLocation.key(),
174
- };
202
+ const isValid = hasRef && (regex === null || regex === void 0 ? void 0 : regex.test(rawValue['$ref']));
203
+ return isValid
204
+ ? []
205
+ : [
206
+ {
207
+ message: `$ref value should match ${condition}`,
208
+ location: hasRef ? baseLocation : baseLocation.key(),
209
+ },
210
+ ];
175
211
  },
176
212
  };
213
+ function buildAssertCustomFunction(fn) {
214
+ return (value, options, baseLocation) => fn.call(null, value, options, baseLocation);
215
+ }
216
+ exports.buildAssertCustomFunction = buildAssertCustomFunction;
@@ -7,7 +7,7 @@ const Assertions = (opts) => {
7
7
  const visitors = [];
8
8
  // As 'Assertions' has an array of asserts,
9
9
  // that array spreads into an 'opts' object on init rules phase here
10
- // https://github.com/Redocly/redocly-cli/blob/master/packages/core/src/config/config.ts#L311
10
+ // https://github.com/Redocly/redocly-cli/blob/main/packages/core/src/config/config.ts#L311
11
11
  // that is why we need to iterate through 'opts' values;
12
12
  // before - filter only object 'opts' values
13
13
  const assertions = Object.values(opts).filter((opt) => typeof opt === 'object' && opt !== null);
@@ -23,12 +23,8 @@ const Assertions = (opts) => {
23
23
  .filter((assertName) => assertion[assertName] !== undefined)
24
24
  .map((assertName) => {
25
25
  return {
26
- assertId,
27
26
  name: assertName,
28
27
  conditions: assertion[assertName],
29
- message: assertion.message,
30
- severity: assertion.severity || 'error',
31
- suggest: assertion.suggest || [],
32
28
  runsOnKeys: asserts_1.runOnKeysSet.has(assertName),
33
29
  runsOnValues: asserts_1.runOnValuesSet.has(assertName),
34
30
  };
@@ -42,7 +38,7 @@ const Assertions = (opts) => {
42
38
  throw new Error(`${shouldRunOnKeys.name} can't be used on a single property. Please use 'property'.`);
43
39
  }
44
40
  for (const subject of subjects) {
45
- const subjectVisitor = utils_1.buildSubjectVisitor(assertion.property, assertsToApply, assertion.context);
41
+ const subjectVisitor = utils_1.buildSubjectVisitor(assertId, assertion, assertsToApply);
46
42
  const visitorObject = utils_1.buildVisitorObject(subject, assertion.context, subjectVisitor);
47
43
  visitors.push(visitorObject);
48
44
  }
@@ -1,21 +1,27 @@
1
- import { ProblemSeverity, UserContext } from '../../../walk';
1
+ import type { RuleSeverity } from '../../../config';
2
+ import { UserContext } from '../../../walk';
2
3
  export declare type OrderDirection = 'asc' | 'desc';
3
4
  export declare type OrderOptions = {
4
5
  direction: OrderDirection;
5
6
  property: string;
6
7
  };
8
+ declare type Assertion = {
9
+ property: string | string[];
10
+ context?: Record<string, any>[];
11
+ severity?: RuleSeverity;
12
+ suggest?: any[];
13
+ message?: string;
14
+ subject: string;
15
+ };
7
16
  export declare type AssertToApply = {
8
17
  name: string;
9
- assertId?: string;
10
18
  conditions: any;
11
- message?: string;
12
- severity?: ProblemSeverity;
13
- suggest?: string[];
14
19
  runsOnKeys: boolean;
15
20
  runsOnValues: boolean;
16
21
  };
17
22
  export declare function buildVisitorObject(subject: string, context: Record<string, any>[], subjectVisitor: any): Record<string, any>;
18
- export declare function buildSubjectVisitor(properties: string | string[], asserts: AssertToApply[], context?: Record<string, any>[]): (node: any, { report, location, rawLocation, key, type, resolve, rawNode }: UserContext) => void;
23
+ export declare function buildSubjectVisitor(assertId: string, assertion: Assertion, asserts: AssertToApply[]): (node: any, { report, location, rawLocation, key, type, resolve, rawNode }: UserContext) => void;
19
24
  export declare function getIntersectionLength(keys: string[], properties: string[]): number;
20
25
  export declare function isOrdered(value: any[], options: OrderOptions | OrderDirection): boolean;
21
26
  export declare function regexFromString(input: string): RegExp | null;
27
+ export {};
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.regexFromString = exports.isOrdered = exports.getIntersectionLength = exports.buildSubjectVisitor = exports.buildVisitorObject = void 0;
4
+ const logger_1 = require("../../../logger");
4
5
  const ref_utils_1 = require("../../../ref-utils");
5
6
  const asserts_1 = require("./asserts");
6
7
  function buildVisitorObject(subject, context, subjectVisitor) {
@@ -44,14 +45,15 @@ function buildVisitorObject(subject, context, subjectVisitor) {
44
45
  return visitor;
45
46
  }
46
47
  exports.buildVisitorObject = buildVisitorObject;
47
- function buildSubjectVisitor(properties, asserts, context) {
48
+ function buildSubjectVisitor(assertId, assertion, asserts) {
48
49
  return (node, { report, location, rawLocation, key, type, resolve, rawNode }) => {
49
50
  var _a;
51
+ let properties = assertion.property;
50
52
  // We need to check context's last node if it has the same type as subject node;
51
53
  // if yes - that means we didn't create context's last node visitor,
52
54
  // so we need to handle 'matchParentKeys' and 'excludeParentKeys' conditions here;
53
- if (context) {
54
- const lastContextNode = context[context.length - 1];
55
+ if (assertion.context) {
56
+ const lastContextNode = assertion.context[assertion.context.length - 1];
55
57
  if (lastContextNode.type === type.name) {
56
58
  const matchParentKeys = lastContextNode.matchParentKeys;
57
59
  const excludeParentKeys = lastContextNode.excludeParentKeys;
@@ -66,35 +68,55 @@ function buildSubjectVisitor(properties, asserts, context) {
66
68
  if (properties) {
67
69
  properties = Array.isArray(properties) ? properties : [properties];
68
70
  }
71
+ const defaultMessage = `${logger_1.colorize.blue(assertId)} failed because the ${logger_1.colorize.blue(assertion.subject)}${logger_1.colorize.blue(properties ? ` ${properties.join(', ')}` : '')} didn't meet the assertions: {{problems}}`;
72
+ const assertResults = [];
69
73
  for (const assert of asserts) {
70
74
  const currentLocation = assert.name === 'ref' ? rawLocation : location;
71
75
  if (properties) {
72
76
  for (const property of properties) {
73
77
  // we can have resolvable scalar so need to resolve value here.
74
78
  const value = ref_utils_1.isRef(node[property]) ? (_a = resolve(node[property])) === null || _a === void 0 ? void 0 : _a.node : node[property];
75
- runAssertion({
79
+ assertResults.push(runAssertion({
76
80
  values: value,
77
81
  rawValues: rawNode[property],
78
82
  assert,
79
83
  location: currentLocation.child(property),
80
- report,
81
- });
84
+ }));
82
85
  }
83
86
  }
84
87
  else {
85
88
  const value = assert.name === 'ref' ? rawNode : Object.keys(node);
86
- runAssertion({
89
+ assertResults.push(runAssertion({
87
90
  values: Object.keys(node),
88
91
  rawValues: value,
89
92
  assert,
90
93
  location: currentLocation,
91
- report,
92
- });
94
+ }));
93
95
  }
94
96
  }
97
+ const problems = assertResults.flat();
98
+ if (problems.length) {
99
+ const message = assertion.message || defaultMessage;
100
+ report({
101
+ message: message.replace('{{problems}}', getProblemsMessage(problems)),
102
+ location: getProblemsLocation(problems) || location,
103
+ forceSeverity: assertion.severity || 'error',
104
+ suggest: assertion.suggest || [],
105
+ ruleId: assertId,
106
+ });
107
+ }
95
108
  };
96
109
  }
97
110
  exports.buildSubjectVisitor = buildSubjectVisitor;
111
+ function getProblemsLocation(problems) {
112
+ return problems.length ? problems[0].location : undefined;
113
+ }
114
+ function getProblemsMessage(problems) {
115
+ var _a;
116
+ return problems.length === 1
117
+ ? (_a = problems[0].message) !== null && _a !== void 0 ? _a : ''
118
+ : problems.map((problem) => { var _a; return `\n- ${(_a = problem.message) !== null && _a !== void 0 ? _a : ''}`; }).join('');
119
+ }
98
120
  function getIntersectionLength(keys, properties) {
99
121
  const props = new Set(properties);
100
122
  let count = 0;
@@ -127,17 +149,8 @@ function isOrdered(value, options) {
127
149
  return true;
128
150
  }
129
151
  exports.isOrdered = isOrdered;
130
- function runAssertion({ values, rawValues, assert, location, report }) {
131
- const lintResult = asserts_1.asserts[assert.name](values, assert.conditions, location, rawValues);
132
- if (!lintResult.isValid) {
133
- report({
134
- message: assert.message || `The ${assert.assertId} doesn't meet required conditions`,
135
- location: lintResult.location || location,
136
- forceSeverity: assert.severity,
137
- suggest: assert.suggest,
138
- ruleId: assert.assertId,
139
- });
140
- }
152
+ function runAssertion({ values, rawValues, assert, location }) {
153
+ return asserts_1.asserts[assert.name](values, assert.conditions, location, rawValues);
141
154
  }
142
155
  function regexFromString(input) {
143
156
  const matches = input.match(/^\/(.*)\/(.*)|(.*)/);
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NoAmbiguousPaths = void 0;
4
4
  const NoAmbiguousPaths = () => {
5
5
  return {
6
- PathsMap(pathMap, { report, location }) {
6
+ Paths(pathMap, { report, location }) {
7
7
  const seenPaths = [];
8
8
  for (const currentPath of Object.keys(pathMap)) {
9
9
  const ambiguousPath = seenPaths.find((seenPath) => arePathsAmbiguous(seenPath, currentPath));
@@ -3,11 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NoIdenticalPaths = void 0;
4
4
  const NoIdenticalPaths = () => {
5
5
  return {
6
- PathsMap(pathMap, { report, location }) {
7
- const pathsMap = new Map();
6
+ Paths(pathMap, { report, location }) {
7
+ const Paths = new Map();
8
8
  for (const pathName of Object.keys(pathMap)) {
9
9
  const id = pathName.replace(/{.+?}/g, '{VARIABLE}');
10
- const existingSamePath = pathsMap.get(id);
10
+ const existingSamePath = Paths.get(id);
11
11
  if (existingSamePath) {
12
12
  report({
13
13
  message: `The path already exists which differs only by path parameter name(s): \`${existingSamePath}\` and \`${pathName}\`.`,
@@ -15,7 +15,7 @@ const NoIdenticalPaths = () => {
15
15
  });
16
16
  }
17
17
  else {
18
- pathsMap.set(id, pathName);
18
+ Paths.set(id, pathName);
19
19
  }
20
20
  }
21
21
  },
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Operation2xxResponse = void 0;
4
4
  const Operation2xxResponse = () => {
5
5
  return {
6
- ResponsesMap(responses, { report }) {
7
- const codes = Object.keys(responses);
6
+ Responses(responses, { report }) {
7
+ const codes = Object.keys(responses || {});
8
8
  if (!codes.some((code) => code === 'default' || /2[Xx0-9]{2}/.test(code))) {
9
9
  report({
10
10
  message: 'Operation must have at least one `2XX` response.',
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Operation4xxResponse = void 0;
4
4
  const Operation4xxResponse = () => {
5
5
  return {
6
- ResponsesMap(responses, { report }) {
7
- const codes = Object.keys(responses);
6
+ Responses(responses, { report }) {
7
+ const codes = Object.keys(responses || {});
8
8
  if (!codes.some((code) => /4[Xx0-9]{2}/.test(code))) {
9
9
  report({
10
10
  message: 'Operation must have at least one `4XX` response.',
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PathNotIncludeQuery = void 0;
4
4
  const PathNotIncludeQuery = () => {
5
5
  return {
6
- PathsMap: {
6
+ Paths: {
7
7
  PathItem(_operation, { report, key }) {
8
8
  if (key.toString().includes('?')) {
9
9
  report({
@@ -6,6 +6,7 @@ const PathParamsDefined = () => {
6
6
  let pathTemplateParams;
7
7
  let definedPathParams;
8
8
  let currentPath;
9
+ let definedOperationParams;
9
10
  return {
10
11
  PathItem: {
11
12
  enter(_, { key }) {
@@ -25,9 +26,13 @@ const PathParamsDefined = () => {
25
26
  }
26
27
  },
27
28
  Operation: {
29
+ enter() {
30
+ definedOperationParams = new Set();
31
+ },
28
32
  leave(_op, { report, location }) {
29
33
  for (const templateParam of Array.from(pathTemplateParams.keys())) {
30
- if (!definedPathParams.has(templateParam)) {
34
+ if (!definedOperationParams.has(templateParam) &&
35
+ !definedPathParams.has(templateParam)) {
31
36
  report({
32
37
  message: `The operation does not define the path parameter \`{${templateParam}}\` expected by path \`${currentPath}\`.`,
33
38
  location: location.child(['parameters']).key(), // report on operation
@@ -37,7 +42,7 @@ const PathParamsDefined = () => {
37
42
  },
38
43
  Parameter(parameter, { report, location }) {
39
44
  if (parameter.in === 'path' && parameter.name) {
40
- definedPathParams.add(parameter.name);
45
+ definedOperationParams.add(parameter.name);
41
46
  if (!pathTemplateParams.has(parameter.name)) {
42
47
  report({
43
48
  message: `Path parameter \`${parameter.name}\` is not used in the path \`${currentPath}\`.`,
@@ -8,13 +8,13 @@ const ResponseContainsHeader = (options) => {
8
8
  Operation: {
9
9
  Response: {
10
10
  enter: (response, { report, location, key }) => {
11
- var _a;
12
11
  const expectedHeaders = names[key] ||
13
12
  names[utils_1.getMatchingStatusCodeRange(key)] ||
14
13
  names[utils_1.getMatchingStatusCodeRange(key).toLowerCase()] ||
15
14
  [];
16
15
  for (const expectedHeader of expectedHeaders) {
17
- if (!((_a = response.headers) === null || _a === void 0 ? void 0 : _a[expectedHeader])) {
16
+ if (!(response === null || response === void 0 ? void 0 : response.headers) ||
17
+ !Object.keys(response === null || response === void 0 ? void 0 : response.headers).some((header) => header.toLowerCase() === expectedHeader.toLowerCase())) {
18
18
  report({
19
19
  message: `Response object must contain a "${expectedHeader}" header.`,
20
20
  location: location.child('headers').key(),
@@ -3,9 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SecurityDefined = void 0;
4
4
  const SecurityDefined = () => {
5
5
  const referencedSchemes = new Map();
6
+ const operationsWithoutSecurity = [];
6
7
  let eachOperationHasSecurity = true;
7
8
  return {
8
- DefinitionRoot: {
9
+ Root: {
9
10
  leave(root, { report }) {
10
11
  for (const [name, scheme] of referencedSchemes.entries()) {
11
12
  if (scheme.defined)
@@ -21,9 +22,12 @@ const SecurityDefined = () => {
21
22
  return;
22
23
  }
23
24
  else {
24
- report({
25
- message: `Every API should have security defined on the root level or for each operation.`,
26
- });
25
+ for (const operationLocation of operationsWithoutSecurity) {
26
+ report({
27
+ message: `Every operation should have security defined on it or on the root level.`,
28
+ location: operationLocation.key(),
29
+ });
30
+ }
27
31
  }
28
32
  },
29
33
  },
@@ -42,9 +46,10 @@ const SecurityDefined = () => {
42
46
  }
43
47
  }
44
48
  },
45
- Operation(operation) {
49
+ Operation(operation, { location }) {
46
50
  if (!(operation === null || operation === void 0 ? void 0 : operation.security)) {
47
51
  eachOperationHasSecurity = false;
52
+ operationsWithoutSecurity.push(location);
48
53
  }
49
54
  },
50
55
  };