@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,3 +1,4 @@
|
|
|
1
|
+
import { Assertion, AssertionDefinition } from '..';
|
|
1
2
|
import { isOrdered, buildVisitorObject, getIntersectionLength } from '../utils';
|
|
2
3
|
|
|
3
4
|
describe('Oas3 assertions', () => {
|
|
@@ -25,29 +26,43 @@ describe('Oas3 assertions', () => {
|
|
|
25
26
|
|
|
26
27
|
describe('buildVisitorObject', () => {
|
|
27
28
|
it('should return a consistent visitor structure', () => {
|
|
28
|
-
const
|
|
29
|
+
const where: AssertionDefinition[] = [
|
|
29
30
|
{
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
subject: {
|
|
32
|
+
type: 'Foo',
|
|
33
|
+
filterInParentKeys: ['test'],
|
|
34
|
+
},
|
|
35
|
+
assertions: {},
|
|
32
36
|
},
|
|
33
37
|
{
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
subject: {
|
|
39
|
+
type: 'Bar',
|
|
40
|
+
filterInParentKeys: ['test'],
|
|
41
|
+
},
|
|
42
|
+
assertions: {},
|
|
36
43
|
},
|
|
37
44
|
{
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
subject: {
|
|
46
|
+
type: 'Roof',
|
|
47
|
+
filterInParentKeys: ['test'],
|
|
48
|
+
},
|
|
49
|
+
assertions: {},
|
|
40
50
|
},
|
|
41
|
-
];
|
|
51
|
+
] as AssertionDefinition[];
|
|
42
52
|
|
|
43
|
-
const visitors = buildVisitorObject(
|
|
53
|
+
const visitors = buildVisitorObject(
|
|
54
|
+
{ subject: { type: 'Bar' }, where, assertions: {} } as Assertion,
|
|
55
|
+
() => {}
|
|
56
|
+
);
|
|
44
57
|
|
|
45
58
|
expect(visitors).toMatchInlineSnapshot(`
|
|
46
59
|
Object {
|
|
47
60
|
"Foo": Object {
|
|
48
61
|
"Bar": Object {
|
|
49
62
|
"Roof": Object {
|
|
50
|
-
"Bar":
|
|
63
|
+
"Bar": Object {
|
|
64
|
+
"enter": [Function],
|
|
65
|
+
},
|
|
51
66
|
"skip": [Function],
|
|
52
67
|
},
|
|
53
68
|
"skip": [Function],
|
|
@@ -59,24 +74,35 @@ describe('Oas3 assertions', () => {
|
|
|
59
74
|
});
|
|
60
75
|
|
|
61
76
|
it('should return the right visitor structure', () => {
|
|
62
|
-
const
|
|
77
|
+
const where = [
|
|
63
78
|
{
|
|
64
|
-
|
|
65
|
-
|
|
79
|
+
subject: {
|
|
80
|
+
type: 'Operation',
|
|
81
|
+
filterInParentKeys: ['put'],
|
|
82
|
+
},
|
|
83
|
+
assertions: {},
|
|
66
84
|
},
|
|
67
85
|
{
|
|
68
|
-
|
|
69
|
-
|
|
86
|
+
subject: {
|
|
87
|
+
type: 'Responses',
|
|
88
|
+
filterInParentKeys: [201, 200],
|
|
89
|
+
},
|
|
90
|
+
assertions: {},
|
|
70
91
|
},
|
|
71
92
|
];
|
|
72
93
|
|
|
73
|
-
const visitors = buildVisitorObject(
|
|
94
|
+
const visitors = buildVisitorObject(
|
|
95
|
+
{ subject: { type: 'MediaTypesMap' }, where, assertions: {} } as Assertion,
|
|
96
|
+
() => {}
|
|
97
|
+
);
|
|
74
98
|
|
|
75
99
|
expect(visitors).toMatchInlineSnapshot(`
|
|
76
100
|
Object {
|
|
77
101
|
"Operation": Object {
|
|
78
|
-
"
|
|
79
|
-
"MediaTypesMap":
|
|
102
|
+
"Responses": Object {
|
|
103
|
+
"MediaTypesMap": Object {
|
|
104
|
+
"enter": [Function],
|
|
105
|
+
},
|
|
80
106
|
"skip": [Function],
|
|
81
107
|
},
|
|
82
108
|
"skip": [Function],
|
|
@@ -9,12 +9,33 @@ import {
|
|
|
9
9
|
regexFromString,
|
|
10
10
|
} from './utils';
|
|
11
11
|
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
export type AssertionFn = (
|
|
13
|
+
value: any,
|
|
14
|
+
condition: any,
|
|
15
|
+
baseLocation: Location,
|
|
16
|
+
rawValue?: any
|
|
17
|
+
) => AssertResult[];
|
|
16
18
|
|
|
17
|
-
export
|
|
19
|
+
export type Asserts = {
|
|
20
|
+
pattern: AssertionFn;
|
|
21
|
+
enum: AssertionFn;
|
|
22
|
+
defined: AssertionFn;
|
|
23
|
+
required: AssertionFn;
|
|
24
|
+
disallowed: AssertionFn;
|
|
25
|
+
undefined: AssertionFn;
|
|
26
|
+
nonEmpty: AssertionFn;
|
|
27
|
+
minLength: AssertionFn;
|
|
28
|
+
maxLength: AssertionFn;
|
|
29
|
+
casing: AssertionFn;
|
|
30
|
+
sortOrder: AssertionFn;
|
|
31
|
+
mutuallyExclusive: AssertionFn;
|
|
32
|
+
mutuallyRequired: AssertionFn;
|
|
33
|
+
requireAny: AssertionFn;
|
|
34
|
+
ref: AssertionFn;
|
|
35
|
+
const: AssertionFn;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const runOnKeysSet = new Set<keyof Asserts>([
|
|
18
39
|
'mutuallyExclusive',
|
|
19
40
|
'mutuallyRequired',
|
|
20
41
|
'enum',
|
|
@@ -27,8 +48,10 @@ export const runOnKeysSet = new Set([
|
|
|
27
48
|
'required',
|
|
28
49
|
'requireAny',
|
|
29
50
|
'ref',
|
|
51
|
+
'const',
|
|
52
|
+
'defined', // In case if `property` for assertions is not added
|
|
30
53
|
]);
|
|
31
|
-
export const runOnValuesSet = new Set([
|
|
54
|
+
export const runOnValuesSet = new Set<keyof Asserts>([
|
|
32
55
|
'pattern',
|
|
33
56
|
'enum',
|
|
34
57
|
'defined',
|
|
@@ -39,6 +62,7 @@ export const runOnValuesSet = new Set([
|
|
|
39
62
|
'casing',
|
|
40
63
|
'sortOrder',
|
|
41
64
|
'ref',
|
|
65
|
+
'const',
|
|
42
66
|
]);
|
|
43
67
|
|
|
44
68
|
export const asserts: Asserts = {
|
|
@@ -106,6 +130,34 @@ export const asserts: Asserts = {
|
|
|
106
130
|
)
|
|
107
131
|
.filter(isTruthy);
|
|
108
132
|
},
|
|
133
|
+
const: (
|
|
134
|
+
value: string | number | boolean | string[] | number[],
|
|
135
|
+
condition: string | number | boolean,
|
|
136
|
+
baseLocation: Location
|
|
137
|
+
) => {
|
|
138
|
+
if (typeof value === 'undefined') return [];
|
|
139
|
+
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
return value
|
|
142
|
+
.map(
|
|
143
|
+
(_val) =>
|
|
144
|
+
condition !== _val && {
|
|
145
|
+
message: `"${_val}" should be equal ${condition} `,
|
|
146
|
+
location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
.filter(isTruthy);
|
|
150
|
+
} else {
|
|
151
|
+
return value !== condition
|
|
152
|
+
? [
|
|
153
|
+
{
|
|
154
|
+
message: `${value} should be equal ${condition}`,
|
|
155
|
+
location: baseLocation,
|
|
156
|
+
},
|
|
157
|
+
]
|
|
158
|
+
: [];
|
|
159
|
+
}
|
|
160
|
+
},
|
|
109
161
|
undefined: (value: any, condition: boolean = true, baseLocation: Location) => {
|
|
110
162
|
const isUndefined = typeof value === 'undefined';
|
|
111
163
|
const isValid = condition ? isUndefined : !isUndefined;
|
|
@@ -210,7 +262,7 @@ export const asserts: Asserts = {
|
|
|
210
262
|
},
|
|
211
263
|
];
|
|
212
264
|
},
|
|
213
|
-
ref: (_value: any, condition: string | boolean, baseLocation, rawValue: any) => {
|
|
265
|
+
ref: (_value: any, condition: string | boolean, baseLocation: Location, rawValue: any) => {
|
|
214
266
|
if (typeof rawValue === 'undefined') return []; // property doesn't exist, no need to lint it with this assert
|
|
215
267
|
const hasRef = rawValue.hasOwnProperty('$ref');
|
|
216
268
|
if (typeof condition === 'boolean') {
|
|
@@ -237,7 +289,7 @@ export const asserts: Asserts = {
|
|
|
237
289
|
},
|
|
238
290
|
};
|
|
239
291
|
|
|
240
|
-
export function buildAssertCustomFunction(fn: CustomFunction) {
|
|
292
|
+
export function buildAssertCustomFunction(fn: CustomFunction): AssertionFn {
|
|
241
293
|
return (value: string[], options: any, baseLocation: Location) =>
|
|
242
294
|
fn.call(null, value, options, baseLocation);
|
|
243
295
|
}
|
|
@@ -1,65 +1,55 @@
|
|
|
1
|
-
import { asserts,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { asserts, AssertionFn } from './asserts';
|
|
2
|
+
import { buildSubjectVisitor, buildVisitorObject } from './utils';
|
|
3
|
+
import { Oas2Visitor, Oas3Visitor } from '../../../visitors';
|
|
4
|
+
import { RuleSeverity } from '../../../config';
|
|
5
|
+
import { isString } from '../../../utils';
|
|
6
|
+
|
|
7
|
+
export type AssertionLocators = {
|
|
8
|
+
filterInParentKeys?: (string | number)[];
|
|
9
|
+
filterOutParentKeys?: (string | number)[];
|
|
10
|
+
matchParentKeys?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type AssertionDefinition = {
|
|
14
|
+
subject: {
|
|
15
|
+
type: string;
|
|
16
|
+
property?: string | string[];
|
|
17
|
+
} & AssertionLocators;
|
|
18
|
+
assertions: { [name in keyof typeof asserts]?: AssertionFn };
|
|
19
|
+
};
|
|
4
20
|
|
|
5
|
-
export
|
|
6
|
-
|
|
21
|
+
export type RawAssertion = AssertionDefinition & {
|
|
22
|
+
where?: AssertionDefinition[];
|
|
23
|
+
message?: string;
|
|
24
|
+
suggest?: string[];
|
|
25
|
+
severity?: RuleSeverity;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type Assertion = RawAssertion & { assertionId: string };
|
|
29
|
+
|
|
30
|
+
export const Assertions = (opts: Record<string, Assertion>) => {
|
|
31
|
+
const visitors: (Oas2Visitor | Oas3Visitor)[] = [];
|
|
7
32
|
|
|
8
33
|
// As 'Assertions' has an array of asserts,
|
|
9
34
|
// that array spreads into an 'opts' object on init rules phase here
|
|
10
35
|
// https://github.com/Redocly/redocly-cli/blob/main/packages/core/src/config/config.ts#L311
|
|
11
36
|
// that is why we need to iterate through 'opts' values;
|
|
12
37
|
// before - filter only object 'opts' values
|
|
13
|
-
const assertions:
|
|
38
|
+
const assertions: Assertion[] = Object.values(opts).filter(
|
|
14
39
|
(opt: unknown) => typeof opt === 'object' && opt !== null
|
|
15
40
|
);
|
|
16
41
|
|
|
17
42
|
for (const [index, assertion] of assertions.entries()) {
|
|
18
43
|
const assertId =
|
|
19
44
|
(assertion.assertionId && `${assertion.assertionId} assertion`) || `assertion #${index + 1}`;
|
|
20
|
-
if (!assertion.subject) {
|
|
21
|
-
throw new Error(`${assertId}: 'subject' is required`);
|
|
22
|
-
}
|
|
23
45
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
: [assertion.subject];
|
|
27
|
-
|
|
28
|
-
const assertsToApply: AssertToApply[] = Object.keys(asserts)
|
|
29
|
-
.filter((assertName: string) => assertion[assertName] !== undefined)
|
|
30
|
-
.map((assertName: string) => {
|
|
31
|
-
return {
|
|
32
|
-
name: assertName,
|
|
33
|
-
conditions: assertion[assertName],
|
|
34
|
-
runsOnKeys: runOnKeysSet.has(assertName),
|
|
35
|
-
runsOnValues: runOnValuesSet.has(assertName),
|
|
36
|
-
};
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const shouldRunOnKeys: AssertToApply | undefined = assertsToApply.find(
|
|
40
|
-
(assert: AssertToApply) => assert.runsOnKeys && !assert.runsOnValues
|
|
41
|
-
);
|
|
42
|
-
const shouldRunOnValues: AssertToApply | undefined = assertsToApply.find(
|
|
43
|
-
(assert: AssertToApply) => assert.runsOnValues && !assert.runsOnKeys
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
if (shouldRunOnValues && !assertion.property) {
|
|
47
|
-
throw new Error(
|
|
48
|
-
`${shouldRunOnValues.name} can't be used on all keys. Please provide a single property.`
|
|
49
|
-
);
|
|
46
|
+
if (!isString(assertion.subject.type)) {
|
|
47
|
+
throw new Error(`${assertId}: 'type' (String) is required`);
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
for (const subject of subjects) {
|
|
59
|
-
const subjectVisitor = buildSubjectVisitor(assertId, assertion, assertsToApply);
|
|
60
|
-
const visitorObject = buildVisitorObject(subject, assertion.context, subjectVisitor);
|
|
61
|
-
visitors.push(visitorObject);
|
|
62
|
-
}
|
|
50
|
+
const subjectVisitor = buildSubjectVisitor(assertId, assertion);
|
|
51
|
+
const visitorObject = buildVisitorObject(assertion, subjectVisitor);
|
|
52
|
+
visitors.push(visitorObject);
|
|
63
53
|
}
|
|
64
54
|
|
|
65
55
|
return visitors;
|