@redocly/openapi-core 1.0.0-beta.110 → 1.0.0-beta.112
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/lib/config/all.js +0 -1
- package/lib/config/config-resolvers.js +42 -34
- package/lib/config/load.d.ts +1 -1
- package/lib/config/load.js +5 -5
- package/lib/config/minimal.js +0 -1
- package/lib/config/recommended.js +0 -1
- package/lib/rules/common/assertions/asserts.d.ts +22 -5
- package/lib/rules/common/assertions/asserts.js +25 -0
- package/lib/rules/common/assertions/index.d.ts +27 -2
- package/lib/rules/common/assertions/index.js +6 -29
- package/lib/rules/common/assertions/utils.d.ts +7 -14
- package/lib/rules/common/assertions/utils.js +129 -97
- package/lib/rules/common/no-ambiguous-paths.js +1 -1
- package/lib/rules/common/no-identical-paths.js +4 -4
- package/lib/rules/common/operation-2xx-response.js +2 -2
- package/lib/rules/common/operation-4xx-response.js +2 -2
- package/lib/rules/common/path-not-include-query.js +1 -1
- package/lib/rules/common/path-params-defined.js +7 -2
- package/lib/rules/common/response-contains-header.js +2 -2
- package/lib/rules/common/security-defined.js +10 -5
- package/lib/rules/common/spec.js +14 -12
- package/lib/rules/oas2/index.d.ts +0 -1
- package/lib/rules/oas2/index.js +0 -2
- package/lib/rules/oas3/index.js +0 -2
- package/lib/rules/oas3/request-mime-type.js +1 -1
- package/lib/rules/oas3/response-mime-type.js +1 -1
- package/lib/rules/other/stats.d.ts +1 -1
- package/lib/rules/other/stats.js +1 -1
- package/lib/rules/utils.d.ts +1 -0
- package/lib/rules/utils.js +17 -1
- package/lib/types/oas2.js +6 -6
- package/lib/types/oas3.js +11 -11
- package/lib/types/oas3_1.js +3 -3
- package/lib/types/redocly-yaml.js +58 -31
- package/lib/utils.d.ts +2 -0
- package/lib/utils.js +19 -1
- package/lib/visitors.d.ts +9 -7
- package/lib/visitors.js +12 -3
- package/lib/walk.js +7 -1
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/bundle.test.ts.snap +1 -1
- package/src/__tests__/lint.test.ts +24 -5
- package/src/__tests__/utils.test.ts +11 -0
- package/src/__tests__/walk.test.ts +2 -2
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +1 -3
- package/src/config/__tests__/config-resolvers.test.ts +30 -5
- package/src/config/__tests__/fixtures/load-redocly.yaml +4 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +6 -4
- package/src/config/__tests__/load.test.ts +4 -1
- package/src/config/all.ts +0 -1
- package/src/config/config-resolvers.ts +44 -20
- package/src/config/load.ts +8 -5
- package/src/config/minimal.ts +0 -1
- package/src/config/recommended.ts +0 -1
- package/src/rules/common/__tests__/operation-2xx-response.test.ts +37 -0
- package/src/rules/common/__tests__/operation-4xx-response.test.ts +37 -0
- package/src/rules/common/__tests__/path-params-defined.test.ts +69 -0
- package/src/rules/common/__tests__/security-defined.test.ts +6 -6
- package/src/rules/common/__tests__/spec.test.ts +125 -0
- package/src/rules/common/assertions/__tests__/asserts.test.ts +7 -3
- package/src/rules/common/assertions/__tests__/index.test.ts +41 -20
- package/src/rules/common/assertions/__tests__/utils.test.ts +44 -18
- package/src/rules/common/assertions/asserts.ts +60 -8
- package/src/rules/common/assertions/index.ts +36 -46
- package/src/rules/common/assertions/utils.ts +204 -127
- package/src/rules/common/no-ambiguous-paths.ts +1 -1
- package/src/rules/common/no-identical-paths.ts +4 -4
- package/src/rules/common/operation-2xx-response.ts +2 -2
- package/src/rules/common/operation-4xx-response.ts +2 -2
- package/src/rules/common/path-not-include-query.ts +1 -1
- package/src/rules/common/path-params-defined.ts +9 -2
- package/src/rules/common/response-contains-header.ts +6 -1
- package/src/rules/common/security-defined.ts +10 -5
- package/src/rules/common/spec.ts +15 -11
- package/src/rules/oas2/index.ts +0 -2
- package/src/rules/oas3/__tests__/response-contains-header.test.ts +116 -0
- package/src/rules/oas3/index.ts +0 -2
- package/src/rules/oas3/request-mime-type.ts +1 -1
- package/src/rules/oas3/response-mime-type.ts +1 -1
- package/src/rules/other/stats.ts +1 -1
- package/src/rules/utils.ts +22 -0
- package/src/types/oas2.ts +6 -6
- package/src/types/oas3.ts +11 -11
- package/src/types/oas3_1.ts +3 -3
- package/src/types/redocly-yaml.ts +58 -33
- package/src/utils.ts +18 -0
- package/src/visitors.ts +32 -11
- package/src/walk.ts +8 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/rules/common/info-description.d.ts +0 -2
- package/lib/rules/common/info-description.js +0 -12
- package/src/rules/common/__tests__/info-description.test.ts +0 -102
- package/src/rules/common/info-description.ts +0 -10
|
@@ -1,122 +1,154 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.regexFromString = exports.isOrdered = exports.getIntersectionLength = exports.buildSubjectVisitor = exports.buildVisitorObject = void 0;
|
|
3
|
+
exports.regexFromString = exports.isOrdered = exports.getIntersectionLength = exports.buildSubjectVisitor = exports.buildVisitorObject = exports.getAssertsToApply = void 0;
|
|
4
|
+
const asserts_1 = require("./asserts");
|
|
4
5
|
const logger_1 = require("../../../logger");
|
|
5
6
|
const ref_utils_1 = require("../../../ref-utils");
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const utils_1 = require("../../../utils");
|
|
8
|
+
const assertionMessageTemplates = {
|
|
9
|
+
problems: '{{problems}}',
|
|
10
|
+
};
|
|
11
|
+
function getPredicatesFromLocators(locators) {
|
|
12
|
+
const { filterInParentKeys, filterOutParentKeys, matchParentKeys } = locators;
|
|
13
|
+
const keyMatcher = matchParentKeys && regexFromString(matchParentKeys);
|
|
14
|
+
const matchKeysPredicate = keyMatcher && ((key) => keyMatcher.test(key.toString()));
|
|
15
|
+
const filterInPredicate = Array.isArray(filterInParentKeys) &&
|
|
16
|
+
((key) => filterInParentKeys.includes(key.toString()));
|
|
17
|
+
const filterOutPredicate = Array.isArray(filterOutParentKeys) &&
|
|
18
|
+
((key) => !filterOutParentKeys.includes(key.toString()));
|
|
19
|
+
return [matchKeysPredicate, filterInPredicate, filterOutPredicate].filter(utils_1.isTruthy);
|
|
20
|
+
}
|
|
21
|
+
function getAssertsToApply(assertion) {
|
|
22
|
+
const assertsToApply = utils_1.keysOf(asserts_1.asserts)
|
|
23
|
+
.filter((assertName) => assertion.assertions[assertName] !== undefined)
|
|
24
|
+
.map((assertName) => {
|
|
25
|
+
return {
|
|
26
|
+
name: assertName,
|
|
27
|
+
conditions: assertion.assertions[assertName],
|
|
28
|
+
runsOnKeys: asserts_1.runOnKeysSet.has(assertName),
|
|
29
|
+
runsOnValues: asserts_1.runOnValuesSet.has(assertName),
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
const shouldRunOnKeys = assertsToApply.find((assert) => assert.runsOnKeys && !assert.runsOnValues);
|
|
33
|
+
const shouldRunOnValues = assertsToApply.find((assert) => assert.runsOnValues && !assert.runsOnKeys);
|
|
34
|
+
if (shouldRunOnValues && !assertion.subject.property) {
|
|
35
|
+
throw new Error(`${shouldRunOnValues.name} can't be used on all keys. Please provide a single property`);
|
|
36
|
+
}
|
|
37
|
+
if (shouldRunOnKeys && assertion.subject.property) {
|
|
38
|
+
throw new Error(`${shouldRunOnKeys.name} can't be used on a single property. Please use 'property'.`);
|
|
39
|
+
}
|
|
40
|
+
return assertsToApply;
|
|
41
|
+
}
|
|
42
|
+
exports.getAssertsToApply = getAssertsToApply;
|
|
43
|
+
function getAssertionProperties({ subject }) {
|
|
44
|
+
return (Array.isArray(subject.property) ? subject.property : [subject === null || subject === void 0 ? void 0 : subject.property]).filter(Boolean);
|
|
45
|
+
}
|
|
46
|
+
function applyAssertions(assertionDefinition, asserts, { rawLocation, rawNode, resolve, location, node }) {
|
|
47
|
+
var _a;
|
|
48
|
+
const properties = getAssertionProperties(assertionDefinition);
|
|
49
|
+
const assertResults = [];
|
|
50
|
+
for (const assert of asserts) {
|
|
51
|
+
const currentLocation = assert.name === 'ref' ? rawLocation : location;
|
|
52
|
+
if (properties.length) {
|
|
53
|
+
for (const property of properties) {
|
|
54
|
+
// we can have resolvable scalar so need to resolve value here.
|
|
55
|
+
const value = ref_utils_1.isRef(node[property]) ? (_a = resolve(node[property])) === null || _a === void 0 ? void 0 : _a.node : node[property];
|
|
56
|
+
assertResults.push(runAssertion({
|
|
57
|
+
values: value,
|
|
58
|
+
rawValues: rawNode[property],
|
|
59
|
+
assert,
|
|
60
|
+
location: currentLocation.child(property),
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const value = assert.name === 'ref' ? rawNode : Object.keys(node);
|
|
66
|
+
assertResults.push(runAssertion({
|
|
67
|
+
values: Object.keys(node),
|
|
68
|
+
rawValues: value,
|
|
69
|
+
assert,
|
|
70
|
+
location: currentLocation,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return assertResults.flat();
|
|
75
|
+
}
|
|
76
|
+
function buildVisitorObject(assertion, subjectVisitor) {
|
|
77
|
+
var _a, _b;
|
|
78
|
+
const targetVisitorLocatorPredicates = getPredicatesFromLocators(assertion.subject);
|
|
79
|
+
const targetVisitorSkipFunction = targetVisitorLocatorPredicates.length
|
|
80
|
+
? (node, key) => !targetVisitorLocatorPredicates.every((predicate) => predicate(key))
|
|
81
|
+
: undefined;
|
|
82
|
+
const targetVisitor = {
|
|
83
|
+
[assertion.subject.type]: Object.assign({ enter: subjectVisitor }, (targetVisitorSkipFunction && { skip: targetVisitorSkipFunction })),
|
|
84
|
+
};
|
|
85
|
+
if (!Array.isArray(assertion.where)) {
|
|
86
|
+
return targetVisitor;
|
|
10
87
|
}
|
|
11
88
|
let currentVisitorLevel = {};
|
|
12
89
|
const visitor = currentVisitorLevel;
|
|
90
|
+
const context = assertion.where;
|
|
13
91
|
for (let index = 0; index < context.length; index++) {
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
// as a workaround for that we don't create separate visitor for the last element
|
|
18
|
-
// which is the same as subject;
|
|
19
|
-
// we will check includes/excludes it in the last visitor.
|
|
20
|
-
continue;
|
|
92
|
+
const assertionDefinitionNode = context[index];
|
|
93
|
+
if (!utils_1.isString((_a = assertionDefinitionNode.subject) === null || _a === void 0 ? void 0 : _a.type)) {
|
|
94
|
+
throw new Error(`${assertion.assertionId} -> where -> [${index}]: 'type' (String) is required`);
|
|
21
95
|
}
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
96
|
+
const locatorPredicates = getPredicatesFromLocators(assertionDefinitionNode.subject);
|
|
97
|
+
const assertsToApply = getAssertsToApply(assertionDefinitionNode);
|
|
98
|
+
const skipFunction = (node, key, { location, rawLocation, resolve, rawNode }) => !locatorPredicates.every((predicate) => predicate(key)) ||
|
|
99
|
+
!!applyAssertions(assertionDefinitionNode, assertsToApply, {
|
|
100
|
+
location,
|
|
101
|
+
node,
|
|
102
|
+
rawLocation,
|
|
103
|
+
rawNode,
|
|
104
|
+
resolve,
|
|
105
|
+
}).length;
|
|
106
|
+
const nodeVisitor = Object.assign({}, ((locatorPredicates.length || assertsToApply.length) && { skip: skipFunction }));
|
|
107
|
+
if (assertionDefinitionNode.subject.type === assertion.subject.type &&
|
|
108
|
+
index === context.length - 1) {
|
|
109
|
+
// We have to merge the visitors if the last node inside the `where` is the same as the subject.
|
|
110
|
+
targetVisitor[assertion.subject.type] = Object.assign({ enter: subjectVisitor }, ((nodeVisitor.skip && { skip: nodeVisitor.skip }) ||
|
|
111
|
+
(targetVisitorSkipFunction && {
|
|
112
|
+
skip: (node, key, ctx // We may have locators defined on assertion level and on where level for the same node type
|
|
113
|
+
) => { var _a; return !!(((_a = nodeVisitor.skip) === null || _a === void 0 ? void 0 : _a.call(nodeVisitor, node, key, ctx)) || (targetVisitorSkipFunction === null || targetVisitorSkipFunction === void 0 ? void 0 : targetVisitorSkipFunction(node, key))); },
|
|
114
|
+
})));
|
|
38
115
|
}
|
|
39
116
|
else {
|
|
40
|
-
currentVisitorLevel[
|
|
117
|
+
currentVisitorLevel = currentVisitorLevel[(_b = assertionDefinitionNode.subject) === null || _b === void 0 ? void 0 : _b.type] =
|
|
118
|
+
nodeVisitor;
|
|
41
119
|
}
|
|
42
|
-
currentVisitorLevel = currentVisitorLevel[node.type];
|
|
43
120
|
}
|
|
44
|
-
currentVisitorLevel[subject] =
|
|
121
|
+
currentVisitorLevel[assertion.subject.type] = targetVisitor[assertion.subject.type];
|
|
45
122
|
return visitor;
|
|
46
123
|
}
|
|
47
124
|
exports.buildVisitorObject = buildVisitorObject;
|
|
48
|
-
function buildSubjectVisitor(assertId, assertion
|
|
49
|
-
return (node, { report, location, rawLocation,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const excludeParentKeys = lastContextNode.excludeParentKeys;
|
|
60
|
-
if (matchParentKeys && !matchParentKeys.includes(key)) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
if (excludeParentKeys && excludeParentKeys.includes(key)) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
if (properties) {
|
|
69
|
-
properties = Array.isArray(properties) ? properties : [properties];
|
|
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 = [];
|
|
73
|
-
for (const assert of asserts) {
|
|
74
|
-
const currentLocation = assert.name === 'ref' ? rawLocation : location;
|
|
75
|
-
if (properties) {
|
|
76
|
-
for (const property of properties) {
|
|
77
|
-
// we can have resolvable scalar so need to resolve value here.
|
|
78
|
-
const value = ref_utils_1.isRef(node[property]) ? (_a = resolve(node[property])) === null || _a === void 0 ? void 0 : _a.node : node[property];
|
|
79
|
-
assertResults.push(runAssertion({
|
|
80
|
-
values: value,
|
|
81
|
-
rawValues: rawNode[property],
|
|
82
|
-
assert,
|
|
83
|
-
location: currentLocation.child(property),
|
|
84
|
-
}));
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
const value = assert.name === 'ref' ? rawNode : Object.keys(node);
|
|
89
|
-
assertResults.push(runAssertion({
|
|
90
|
-
values: Object.keys(node),
|
|
91
|
-
rawValues: value,
|
|
92
|
-
assert,
|
|
93
|
-
location: currentLocation,
|
|
94
|
-
}));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
const problems = assertResults.flat();
|
|
125
|
+
function buildSubjectVisitor(assertId, assertion) {
|
|
126
|
+
return (node, { report, location, rawLocation, resolve, rawNode }) => {
|
|
127
|
+
const properties = getAssertionProperties(assertion);
|
|
128
|
+
const defaultMessage = `${logger_1.colorize.blue(assertId)} failed because the ${logger_1.colorize.blue(assertion.subject.type)} ${logger_1.colorize.blue(properties.join(', '))} didn't meet the assertions: ${assertionMessageTemplates.problems}`.replace(/ +/g, ' ');
|
|
129
|
+
const problems = applyAssertions(assertion, getAssertsToApply(assertion), {
|
|
130
|
+
rawLocation,
|
|
131
|
+
rawNode,
|
|
132
|
+
resolve,
|
|
133
|
+
location,
|
|
134
|
+
node,
|
|
135
|
+
});
|
|
98
136
|
if (problems.length) {
|
|
99
137
|
const message = assertion.message || defaultMessage;
|
|
100
|
-
|
|
101
|
-
message
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
138
|
+
for (const problem of problems) {
|
|
139
|
+
const problemMessage = problem.message ? problem.message : defaultMessage;
|
|
140
|
+
report({
|
|
141
|
+
message: message.replace(assertionMessageTemplates.problems, problemMessage),
|
|
142
|
+
location: problem.location || location,
|
|
143
|
+
forceSeverity: assertion.severity || 'error',
|
|
144
|
+
suggest: assertion.suggest || [],
|
|
145
|
+
ruleId: assertId,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
107
148
|
}
|
|
108
149
|
};
|
|
109
150
|
}
|
|
110
151
|
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
|
-
}
|
|
120
152
|
function getIntersectionLength(keys, properties) {
|
|
121
153
|
const props = new Set(properties);
|
|
122
154
|
let count = 0;
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.NoAmbiguousPaths = void 0;
|
|
4
4
|
const NoAmbiguousPaths = () => {
|
|
5
5
|
return {
|
|
6
|
-
|
|
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
|
-
|
|
7
|
-
const
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.',
|
|
@@ -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 (!
|
|
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
|
-
|
|
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 (!(
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
};
|
package/lib/rules/common/spec.js
CHANGED
|
@@ -8,7 +8,7 @@ const utils_2 = require("../../utils");
|
|
|
8
8
|
const OasSpec = () => {
|
|
9
9
|
return {
|
|
10
10
|
any(node, { report, type, location, rawLocation, key, resolve, ignoreNextVisitorsOnNode }) {
|
|
11
|
-
var _a, _b, _c, _d;
|
|
11
|
+
var _a, _b, _c, _d, _e, _f;
|
|
12
12
|
const nodeType = utils_1.oasTypeOf(node);
|
|
13
13
|
const refLocation = rawLocation !== location ? rawLocation : undefined;
|
|
14
14
|
if (type.items) {
|
|
@@ -99,18 +99,20 @@ const OasSpec = () => {
|
|
|
99
99
|
if (propSchema.resolvable !== false && ref_utils_1.isRef(propValue)) {
|
|
100
100
|
propValue = resolve(propValue).node;
|
|
101
101
|
}
|
|
102
|
-
if (propSchema.enum) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.map((i) => `"${i}"`)
|
|
108
|
-
.join(', ')}.`,
|
|
109
|
-
from: refLocation,
|
|
110
|
-
suggest: utils_1.getSuggest(propValue, propSchema.enum),
|
|
102
|
+
if (propSchema.items && ((_c = propSchema.items) === null || _c === void 0 ? void 0 : _c.enum) && Array.isArray(propValue)) {
|
|
103
|
+
for (let i = 0; i < propValue.length; i++) {
|
|
104
|
+
utils_1.validateSchemaEnumType((_d = propSchema.items) === null || _d === void 0 ? void 0 : _d.enum, propValue[i], propName, refLocation, {
|
|
105
|
+
report,
|
|
106
|
+
location: location.child([propName, i]),
|
|
111
107
|
});
|
|
112
108
|
}
|
|
113
109
|
}
|
|
110
|
+
if (propSchema.enum) {
|
|
111
|
+
utils_1.validateSchemaEnumType(propSchema.enum, propValue, propName, refLocation, {
|
|
112
|
+
report,
|
|
113
|
+
location: location.child([propName]),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
114
116
|
else if (propSchema.type && !utils_1.matchesJsonSchemaType(propValue, propSchema.type, false)) {
|
|
115
117
|
report({
|
|
116
118
|
message: `Expected type \`${propSchema.type}\` but got \`${propValueType}\`.`,
|
|
@@ -118,8 +120,8 @@ const OasSpec = () => {
|
|
|
118
120
|
location: propLocation,
|
|
119
121
|
});
|
|
120
122
|
}
|
|
121
|
-
else if (propValueType === 'array' && ((
|
|
122
|
-
const itemsType = (
|
|
123
|
+
else if (propValueType === 'array' && ((_e = propSchema.items) === null || _e === void 0 ? void 0 : _e.type)) {
|
|
124
|
+
const itemsType = (_f = propSchema.items) === null || _f === void 0 ? void 0 : _f.type;
|
|
123
125
|
for (let i = 0; i < propValue.length; i++) {
|
|
124
126
|
const item = propValue[i];
|
|
125
127
|
if (!utils_1.matchesJsonSchemaType(item, itemsType, false)) {
|
package/lib/rules/oas2/index.js
CHANGED
|
@@ -4,7 +4,6 @@ exports.preprocessors = exports.rules = void 0;
|
|
|
4
4
|
const spec_1 = require("../common/spec");
|
|
5
5
|
const no_invalid_schema_examples_1 = require("../common/no-invalid-schema-examples");
|
|
6
6
|
const no_invalid_parameter_examples_1 = require("../common/no-invalid-parameter-examples");
|
|
7
|
-
const info_description_1 = require("../common/info-description");
|
|
8
7
|
const info_contact_1 = require("../common/info-contact");
|
|
9
8
|
const info_license_1 = require("../common/info-license");
|
|
10
9
|
const info_license_url_1 = require("../common/info-license-url");
|
|
@@ -46,7 +45,6 @@ exports.rules = {
|
|
|
46
45
|
spec: spec_1.OasSpec,
|
|
47
46
|
'no-invalid-schema-examples': no_invalid_schema_examples_1.NoInvalidSchemaExamples,
|
|
48
47
|
'no-invalid-parameter-examples': no_invalid_parameter_examples_1.NoInvalidParameterExamples,
|
|
49
|
-
'info-description': info_description_1.InfoDescription,
|
|
50
48
|
'info-contact': info_contact_1.InfoContact,
|
|
51
49
|
'info-license': info_license_1.InfoLicense,
|
|
52
50
|
'info-license-url': info_license_url_1.InfoLicenseUrl,
|
package/lib/rules/oas3/index.js
CHANGED
|
@@ -17,7 +17,6 @@ const operation_operationId_url_safe_1 = require("../common/operation-operationI
|
|
|
17
17
|
const tags_alphabetical_1 = require("../common/tags-alphabetical");
|
|
18
18
|
const no_server_example_com_1 = require("./no-server-example.com");
|
|
19
19
|
const no_server_trailing_slash_1 = require("./no-server-trailing-slash");
|
|
20
|
-
const info_description_1 = require("../common/info-description");
|
|
21
20
|
const tag_description_1 = require("../common/tag-description");
|
|
22
21
|
const info_contact_1 = require("../common/info-contact");
|
|
23
22
|
const info_license_1 = require("../common/info-license");
|
|
@@ -54,7 +53,6 @@ const spec_components_invalid_map_name_1 = require("./spec-components-invalid-ma
|
|
|
54
53
|
const operation_4xx_problem_details_rfc7807_1 = require("./operation-4xx-problem-details-rfc7807");
|
|
55
54
|
exports.rules = {
|
|
56
55
|
spec: spec_1.OasSpec,
|
|
57
|
-
'info-description': info_description_1.InfoDescription,
|
|
58
56
|
'info-contact': info_contact_1.InfoContact,
|
|
59
57
|
'info-license': info_license_1.InfoLicense,
|
|
60
58
|
'info-license-url': info_license_url_1.InfoLicenseUrl,
|
|
@@ -4,7 +4,7 @@ exports.RequestMimeType = void 0;
|
|
|
4
4
|
const utils_1 = require("../../utils");
|
|
5
5
|
const RequestMimeType = ({ allowedValues }) => {
|
|
6
6
|
return {
|
|
7
|
-
|
|
7
|
+
Paths: {
|
|
8
8
|
RequestBody: {
|
|
9
9
|
leave(requestBody, ctx) {
|
|
10
10
|
utils_1.validateMimeTypeOAS3({ type: 'consumes', value: requestBody }, ctx, allowedValues);
|
|
@@ -4,7 +4,7 @@ exports.ResponseMimeType = void 0;
|
|
|
4
4
|
const utils_1 = require("../../utils");
|
|
5
5
|
const ResponseMimeType = ({ allowedValues }) => {
|
|
6
6
|
return {
|
|
7
|
-
|
|
7
|
+
Paths: {
|
|
8
8
|
Response: {
|
|
9
9
|
leave(response, ctx) {
|
|
10
10
|
utils_1.validateMimeTypeOAS3({ type: 'produces', value: response }, ctx, allowedValues);
|
package/lib/rules/other/stats.js
CHANGED
package/lib/rules/utils.d.ts
CHANGED
|
@@ -16,3 +16,4 @@ export declare function validateDefinedAndNonEmpty(fieldName: string, value: any
|
|
|
16
16
|
export declare function getSuggest(given: string, variants: string[]): string[];
|
|
17
17
|
export declare function validateExample(example: any, schema: Referenced<Oas3Schema>, dataLoc: Location, { resolve, location, report }: UserContext, allowAdditionalProperties: boolean): void;
|
|
18
18
|
export declare function getAdditionalPropertiesOption(opts: Record<string, any>): boolean;
|
|
19
|
+
export declare function validateSchemaEnumType(schemaEnum: string[], propertyValue: string, propName: string, refLocation: Location | undefined, { report, location }: UserContext): void;
|
package/lib/rules/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getAdditionalPropertiesOption = exports.validateExample = exports.getSuggest = exports.validateDefinedAndNonEmpty = exports.fieldNonEmpty = exports.missingRequiredField = exports.matchesJsonSchemaType = exports.oasTypeOf = void 0;
|
|
3
|
+
exports.validateSchemaEnumType = exports.getAdditionalPropertiesOption = exports.validateExample = exports.getSuggest = exports.validateDefinedAndNonEmpty = exports.fieldNonEmpty = exports.missingRequiredField = exports.matchesJsonSchemaType = exports.oasTypeOf = void 0;
|
|
4
4
|
const levenshtein = require("js-levenshtein");
|
|
5
5
|
const ref_utils_1 = require("../ref-utils");
|
|
6
6
|
const ajv_1 = require("./ajv");
|
|
@@ -120,3 +120,19 @@ function getAdditionalPropertiesOption(opts) {
|
|
|
120
120
|
return !opts.disallowAdditionalProperties;
|
|
121
121
|
}
|
|
122
122
|
exports.getAdditionalPropertiesOption = getAdditionalPropertiesOption;
|
|
123
|
+
function validateSchemaEnumType(schemaEnum, propertyValue, propName, refLocation, { report, location }) {
|
|
124
|
+
if (!schemaEnum) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (!schemaEnum.includes(propertyValue === null ? 'null' : propertyValue)) {
|
|
128
|
+
report({
|
|
129
|
+
location,
|
|
130
|
+
message: `\`${propName}\` can be one of the following only: ${schemaEnum
|
|
131
|
+
.map((type) => `"${type}"`)
|
|
132
|
+
.join(', ')}.`,
|
|
133
|
+
from: refLocation,
|
|
134
|
+
suggest: getSuggest(propertyValue, schemaEnum),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
exports.validateSchemaEnumType = validateSchemaEnumType;
|
package/lib/types/oas2.js
CHANGED
|
@@ -12,7 +12,7 @@ const Root = {
|
|
|
12
12
|
schemes: { type: 'array', items: { type: 'string' } },
|
|
13
13
|
consumes: { type: 'array', items: { type: 'string' } },
|
|
14
14
|
produces: { type: 'array', items: { type: 'string' } },
|
|
15
|
-
paths: '
|
|
15
|
+
paths: 'Paths',
|
|
16
16
|
definitions: 'NamedSchemas',
|
|
17
17
|
parameters: 'NamedParameters',
|
|
18
18
|
responses: 'NamedResponses',
|
|
@@ -48,7 +48,7 @@ const License = {
|
|
|
48
48
|
},
|
|
49
49
|
required: ['name'],
|
|
50
50
|
};
|
|
51
|
-
const
|
|
51
|
+
const Paths = {
|
|
52
52
|
properties: {},
|
|
53
53
|
additionalProperties: (_value, key) => key.startsWith('/') ? 'PathItem' : undefined,
|
|
54
54
|
};
|
|
@@ -75,7 +75,7 @@ const Operation = {
|
|
|
75
75
|
consumes: { type: 'array', items: { type: 'string' } },
|
|
76
76
|
produces: { type: 'array', items: { type: 'string' } },
|
|
77
77
|
parameters: _1.listOf('Parameter'),
|
|
78
|
-
responses: '
|
|
78
|
+
responses: 'Responses',
|
|
79
79
|
schemes: { type: 'array', items: { type: 'string' } },
|
|
80
80
|
deprecated: { type: 'boolean' },
|
|
81
81
|
security: _1.listOf('SecurityRequirement'),
|
|
@@ -171,7 +171,7 @@ const ParameterItems = {
|
|
|
171
171
|
}
|
|
172
172
|
},
|
|
173
173
|
};
|
|
174
|
-
const
|
|
174
|
+
const Responses = {
|
|
175
175
|
properties: {
|
|
176
176
|
default: 'Response',
|
|
177
177
|
},
|
|
@@ -359,14 +359,14 @@ exports.Oas2Types = {
|
|
|
359
359
|
Info,
|
|
360
360
|
Contact,
|
|
361
361
|
License,
|
|
362
|
-
|
|
362
|
+
Paths,
|
|
363
363
|
PathItem,
|
|
364
364
|
Parameter,
|
|
365
365
|
ParameterItems,
|
|
366
366
|
Operation,
|
|
367
367
|
Examples,
|
|
368
368
|
Header,
|
|
369
|
-
|
|
369
|
+
Responses,
|
|
370
370
|
Response,
|
|
371
371
|
Schema,
|
|
372
372
|
Xml,
|