@redocly/openapi-core 1.0.0-beta.91 → 1.0.0-beta.94
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/__tests__/normalizeVisitors.test.ts +1 -1
- package/__tests__/walk.test.ts +6 -6
- package/lib/config/all.js +1 -0
- package/lib/config/config.js +1 -1
- package/lib/config/minimal.js +1 -0
- package/lib/config/recommended.js +1 -0
- package/lib/config/rules.d.ts +1 -1
- package/lib/config/rules.js +10 -2
- package/lib/rules/common/assertions/asserts.d.ts +5 -0
- package/lib/rules/common/assertions/asserts.js +143 -0
- package/lib/rules/common/assertions/index.d.ts +2 -0
- package/lib/rules/common/assertions/index.js +52 -0
- package/lib/rules/common/assertions/utils.d.ts +20 -0
- package/lib/rules/common/assertions/utils.js +123 -0
- package/lib/rules/oas2/index.d.ts +1 -0
- package/lib/rules/oas2/index.js +2 -0
- package/lib/rules/oas3/index.js +2 -0
- package/lib/types/index.js +2 -2
- package/lib/types/oas3_1.js +31 -5
- package/lib/visitors.d.ts +2 -2
- package/lib/walk.d.ts +1 -0
- package/lib/walk.js +1 -1
- package/package.json +1 -1
- package/src/bundle.ts +1 -1
- package/src/config/__tests__/config.test.ts +256 -0
- package/src/config/all.ts +1 -0
- package/src/config/config.ts +15 -12
- package/src/config/minimal.ts +1 -0
- package/src/config/recommended.ts +1 -0
- package/src/config/rules.ts +11 -2
- package/src/lint.ts +3 -3
- package/src/rules/common/assertions/__tests__/asserts.test.ts +231 -0
- package/src/rules/common/assertions/__tests__/index.test.ts +65 -0
- package/src/rules/common/assertions/__tests__/utils.test.ts +89 -0
- package/src/rules/common/assertions/asserts.ts +137 -0
- package/src/rules/common/assertions/index.ts +75 -0
- package/src/rules/common/assertions/utils.ts +164 -0
- package/src/rules/oas2/index.ts +2 -0
- package/src/rules/oas3/__tests__/spec/servers.test.ts +1 -1
- package/src/rules/oas3/index.ts +2 -0
- package/src/types/index.ts +2 -2
- package/src/types/oas3_1.ts +32 -7
- package/src/visitors.ts +2 -2
- package/src/walk.ts +2 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -112,7 +112,7 @@ describe('Normalize visitors', () => {
|
|
|
112
112
|
expect(normalized.PathItem.enter).toHaveLength(1);
|
|
113
113
|
expect(normalized.Operation.enter).toHaveLength(1);
|
|
114
114
|
expect(normalized.Parameter.enter).toHaveLength(1);
|
|
115
|
-
expect(normalized.
|
|
115
|
+
expect(normalized.ParameterList.enter).toHaveLength(2);
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
it('should order deeper visitors first', () => {
|
package/__tests__/walk.test.ts
CHANGED
|
@@ -1057,12 +1057,12 @@ describe('walk order', () => {
|
|
|
1057
1057
|
"enter DefinitionRoot",
|
|
1058
1058
|
"enter PathMap",
|
|
1059
1059
|
"enter PathItem",
|
|
1060
|
-
"enter
|
|
1060
|
+
"enter ParameterList",
|
|
1061
1061
|
"enter Parameter",
|
|
1062
1062
|
"leave Parameter",
|
|
1063
|
-
"leave
|
|
1063
|
+
"leave ParameterList",
|
|
1064
1064
|
"enter Operation",
|
|
1065
|
-
"enter
|
|
1065
|
+
"enter ParameterList",
|
|
1066
1066
|
"enter ref #/components/parameters/shared_a",
|
|
1067
1067
|
"enter Parameter",
|
|
1068
1068
|
"leave Parameter",
|
|
@@ -1071,7 +1071,7 @@ describe('walk order', () => {
|
|
|
1071
1071
|
"leave Parameter",
|
|
1072
1072
|
"enter Parameter",
|
|
1073
1073
|
"leave Parameter",
|
|
1074
|
-
"leave
|
|
1074
|
+
"leave ParameterList",
|
|
1075
1075
|
"leave Operation",
|
|
1076
1076
|
"leave PathItem",
|
|
1077
1077
|
"leave PathMap",
|
|
@@ -1404,10 +1404,10 @@ describe('type extensions', () => {
|
|
|
1404
1404
|
"enter DefinitionRoot",
|
|
1405
1405
|
"enter XWebHooks",
|
|
1406
1406
|
"enter hook test",
|
|
1407
|
-
"enter
|
|
1407
|
+
"enter ParameterList",
|
|
1408
1408
|
"enter Parameter",
|
|
1409
1409
|
"leave Parameter",
|
|
1410
|
-
"leave
|
|
1410
|
+
"leave ParameterList",
|
|
1411
1411
|
"leave hook test",
|
|
1412
1412
|
"leave XWebHooks",
|
|
1413
1413
|
"leave DefinitionRoot",
|
package/lib/config/all.js
CHANGED
|
@@ -19,6 +19,7 @@ exports.default = {
|
|
|
19
19
|
'operation-description': 'error',
|
|
20
20
|
'operation-2xx-response': 'error',
|
|
21
21
|
'operation-4xx-response': 'error',
|
|
22
|
+
'assertions': 'error',
|
|
22
23
|
'operation-operationId': 'error',
|
|
23
24
|
'operation-summary': 'error',
|
|
24
25
|
'operation-operationId-unique': 'error',
|
package/lib/config/config.js
CHANGED
|
@@ -417,7 +417,7 @@ function assignExisting(target, obj) {
|
|
|
417
417
|
function getMergedConfig(config, entrypointAlias) {
|
|
418
418
|
var _a, _b;
|
|
419
419
|
return entrypointAlias
|
|
420
|
-
? new Config(Object.assign(Object.assign({}, config.rawConfig), { lint: getMergedLintConfig(config, entrypointAlias), 'features.openapi': Object.assign(Object.assign({}, config['features.openapi']), (_a = config.apis[entrypointAlias]) === null || _a === void 0 ? void 0 : _a['features.openapi']), 'features.mockServer': Object.assign(Object.assign({}, config['features.mockServer']), (_b = config.apis[entrypointAlias]) === null || _b === void 0 ? void 0 : _b['features.mockServer']) }))
|
|
420
|
+
? new Config(Object.assign(Object.assign({}, config.rawConfig), { lint: getMergedLintConfig(config, entrypointAlias), 'features.openapi': Object.assign(Object.assign({}, config['features.openapi']), (_a = config.apis[entrypointAlias]) === null || _a === void 0 ? void 0 : _a['features.openapi']), 'features.mockServer': Object.assign(Object.assign({}, config['features.mockServer']), (_b = config.apis[entrypointAlias]) === null || _b === void 0 ? void 0 : _b['features.mockServer']) }), config.configFile)
|
|
421
421
|
: config;
|
|
422
422
|
}
|
|
423
423
|
exports.getMergedConfig = getMergedConfig;
|
package/lib/config/minimal.js
CHANGED
|
@@ -18,6 +18,7 @@ exports.default = {
|
|
|
18
18
|
'operation-description': 'off',
|
|
19
19
|
'operation-2xx-response': 'warn',
|
|
20
20
|
'operation-4xx-response': 'off',
|
|
21
|
+
'assertions': 'warn',
|
|
21
22
|
'operation-operationId': 'warn',
|
|
22
23
|
'operation-summary': 'warn',
|
|
23
24
|
'operation-operationId-unique': 'warn',
|
package/lib/config/rules.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RuleSet, OasVersion } from '../oas-types';
|
|
2
2
|
import { LintConfig } from './config';
|
|
3
3
|
export declare function initRules<T extends Function, P extends RuleSet<T>>(rules: P[], config: LintConfig, type: 'rules' | 'preprocessors' | 'decorators', oasVersion: OasVersion): {
|
|
4
|
-
severity: import("..").ProblemSeverity;
|
|
4
|
+
severity: import("..").ProblemSeverity | "off";
|
|
5
5
|
ruleId: string;
|
|
6
6
|
visitor: any;
|
|
7
7
|
}[];
|
package/lib/config/rules.js
CHANGED
|
@@ -14,13 +14,21 @@ function initRules(rules, config, type, oasVersion) {
|
|
|
14
14
|
if (ruleSettings.severity === 'off') {
|
|
15
15
|
return undefined;
|
|
16
16
|
}
|
|
17
|
-
const
|
|
17
|
+
const visitors = rule(ruleSettings);
|
|
18
|
+
if (Array.isArray(visitors)) {
|
|
19
|
+
return visitors.map((visitor) => ({
|
|
20
|
+
severity: ruleSettings.severity,
|
|
21
|
+
ruleId,
|
|
22
|
+
visitor: visitor,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
18
25
|
return {
|
|
19
26
|
severity: ruleSettings.severity,
|
|
20
27
|
ruleId,
|
|
21
|
-
visitor,
|
|
28
|
+
visitor: visitors, // note: actually it is only one visitor object
|
|
22
29
|
};
|
|
23
30
|
}))
|
|
31
|
+
.flatMap(visitor => visitor)
|
|
24
32
|
.filter(utils_1.notUndefined);
|
|
25
33
|
}
|
|
26
34
|
exports.initRules = initRules;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.asserts = exports.runOnValuesSet = exports.runOnKeysSet = void 0;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
exports.runOnKeysSet = new Set([
|
|
6
|
+
'mutuallyExclusive',
|
|
7
|
+
'mutuallyRequired',
|
|
8
|
+
'enum',
|
|
9
|
+
'pattern',
|
|
10
|
+
'minLength',
|
|
11
|
+
'maxLength',
|
|
12
|
+
'casing',
|
|
13
|
+
'sortOrder',
|
|
14
|
+
'disallowed',
|
|
15
|
+
'required',
|
|
16
|
+
]);
|
|
17
|
+
exports.runOnValuesSet = new Set([
|
|
18
|
+
'pattern',
|
|
19
|
+
'enum',
|
|
20
|
+
'defined',
|
|
21
|
+
'undefined',
|
|
22
|
+
'nonEmpty',
|
|
23
|
+
'minLength',
|
|
24
|
+
'maxLength',
|
|
25
|
+
'casing',
|
|
26
|
+
'sortOrder',
|
|
27
|
+
]);
|
|
28
|
+
exports.asserts = {
|
|
29
|
+
pattern: (value, condition) => {
|
|
30
|
+
if (typeof value === 'undefined')
|
|
31
|
+
return true; // property doesn't exist, no need to lint it with this assert
|
|
32
|
+
const values = typeof value === 'string' ? [value] : value;
|
|
33
|
+
const regexOptions = condition.match(/(\b\/\b)(.+)/g) || ['/'];
|
|
34
|
+
condition = condition.slice(1).replace(regexOptions[0], '');
|
|
35
|
+
const regx = new RegExp(condition, regexOptions[0].slice(1));
|
|
36
|
+
for (let _val of values) {
|
|
37
|
+
if (!_val.match(regx)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
},
|
|
43
|
+
enum: (value, condition) => {
|
|
44
|
+
if (typeof value === 'undefined')
|
|
45
|
+
return true; // property doesn't exist, no need to lint it with this assert
|
|
46
|
+
const values = typeof value === 'string' ? [value] : value;
|
|
47
|
+
for (let _val of values) {
|
|
48
|
+
if (!condition.includes(_val)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
},
|
|
54
|
+
defined: (value, condition = true) => {
|
|
55
|
+
const isDefined = typeof value !== 'undefined';
|
|
56
|
+
return condition ? isDefined : !isDefined;
|
|
57
|
+
},
|
|
58
|
+
required: (value, keys) => {
|
|
59
|
+
for (const requiredKey of keys) {
|
|
60
|
+
if (!value.includes(requiredKey)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
},
|
|
66
|
+
disallowed: (value, condition) => {
|
|
67
|
+
if (typeof value === 'undefined')
|
|
68
|
+
return true; // property doesn't exist, no need to lint it with this assert
|
|
69
|
+
const values = typeof value === 'string' ? [value] : value;
|
|
70
|
+
for (let _val of values) {
|
|
71
|
+
if (condition.includes(_val)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
},
|
|
77
|
+
undefined: (value, condition = true) => {
|
|
78
|
+
const isUndefined = typeof value === 'undefined';
|
|
79
|
+
return condition ? isUndefined : !isUndefined;
|
|
80
|
+
},
|
|
81
|
+
nonEmpty: (value, condition = true) => {
|
|
82
|
+
const isEmpty = typeof value === 'undefined' || value === null || value === '';
|
|
83
|
+
return condition ? !isEmpty : isEmpty;
|
|
84
|
+
},
|
|
85
|
+
minLength: (value, condition) => {
|
|
86
|
+
if (typeof value === 'undefined')
|
|
87
|
+
return true; // property doesn't exist, no need to lint it with this assert
|
|
88
|
+
return value.length >= condition;
|
|
89
|
+
},
|
|
90
|
+
maxLength: (value, condition) => {
|
|
91
|
+
if (typeof value === 'undefined')
|
|
92
|
+
return true; // property doesn't exist, no need to lint it with this assert
|
|
93
|
+
return value.length <= condition;
|
|
94
|
+
},
|
|
95
|
+
casing: (value, condition) => {
|
|
96
|
+
if (typeof value === 'undefined')
|
|
97
|
+
return true; // property doesn't exist, no need to lint it with this assert
|
|
98
|
+
const values = typeof value === 'string' ? [value] : value;
|
|
99
|
+
for (let _val of values) {
|
|
100
|
+
let matchCase = false;
|
|
101
|
+
switch (condition) {
|
|
102
|
+
case 'camelCase':
|
|
103
|
+
matchCase = !!_val.match(/^[a-z][a-zA-Z0-9]+$/g);
|
|
104
|
+
break;
|
|
105
|
+
case 'kebab-case':
|
|
106
|
+
matchCase = !!_val.match(/^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/g);
|
|
107
|
+
break;
|
|
108
|
+
case 'snake_case':
|
|
109
|
+
matchCase = !!_val.match(/^([a-z][a-z0-9]*)(_[a-z0-9]+)*$/g);
|
|
110
|
+
break;
|
|
111
|
+
case 'PascalCase':
|
|
112
|
+
matchCase = !!_val.match(/^[A-Z][a-zA-Z0-9]+$/g);
|
|
113
|
+
break;
|
|
114
|
+
case 'MACRO_CASE':
|
|
115
|
+
matchCase = !!_val.match(/^([A-Z][A-Z0-9]*)(_[A-Z0-9]+)*$/g);
|
|
116
|
+
break;
|
|
117
|
+
case 'COBOL-CASE':
|
|
118
|
+
matchCase = !!_val.match(/^([A-Z][A-Z0-9]*)(-[A-Z0-9]+)*$/g);
|
|
119
|
+
break;
|
|
120
|
+
case 'flatcase':
|
|
121
|
+
matchCase = !!_val.match(/^[a-z][a-z0-9]+$/g);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
if (!matchCase) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
},
|
|
130
|
+
sortOrder: (value, condition) => {
|
|
131
|
+
if (typeof value === 'undefined')
|
|
132
|
+
return true;
|
|
133
|
+
return utils_1.isOrdered(value, condition);
|
|
134
|
+
},
|
|
135
|
+
mutuallyExclusive: (value, condition) => {
|
|
136
|
+
return utils_1.getIntersectionLength(value, condition) < 2;
|
|
137
|
+
},
|
|
138
|
+
mutuallyRequired: (value, condition) => {
|
|
139
|
+
return utils_1.getIntersectionLength(value, condition) > 0
|
|
140
|
+
? utils_1.getIntersectionLength(value, condition) === condition.length
|
|
141
|
+
: true;
|
|
142
|
+
},
|
|
143
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Assertions = void 0;
|
|
4
|
+
const asserts_1 = require("./asserts");
|
|
5
|
+
const utils_1 = require("./utils");
|
|
6
|
+
const Assertions = (opts) => {
|
|
7
|
+
let visitors = [];
|
|
8
|
+
// As 'Assertions' has an array of asserts,
|
|
9
|
+
// that array spreads into an 'opts' object on init rules phase here
|
|
10
|
+
// https://github.com/Redocly/openapi-cli/blob/master/packages/core/src/config/config.ts#L311
|
|
11
|
+
// that is why we need to iterate through 'opts' values;
|
|
12
|
+
// before - filter only object 'opts' values
|
|
13
|
+
const assertions = Object.values(opts).filter((opt) => typeof opt === 'object' && opt !== null);
|
|
14
|
+
for (const [index, assertion] of assertions.entries()) {
|
|
15
|
+
const assertId = (assertion.assertionId && `${assertion.assertionId} assertion`) || `assertion #${index + 1}`;
|
|
16
|
+
if (!assertion.subject) {
|
|
17
|
+
throw new Error(`${assertId}: 'subject' is required`);
|
|
18
|
+
}
|
|
19
|
+
const subjects = Array.isArray(assertion.subject)
|
|
20
|
+
? assertion.subject
|
|
21
|
+
: [assertion.subject];
|
|
22
|
+
const assertsToApply = Object.keys(asserts_1.asserts)
|
|
23
|
+
.filter((assertName) => assertion[assertName] !== undefined)
|
|
24
|
+
.map((assertName) => {
|
|
25
|
+
return {
|
|
26
|
+
assertId,
|
|
27
|
+
name: assertName,
|
|
28
|
+
conditions: assertion[assertName],
|
|
29
|
+
message: assertion.message,
|
|
30
|
+
severity: assertion.severity || 'error',
|
|
31
|
+
suggest: assertion.suggest || [],
|
|
32
|
+
runsOnKeys: asserts_1.runOnKeysSet.has(assertName),
|
|
33
|
+
runsOnValues: asserts_1.runOnValuesSet.has(assertName),
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
const shouldRunOnKeys = assertsToApply.find((assert) => assert.runsOnKeys && !assert.runsOnValues);
|
|
37
|
+
const shouldRunOnValues = assertsToApply.find((assert) => assert.runsOnValues && !assert.runsOnKeys);
|
|
38
|
+
if (shouldRunOnValues && !assertion.property) {
|
|
39
|
+
throw new Error(`${shouldRunOnValues.name} can't be used on all keys. Please provide a single property.`);
|
|
40
|
+
}
|
|
41
|
+
if (shouldRunOnKeys && assertion.property) {
|
|
42
|
+
throw new Error(`${shouldRunOnKeys.name} can't be used on a single property. Please use 'property'.`);
|
|
43
|
+
}
|
|
44
|
+
for (const subject of subjects) {
|
|
45
|
+
const subjectVisitor = utils_1.buildSubjectVisitor(assertion.property, assertsToApply, assertion.context);
|
|
46
|
+
const visitorObject = utils_1.buildVisitorObject(subject, assertion.context, subjectVisitor);
|
|
47
|
+
visitors.push(visitorObject);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return visitors;
|
|
51
|
+
};
|
|
52
|
+
exports.Assertions = Assertions;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ProblemSeverity, UserContext } from '../../../walk';
|
|
2
|
+
export declare type OrderDirection = 'asc' | 'desc';
|
|
3
|
+
export declare type OrderOptions = {
|
|
4
|
+
direction: OrderDirection;
|
|
5
|
+
property: string;
|
|
6
|
+
};
|
|
7
|
+
export declare type AssertToApply = {
|
|
8
|
+
name: string;
|
|
9
|
+
assertId?: string;
|
|
10
|
+
conditions: any;
|
|
11
|
+
message?: string;
|
|
12
|
+
severity?: ProblemSeverity;
|
|
13
|
+
suggest?: string[];
|
|
14
|
+
runsOnKeys: boolean;
|
|
15
|
+
runsOnValues: boolean;
|
|
16
|
+
};
|
|
17
|
+
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, key, type }: UserContext) => void;
|
|
19
|
+
export declare function getIntersectionLength(keys: string[], properties: string[]): number;
|
|
20
|
+
export declare function isOrdered(value: any[], options: OrderOptions | OrderDirection): boolean;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isOrdered = exports.getIntersectionLength = exports.buildSubjectVisitor = exports.buildVisitorObject = void 0;
|
|
4
|
+
const asserts_1 = require("./asserts");
|
|
5
|
+
function buildVisitorObject(subject, context, subjectVisitor) {
|
|
6
|
+
if (!context) {
|
|
7
|
+
return { [subject]: subjectVisitor };
|
|
8
|
+
}
|
|
9
|
+
let currentVisitorLevel = {};
|
|
10
|
+
const visitor = currentVisitorLevel;
|
|
11
|
+
for (let index = 0; index < context.length; index++) {
|
|
12
|
+
const node = context[index];
|
|
13
|
+
if (context.length === index + 1 && node.type === subject) {
|
|
14
|
+
// Visitors don't work properly for the same type nested nodes, so
|
|
15
|
+
// as a workaround for that we don't create separate visitor for the last element
|
|
16
|
+
// which is the same as subject;
|
|
17
|
+
// we will check includes/excludes it in the last visitor.
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const matchParentKeys = node.matchParentKeys;
|
|
21
|
+
const excludeParentKeys = node.excludeParentKeys;
|
|
22
|
+
if (matchParentKeys && excludeParentKeys) {
|
|
23
|
+
throw new Error(`Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item`);
|
|
24
|
+
}
|
|
25
|
+
if (matchParentKeys || excludeParentKeys) {
|
|
26
|
+
currentVisitorLevel[node.type] = {
|
|
27
|
+
skip: (_value, key) => {
|
|
28
|
+
if (matchParentKeys) {
|
|
29
|
+
return !matchParentKeys.includes(key);
|
|
30
|
+
}
|
|
31
|
+
if (excludeParentKeys) {
|
|
32
|
+
return excludeParentKeys.includes(key);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
currentVisitorLevel[node.type] = {};
|
|
39
|
+
}
|
|
40
|
+
currentVisitorLevel = currentVisitorLevel[node.type];
|
|
41
|
+
}
|
|
42
|
+
currentVisitorLevel[subject] = subjectVisitor;
|
|
43
|
+
return visitor;
|
|
44
|
+
}
|
|
45
|
+
exports.buildVisitorObject = buildVisitorObject;
|
|
46
|
+
function buildSubjectVisitor(properties, asserts, context) {
|
|
47
|
+
return function (node, { report, location, key, type }) {
|
|
48
|
+
// We need to check context's last node if it has the same type as subject node;
|
|
49
|
+
// if yes - that means we didn't create context's last node visitor,
|
|
50
|
+
// so we need to handle 'matchParentKeys' and 'excludeParentKeys' conditions here;
|
|
51
|
+
if (context) {
|
|
52
|
+
const lastContextNode = context[context.length - 1];
|
|
53
|
+
if (lastContextNode.type === type.name) {
|
|
54
|
+
const matchParentKeys = lastContextNode.matchParentKeys;
|
|
55
|
+
const excludeParentKeys = lastContextNode.excludeParentKeys;
|
|
56
|
+
if (matchParentKeys && !matchParentKeys.includes(key)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (excludeParentKeys && excludeParentKeys.includes(key)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (properties) {
|
|
65
|
+
properties = Array.isArray(properties) ? properties : [properties];
|
|
66
|
+
}
|
|
67
|
+
for (const assert of asserts) {
|
|
68
|
+
if (properties) {
|
|
69
|
+
for (const property of properties) {
|
|
70
|
+
runAssertion(node[property], assert, location.child(property), report);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
runAssertion(Object.keys(node), assert, location.key(), report);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
exports.buildSubjectVisitor = buildSubjectVisitor;
|
|
80
|
+
function getIntersectionLength(keys, properties) {
|
|
81
|
+
const props = new Set(properties);
|
|
82
|
+
let count = 0;
|
|
83
|
+
for (const key of keys) {
|
|
84
|
+
if (props.has(key)) {
|
|
85
|
+
count++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return count;
|
|
89
|
+
}
|
|
90
|
+
exports.getIntersectionLength = getIntersectionLength;
|
|
91
|
+
function isOrdered(value, options) {
|
|
92
|
+
const direction = options.direction || options;
|
|
93
|
+
const property = options.property;
|
|
94
|
+
for (let i = 1; i < value.length; i++) {
|
|
95
|
+
let currValue = value[i];
|
|
96
|
+
let prevVal = value[i - 1];
|
|
97
|
+
if (property) {
|
|
98
|
+
if (!value[i][property] || !value[i - 1][property]) {
|
|
99
|
+
return false; // property doesn't exist, so collection is not ordered
|
|
100
|
+
}
|
|
101
|
+
currValue = value[i][property];
|
|
102
|
+
prevVal = value[i - 1][property];
|
|
103
|
+
}
|
|
104
|
+
const result = direction === 'asc' ? currValue >= prevVal : currValue <= prevVal;
|
|
105
|
+
if (!result) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
exports.isOrdered = isOrdered;
|
|
112
|
+
function runAssertion(values, assert, location, report) {
|
|
113
|
+
const lintResult = asserts_1.asserts[assert.name](values, assert.conditions);
|
|
114
|
+
if (!lintResult) {
|
|
115
|
+
report({
|
|
116
|
+
message: assert.message || `The ${assert.assertId} doesn't meet required conditions`,
|
|
117
|
+
location,
|
|
118
|
+
forceSeverity: assert.severity,
|
|
119
|
+
suggest: assert.suggest,
|
|
120
|
+
ruleId: assert.assertId,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -15,6 +15,7 @@ export declare const rules: {
|
|
|
15
15
|
'no-path-trailing-slash': Oas2Rule;
|
|
16
16
|
'operation-2xx-response': Oas2Rule;
|
|
17
17
|
'operation-4xx-response': Oas2Rule;
|
|
18
|
+
assertions: Oas2Rule;
|
|
18
19
|
'operation-operationId-unique': Oas2Rule;
|
|
19
20
|
'operation-parameters-unique': Oas2Rule;
|
|
20
21
|
'path-parameters-defined': Oas2Rule;
|
package/lib/rules/oas2/index.js
CHANGED
|
@@ -16,6 +16,7 @@ const no_enum_type_mismatch_1 = require("../common/no-enum-type-mismatch");
|
|
|
16
16
|
const no_path_trailing_slash_1 = require("../common/no-path-trailing-slash");
|
|
17
17
|
const operation_2xx_response_1 = require("../common/operation-2xx-response");
|
|
18
18
|
const operation_4xx_response_1 = require("../common/operation-4xx-response");
|
|
19
|
+
const assertions_1 = require("../common/assertions");
|
|
19
20
|
const operation_operationId_unique_1 = require("../common/operation-operationId-unique");
|
|
20
21
|
const operation_parameters_unique_1 = require("../common/operation-parameters-unique");
|
|
21
22
|
const path_params_defined_1 = require("../common/path-params-defined");
|
|
@@ -54,6 +55,7 @@ exports.rules = {
|
|
|
54
55
|
'no-path-trailing-slash': no_path_trailing_slash_1.NoPathTrailingSlash,
|
|
55
56
|
'operation-2xx-response': operation_2xx_response_1.Operation2xxResponse,
|
|
56
57
|
'operation-4xx-response': operation_4xx_response_1.Operation4xxResponse,
|
|
58
|
+
'assertions': assertions_1.Assertions,
|
|
57
59
|
'operation-operationId-unique': operation_operationId_unique_1.OperationIdUnique,
|
|
58
60
|
'operation-parameters-unique': operation_parameters_unique_1.OperationParametersUnique,
|
|
59
61
|
'path-parameters-defined': path_params_defined_1.PathParamsDefined,
|
package/lib/rules/oas3/index.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.preprocessors = exports.rules = void 0;
|
|
|
4
4
|
const spec_1 = require("../common/spec");
|
|
5
5
|
const operation_2xx_response_1 = require("../common/operation-2xx-response");
|
|
6
6
|
const operation_4xx_response_1 = require("../common/operation-4xx-response");
|
|
7
|
+
const assertions_1 = require("../common/assertions");
|
|
7
8
|
const operation_operationId_unique_1 = require("../common/operation-operationId-unique");
|
|
8
9
|
const operation_parameters_unique_1 = require("../common/operation-parameters-unique");
|
|
9
10
|
const path_params_defined_1 = require("../common/path-params-defined");
|
|
@@ -54,6 +55,7 @@ exports.rules = {
|
|
|
54
55
|
'info-license-url': license_url_1.InfoLicenseUrl,
|
|
55
56
|
'operation-2xx-response': operation_2xx_response_1.Operation2xxResponse,
|
|
56
57
|
'operation-4xx-response': operation_4xx_response_1.Operation4xxResponse,
|
|
58
|
+
'assertions': assertions_1.Assertions,
|
|
57
59
|
'operation-operationId-unique': operation_operationId_unique_1.OperationIdUnique,
|
|
58
60
|
'operation-parameters-unique': operation_parameters_unique_1.OperationParametersUnique,
|
|
59
61
|
'path-parameters-defined': path_params_defined_1.PathParamsDefined,
|
package/lib/types/index.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.isNamedType = exports.normalizeTypes = exports.mapOf = exports.listOf = void 0;
|
|
4
4
|
function listOf(typeName) {
|
|
5
5
|
return {
|
|
6
|
-
name: typeName
|
|
6
|
+
name: `${typeName}List`,
|
|
7
7
|
properties: {},
|
|
8
8
|
items: typeName,
|
|
9
9
|
};
|
|
@@ -11,7 +11,7 @@ function listOf(typeName) {
|
|
|
11
11
|
exports.listOf = listOf;
|
|
12
12
|
function mapOf(typeName) {
|
|
13
13
|
return {
|
|
14
|
-
name: typeName
|
|
14
|
+
name: `${typeName}Map`,
|
|
15
15
|
properties: {},
|
|
16
16
|
additionalProperties: () => typeName,
|
|
17
17
|
};
|
package/lib/types/oas3_1.js
CHANGED
|
@@ -17,7 +17,7 @@ const DefinitionRoot = {
|
|
|
17
17
|
jsonSchemaDialect: { type: 'string' },
|
|
18
18
|
},
|
|
19
19
|
required: ['openapi', 'info'],
|
|
20
|
-
requiredOneOf: ['paths', 'components', 'webhooks']
|
|
20
|
+
requiredOneOf: ['paths', 'components', 'webhooks'],
|
|
21
21
|
};
|
|
22
22
|
const License = {
|
|
23
23
|
properties: {
|
|
@@ -103,7 +103,10 @@ const Schema = {
|
|
|
103
103
|
enum: { type: 'array' },
|
|
104
104
|
type: (value) => {
|
|
105
105
|
if (Array.isArray(value)) {
|
|
106
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
type: 'array',
|
|
108
|
+
items: { enum: ['object', 'array', 'string', 'number', 'integer', 'boolean', 'null'] },
|
|
109
|
+
};
|
|
107
110
|
}
|
|
108
111
|
else {
|
|
109
112
|
return {
|
|
@@ -124,7 +127,14 @@ const Schema = {
|
|
|
124
127
|
patternProperties: { type: 'object' },
|
|
125
128
|
propertyNames: 'Schema',
|
|
126
129
|
unevaluatedItems: 'Schema',
|
|
127
|
-
unevaluatedProperties:
|
|
130
|
+
unevaluatedProperties: (value) => {
|
|
131
|
+
if (typeof value === 'boolean') {
|
|
132
|
+
return { type: 'boolean' };
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
return 'Schema';
|
|
136
|
+
}
|
|
137
|
+
},
|
|
128
138
|
summary: { type: 'string' },
|
|
129
139
|
properties: 'SchemaProperties',
|
|
130
140
|
items: (value) => {
|
|
@@ -195,9 +205,25 @@ const SecurityScheme = {
|
|
|
195
205
|
case 'clientCredentials':
|
|
196
206
|
return ['type', 'flows', 'tokenUrl', 'refreshUrl', 'description', 'scopes'];
|
|
197
207
|
case 'authorizationCode':
|
|
198
|
-
return [
|
|
208
|
+
return [
|
|
209
|
+
'type',
|
|
210
|
+
'flows',
|
|
211
|
+
'authorizationUrl',
|
|
212
|
+
'refreshUrl',
|
|
213
|
+
'tokenUrl',
|
|
214
|
+
'description',
|
|
215
|
+
'scopes',
|
|
216
|
+
];
|
|
199
217
|
default:
|
|
200
|
-
return [
|
|
218
|
+
return [
|
|
219
|
+
'type',
|
|
220
|
+
'flows',
|
|
221
|
+
'authorizationUrl',
|
|
222
|
+
'refreshUrl',
|
|
223
|
+
'tokenUrl',
|
|
224
|
+
'description',
|
|
225
|
+
'scopes',
|
|
226
|
+
];
|
|
201
227
|
}
|
|
202
228
|
case 'openIdConnect':
|
|
203
229
|
return ['type', 'openIdConnectUrl', 'description'];
|
package/lib/visitors.d.ts
CHANGED
|
@@ -156,8 +156,8 @@ export declare type NormalizedOasVisitors<T extends BaseVisitor> = {
|
|
|
156
156
|
leave: Array<VisitorNode<any>>;
|
|
157
157
|
};
|
|
158
158
|
};
|
|
159
|
-
export declare type Oas3Rule = (options: Record<string, any>) => Oas3Visitor;
|
|
160
|
-
export declare type Oas2Rule = (options: Record<string, any>) => Oas2Visitor;
|
|
159
|
+
export declare type Oas3Rule = (options: Record<string, any>) => Oas3Visitor | Oas3Visitor[];
|
|
160
|
+
export declare type Oas2Rule = (options: Record<string, any>) => Oas2Visitor | Oas2Visitor[];
|
|
161
161
|
export declare type Oas3Preprocessor = (options: Record<string, any>) => Oas3TransformVisitor;
|
|
162
162
|
export declare type Oas2Preprocessor = (options: Record<string, any>) => Oas2TransformVisitor;
|
|
163
163
|
export declare type Oas3Decorator = (options: Record<string, any>) => Oas3TransformVisitor;
|
package/lib/walk.d.ts
CHANGED
package/lib/walk.js
CHANGED
|
@@ -240,7 +240,7 @@ function walkDocument(opts) {
|
|
|
240
240
|
? opts.location
|
|
241
241
|
: [opts.location]
|
|
242
242
|
: [Object.assign(Object.assign({}, currentLocation), { reportOnKey: false })];
|
|
243
|
-
ctx.problems.push(Object.assign(Object.assign({ ruleId, severity: opts.forceSeverity || severity }, opts), { suggest: opts.suggest || [], location: loc.map((loc) => {
|
|
243
|
+
ctx.problems.push(Object.assign(Object.assign({ ruleId: opts.ruleId || ruleId, severity: opts.forceSeverity || severity }, opts), { suggest: opts.suggest || [], location: loc.map((loc) => {
|
|
244
244
|
return Object.assign(Object.assign(Object.assign({}, currentLocation), { reportOnKey: false }), loc);
|
|
245
245
|
}) }));
|
|
246
246
|
}
|
package/package.json
CHANGED
package/src/bundle.ts
CHANGED