@redocly/openapi-core 1.0.0-beta.89 → 1.0.0-beta.92
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__/__snapshots__/bundle.test.ts.snap +15 -13
- package/__tests__/ref-utils.test.ts +23 -1
- package/lib/config/all.js +1 -0
- package/lib/config/config.js +6 -11
- 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/redocly/index.d.ts +4 -1
- package/lib/redocly/index.js +28 -19
- package/lib/redocly/registry-api.d.ts +4 -1
- package/lib/redocly/registry-api.js +3 -3
- package/lib/ref-utils.js +1 -1
- 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/redocly-yaml.js +24 -11
- 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/__tests__/lint.test.ts +40 -6
- package/src/bundle.ts +1 -1
- 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/redocly/index.ts +49 -30
- package/src/redocly/registry-api.ts +38 -21
- package/src/ref-utils.ts +1 -1
- 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/index.ts +2 -0
- package/src/types/redocly-yaml.ts +30 -11
- package/src/visitors.ts +2 -2
- package/src/walk.ts +2 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -22,28 +22,43 @@ export class RegistryApi {
|
|
|
22
22
|
|
|
23
23
|
private async request(path = '', options: RequestInit = {}, region?: Region) {
|
|
24
24
|
const headers = Object.assign({}, options.headers || {}, { 'x-redocly-cli-version': version });
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
if (!headers.hasOwnProperty('authorization')) {
|
|
27
|
+
throw new Error('Unauthorized');
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
const response = await fetch(
|
|
27
31
|
`${this.getBaseUrl(region)}${path}`,
|
|
28
32
|
Object.assign({}, options, { headers }),
|
|
29
33
|
);
|
|
30
|
-
|
|
34
|
+
|
|
35
|
+
if (response.status === 401) {
|
|
36
|
+
throw new Error('Unauthorized');
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
if (response.status === 404) {
|
|
32
40
|
const body: RegistryApiTypes.NotFoundProblemResponse = await response.json();
|
|
33
41
|
throw new Error(body.code);
|
|
34
42
|
}
|
|
43
|
+
|
|
35
44
|
return response;
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
async authStatus(
|
|
47
|
+
async authStatus(
|
|
48
|
+
accessToken: string,
|
|
49
|
+
region: Region,
|
|
50
|
+
verbose = false,
|
|
51
|
+
): Promise<{ viewerId: string; organizations: string[] }> {
|
|
39
52
|
try {
|
|
40
|
-
const response = await this.request('', { headers: { authorization: accessToken }}, region);
|
|
41
|
-
|
|
53
|
+
const response = await this.request('', { headers: { authorization: accessToken } }, region);
|
|
54
|
+
|
|
55
|
+
return await response.json();
|
|
42
56
|
} catch (error) {
|
|
43
57
|
if (verbose) {
|
|
44
58
|
console.log(error);
|
|
45
59
|
}
|
|
46
|
-
|
|
60
|
+
|
|
61
|
+
throw error;
|
|
47
62
|
}
|
|
48
63
|
}
|
|
49
64
|
|
|
@@ -69,7 +84,7 @@ export class RegistryApi {
|
|
|
69
84
|
isUpsert,
|
|
70
85
|
}),
|
|
71
86
|
},
|
|
72
|
-
this.region
|
|
87
|
+
this.region,
|
|
73
88
|
);
|
|
74
89
|
|
|
75
90
|
if (response.ok) {
|
|
@@ -88,20 +103,22 @@ export class RegistryApi {
|
|
|
88
103
|
branch,
|
|
89
104
|
isUpsert,
|
|
90
105
|
}: RegistryApiTypes.PushApiParams) {
|
|
91
|
-
const response = await this.request(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
const response = await this.request(
|
|
107
|
+
`/${organizationId}/${name}/${version}`,
|
|
108
|
+
{
|
|
109
|
+
method: 'PUT',
|
|
110
|
+
headers: {
|
|
111
|
+
'content-type': 'application/json',
|
|
112
|
+
authorization: this.accessToken,
|
|
113
|
+
} as HeadersInit,
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
rootFilePath,
|
|
116
|
+
filePaths,
|
|
117
|
+
branch,
|
|
118
|
+
isUpsert,
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
this.region,
|
|
105
122
|
);
|
|
106
123
|
|
|
107
124
|
if (response.ok) {
|
package/src/ref-utils.ts
CHANGED
|
@@ -60,7 +60,7 @@ export function pointerBaseName(pointer: string) {
|
|
|
60
60
|
|
|
61
61
|
export function refBaseName(ref: string) {
|
|
62
62
|
const parts = ref.split(/[\/\\]/); // split by '\' and '/'
|
|
63
|
-
return parts[parts.length - 1].
|
|
63
|
+
return parts[parts.length - 1].replace(/\.[^.]+$/, ''); // replace extension with empty string
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export function isAbsoluteUrl(ref: string) {
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { asserts } from '../asserts';
|
|
2
|
+
|
|
3
|
+
describe('oas3 assertions', () => {
|
|
4
|
+
describe('generic rules', () => {
|
|
5
|
+
const fakeNode = {
|
|
6
|
+
foo: '',
|
|
7
|
+
bar: '',
|
|
8
|
+
baz: '',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe('pattern', () => {
|
|
12
|
+
it('value should match regex pattern', () => {
|
|
13
|
+
expect(asserts.pattern('test string', '/test/')).toBeTruthy();
|
|
14
|
+
expect(asserts.pattern('test string', '/test me/')).toBeFalsy();
|
|
15
|
+
expect(asserts.pattern(['test string', 'test me'], '/test/')).toBeTruthy();
|
|
16
|
+
expect(asserts.pattern(['test string', 'test me'], '/test me/')).toBeFalsy();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('enum', () => {
|
|
21
|
+
it('value should be among predefined keys', () => {
|
|
22
|
+
expect(asserts.enum('test', ['test', 'example'])).toBeTruthy();
|
|
23
|
+
expect(asserts.enum(['test'], ['test', 'example'])).toBeTruthy();
|
|
24
|
+
expect(asserts.enum(['test', 'example'], ['test', 'example'])).toBeTruthy();
|
|
25
|
+
expect(asserts.enum(['test', 'example', 'foo'], ['test', 'example'])).toBeFalsy();
|
|
26
|
+
expect(asserts.enum('test', ['foo', 'example'])).toBeFalsy();
|
|
27
|
+
expect(asserts.enum(['test', 'foo'], ['test', 'example'])).toBeFalsy();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('defined', () => {
|
|
32
|
+
it('value should be defined', () => {
|
|
33
|
+
expect(asserts.defined('test', true)).toBeTruthy();
|
|
34
|
+
expect(asserts.defined(undefined, true)).toBeFalsy();
|
|
35
|
+
});
|
|
36
|
+
it('value should be undefined', () => {
|
|
37
|
+
expect(asserts.defined(undefined, false)).toBeTruthy();
|
|
38
|
+
expect(asserts.defined('test', false)).toBeFalsy();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('undefined', () => {
|
|
43
|
+
it('value should be undefined', () => {
|
|
44
|
+
expect(asserts.undefined(undefined, true)).toBeTruthy();
|
|
45
|
+
expect(asserts.undefined('test', true)).toBeFalsy();
|
|
46
|
+
});
|
|
47
|
+
it('value should be defined', () => {
|
|
48
|
+
expect(asserts.undefined('test', false)).toBeTruthy();
|
|
49
|
+
expect(asserts.undefined(undefined, false)).toBeFalsy();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('required', () => {
|
|
54
|
+
it('values should be required', () => {
|
|
55
|
+
expect(asserts.required(['one', 'two', 'three'], ['one', 'two'])).toBeTruthy();
|
|
56
|
+
expect(asserts.required(['one', 'two'], ['one', 'two', 'three'])).toBeFalsy();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('nonEmpty', () => {
|
|
61
|
+
it('value should not be empty', () => {
|
|
62
|
+
expect(asserts.nonEmpty('test', true)).toBeTruthy();
|
|
63
|
+
expect(asserts.nonEmpty('', true)).toBeFalsy();
|
|
64
|
+
expect(asserts.nonEmpty(null, true)).toBeFalsy();
|
|
65
|
+
expect(asserts.nonEmpty(undefined, true)).toBeFalsy();
|
|
66
|
+
});
|
|
67
|
+
it('value should be empty', () => {
|
|
68
|
+
expect(asserts.nonEmpty('', false)).toBeTruthy();
|
|
69
|
+
expect(asserts.nonEmpty(null, false)).toBeTruthy();
|
|
70
|
+
expect(asserts.nonEmpty(undefined, false)).toBeTruthy();
|
|
71
|
+
expect(asserts.nonEmpty('test', false)).toBeFalsy();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('minLength', () => {
|
|
76
|
+
it('value should have less or equal than 5 symbols length', () => {
|
|
77
|
+
expect(asserts.minLength('test', 5)).toBeFalsy();
|
|
78
|
+
expect(asserts.minLength([1, 2, 3, 4], 5)).toBeFalsy();
|
|
79
|
+
expect(asserts.minLength([1, 2, 3, 4, 5], 5)).toBeTruthy();
|
|
80
|
+
expect(asserts.minLength([1, 2, 3, 4, 5, 6], 5)).toBeTruthy();
|
|
81
|
+
expect(asserts.minLength('example', 5)).toBeTruthy();
|
|
82
|
+
expect(asserts.minLength([], 5)).toBeFalsy();
|
|
83
|
+
expect(asserts.minLength('', 5)).toBeFalsy();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('maxLength', () => {
|
|
88
|
+
it('value should have more or equal than 5 symbols length', () => {
|
|
89
|
+
expect(asserts.maxLength('test', 5)).toBeTruthy();
|
|
90
|
+
expect(asserts.maxLength([1, 2, 3, 4], 5)).toBeTruthy();
|
|
91
|
+
expect(asserts.maxLength([1, 2, 3, 4, 5], 5)).toBeTruthy();
|
|
92
|
+
expect(asserts.maxLength([1, 2, 3, 4, 5, 6], 5)).toBeFalsy();
|
|
93
|
+
expect(asserts.maxLength('example', 5)).toBeFalsy();
|
|
94
|
+
expect(asserts.maxLength([], 5)).toBeTruthy();
|
|
95
|
+
expect(asserts.maxLength('', 5)).toBeTruthy();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('casing', () => {
|
|
100
|
+
it('value should be camelCase', () => {
|
|
101
|
+
expect(asserts.casing(['testExample', 'fooBar'], 'camelCase')).toBeTruthy();
|
|
102
|
+
expect(asserts.casing(['testExample', 'FooBar'], 'camelCase')).toBeFalsy();
|
|
103
|
+
expect(asserts.casing('testExample', 'camelCase')).toBeTruthy();
|
|
104
|
+
expect(asserts.casing('TestExample', 'camelCase')).toBeFalsy();
|
|
105
|
+
expect(asserts.casing('test-example', 'camelCase')).toBeFalsy();
|
|
106
|
+
expect(asserts.casing('test_example', 'camelCase')).toBeFalsy();
|
|
107
|
+
});
|
|
108
|
+
it('value should be PascalCase', () => {
|
|
109
|
+
expect(asserts.casing('TestExample', 'PascalCase')).toBeTruthy();
|
|
110
|
+
expect(asserts.casing(['TestExample', 'FooBar'], 'PascalCase')).toBeTruthy();
|
|
111
|
+
expect(asserts.casing(['TestExample', 'fooBar'], 'PascalCase')).toBeFalsy();
|
|
112
|
+
expect(asserts.casing('testExample', 'PascalCase')).toBeFalsy();
|
|
113
|
+
expect(asserts.casing('test-example', 'PascalCase')).toBeFalsy();
|
|
114
|
+
expect(asserts.casing('test_example', 'PascalCase')).toBeFalsy();
|
|
115
|
+
});
|
|
116
|
+
it('value should be kebab-case', () => {
|
|
117
|
+
expect(asserts.casing('test-example', 'kebab-case')).toBeTruthy();
|
|
118
|
+
expect(asserts.casing(['test-example', 'foo-bar'], 'kebab-case')).toBeTruthy();
|
|
119
|
+
expect(asserts.casing(['test-example', 'foo_bar'], 'kebab-case')).toBeFalsy();
|
|
120
|
+
expect(asserts.casing('testExample', 'kebab-case')).toBeFalsy();
|
|
121
|
+
expect(asserts.casing('TestExample', 'kebab-case')).toBeFalsy();
|
|
122
|
+
expect(asserts.casing('test_example', 'kebab-case')).toBeFalsy();
|
|
123
|
+
});
|
|
124
|
+
it('value should be snake_case', () => {
|
|
125
|
+
expect(asserts.casing('test_example', 'snake_case')).toBeTruthy();
|
|
126
|
+
expect(asserts.casing(['test_example', 'foo_bar'], 'snake_case')).toBeTruthy();
|
|
127
|
+
expect(asserts.casing(['test_example', 'foo-bar'], 'snake_case')).toBeFalsy();
|
|
128
|
+
expect(asserts.casing('testExample', 'snake_case')).toBeFalsy();
|
|
129
|
+
expect(asserts.casing('TestExample', 'snake_case')).toBeFalsy();
|
|
130
|
+
expect(asserts.casing('test-example', 'snake_case')).toBeFalsy();
|
|
131
|
+
});
|
|
132
|
+
it('value should be MACRO_CASE', () => {
|
|
133
|
+
expect(asserts.casing('TEST_EXAMPLE', 'MACRO_CASE')).toBeTruthy();
|
|
134
|
+
expect(asserts.casing(['TEST_EXAMPLE', 'FOO_BAR'], 'MACRO_CASE')).toBeTruthy();
|
|
135
|
+
expect(asserts.casing(['TEST_EXAMPLE', 'FOO-BAR'], 'MACRO_CASE')).toBeFalsy();
|
|
136
|
+
expect(asserts.casing('TEST_EXAMPLE_', 'MACRO_CASE')).toBeFalsy();
|
|
137
|
+
expect(asserts.casing('_TEST_EXAMPLE', 'MACRO_CASE')).toBeFalsy();
|
|
138
|
+
expect(asserts.casing('TEST__EXAMPLE', 'MACRO_CASE')).toBeFalsy();
|
|
139
|
+
expect(asserts.casing('TEST-EXAMPLE', 'MACRO_CASE')).toBeFalsy();
|
|
140
|
+
expect(asserts.casing('testExample', 'MACRO_CASE')).toBeFalsy();
|
|
141
|
+
expect(asserts.casing('TestExample', 'MACRO_CASE')).toBeFalsy();
|
|
142
|
+
expect(asserts.casing('test-example', 'MACRO_CASE')).toBeFalsy();
|
|
143
|
+
});
|
|
144
|
+
it('value should be COBOL-CASE', () => {
|
|
145
|
+
expect(asserts.casing('TEST-EXAMPLE', 'COBOL-CASE')).toBeTruthy();
|
|
146
|
+
expect(asserts.casing(['TEST-EXAMPLE', 'FOO-BAR'], 'COBOL-CASE')).toBeTruthy();
|
|
147
|
+
expect(asserts.casing(['TEST-EXAMPLE', 'FOO_BAR'], 'COBOL-CASE')).toBeFalsy();
|
|
148
|
+
expect(asserts.casing('TEST-EXAMPLE-', 'COBOL-CASE')).toBeFalsy();
|
|
149
|
+
expect(asserts.casing('0TEST-EXAMPLE', 'COBOL-CASE')).toBeFalsy();
|
|
150
|
+
expect(asserts.casing('-TEST-EXAMPLE', 'COBOL-CASE')).toBeFalsy();
|
|
151
|
+
expect(asserts.casing('TEST--EXAMPLE', 'COBOL-CASE')).toBeFalsy();
|
|
152
|
+
expect(asserts.casing('TEST_EXAMPLE', 'COBOL-CASE')).toBeFalsy();
|
|
153
|
+
expect(asserts.casing('testExample', 'COBOL-CASE')).toBeFalsy();
|
|
154
|
+
expect(asserts.casing('TestExample', 'COBOL-CASE')).toBeFalsy();
|
|
155
|
+
expect(asserts.casing('test-example', 'COBOL-CASE')).toBeFalsy();
|
|
156
|
+
});
|
|
157
|
+
it('value should be flatcase', () => {
|
|
158
|
+
expect(asserts.casing('testexample', 'flatcase')).toBeTruthy();
|
|
159
|
+
expect(asserts.casing(['testexample', 'foobar'], 'flatcase')).toBeTruthy();
|
|
160
|
+
expect(asserts.casing(['testexample', 'foo_bar'], 'flatcase')).toBeFalsy();
|
|
161
|
+
expect(asserts.casing('testexample_', 'flatcase')).toBeFalsy();
|
|
162
|
+
expect(asserts.casing('0testexample', 'flatcase')).toBeFalsy();
|
|
163
|
+
expect(asserts.casing('testExample', 'flatcase')).toBeFalsy();
|
|
164
|
+
expect(asserts.casing('TestExample', 'flatcase')).toBeFalsy();
|
|
165
|
+
expect(asserts.casing('test-example', 'flatcase')).toBeFalsy();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe.skip('sortOrder', () => {
|
|
170
|
+
it('value should be ordered in ASC direction', () => {
|
|
171
|
+
expect(asserts.sortOrder(['example', 'foo', 'test'], 'asc')).toBeTruthy();
|
|
172
|
+
expect(asserts.sortOrder(['example', 'foo', 'test'], { direction: 'asc' })).toBeTruthy();
|
|
173
|
+
expect(asserts.sortOrder(['example'], 'asc')).toBeTruthy();
|
|
174
|
+
expect(asserts.sortOrder(['example', 'test', 'foo'], 'asc')).toBeFalsy();
|
|
175
|
+
expect(asserts.sortOrder(['example', 'foo', 'test'], 'desc')).toBeFalsy();
|
|
176
|
+
expect(
|
|
177
|
+
asserts.sortOrder([{ name: 'bar' }, { name: 'baz' }, { name: 'foo' }], {
|
|
178
|
+
direction: 'asc',
|
|
179
|
+
property: 'name',
|
|
180
|
+
}),
|
|
181
|
+
).toBeTruthy();
|
|
182
|
+
expect(
|
|
183
|
+
asserts.sortOrder([{ name: 'bar' }, { name: 'baz' }, { name: 'foo' }], {
|
|
184
|
+
direction: 'desc',
|
|
185
|
+
property: 'name',
|
|
186
|
+
}),
|
|
187
|
+
).toBeFalsy();
|
|
188
|
+
});
|
|
189
|
+
it('value should be ordered in DESC direction', () => {
|
|
190
|
+
expect(asserts.sortOrder(['test', 'foo', 'example'], 'desc')).toBeTruthy();
|
|
191
|
+
expect(asserts.sortOrder(['test', 'foo', 'example'], { direction: 'desc' })).toBeTruthy();
|
|
192
|
+
expect(asserts.sortOrder(['example'], 'desc')).toBeTruthy();
|
|
193
|
+
expect(asserts.sortOrder(['example', 'test', 'foo'], 'desc')).toBeFalsy();
|
|
194
|
+
expect(asserts.sortOrder(['test', 'foo', 'example'], 'asc')).toBeFalsy();
|
|
195
|
+
expect(
|
|
196
|
+
asserts.sortOrder([{ name: 'foo' }, { name: 'baz' }, { name: 'bar' }], {
|
|
197
|
+
direction: 'desc',
|
|
198
|
+
property: 'name',
|
|
199
|
+
}),
|
|
200
|
+
).toBeTruthy();
|
|
201
|
+
expect(
|
|
202
|
+
asserts.sortOrder([{ name: 'foo' }, { name: 'baz' }, { name: 'bar' }], {
|
|
203
|
+
direction: 'asc',
|
|
204
|
+
property: 'name',
|
|
205
|
+
}),
|
|
206
|
+
).toBeFalsy();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('mutuallyExclusive', () => {
|
|
211
|
+
it('node should not have more than one property from predefined list', () => {
|
|
212
|
+
expect(asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'test'])).toBeTruthy();
|
|
213
|
+
expect(asserts.mutuallyExclusive(Object.keys(fakeNode), [])).toBeTruthy();
|
|
214
|
+
expect(asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'bar'])).toBeFalsy();
|
|
215
|
+
expect(
|
|
216
|
+
asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'bar', 'test']),
|
|
217
|
+
).toBeFalsy();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('mutuallyRequired', () => {
|
|
222
|
+
it('node should have all the properties from predefined list', () => {
|
|
223
|
+
expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar'])).toBeTruthy();
|
|
224
|
+
expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar', 'baz'])).toBeTruthy();
|
|
225
|
+
expect(asserts.mutuallyRequired(Object.keys(fakeNode), [])).toBeTruthy();
|
|
226
|
+
expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'test'])).toBeFalsy();
|
|
227
|
+
expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar', 'test'])).toBeFalsy();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Assertions } from '../.';
|
|
2
|
+
|
|
3
|
+
const opts = {
|
|
4
|
+
'0': {
|
|
5
|
+
subject: 'Operation',
|
|
6
|
+
property: 'summary',
|
|
7
|
+
description: 'example warn text',
|
|
8
|
+
severity: 'warn',
|
|
9
|
+
pattern: '/example/',
|
|
10
|
+
},
|
|
11
|
+
'1': {
|
|
12
|
+
subject: 'PathItem',
|
|
13
|
+
context: [{ type: 'Operation', matchParentKeys: ['post'] }],
|
|
14
|
+
description: 'example warn text',
|
|
15
|
+
severity: 'warn',
|
|
16
|
+
mutuallyExclusive: ['summary', 'security'],
|
|
17
|
+
},
|
|
18
|
+
'2': {
|
|
19
|
+
subject: ['PathItem'],
|
|
20
|
+
context: [{ type: 'Operation' }],
|
|
21
|
+
property: 'tags',
|
|
22
|
+
description: 'example warn text',
|
|
23
|
+
severity: 'warn',
|
|
24
|
+
sortOrder: 'desc',
|
|
25
|
+
},
|
|
26
|
+
'3': {
|
|
27
|
+
subject: ['Foo'],
|
|
28
|
+
context: [{ type: 'Bar' }, { type: 'Baz' }],
|
|
29
|
+
property: 'test',
|
|
30
|
+
description: 'example warn text',
|
|
31
|
+
severity: 'warn',
|
|
32
|
+
sortOrder: 'desc',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe('Oas3 assertions', () => {
|
|
37
|
+
it('should return the right visitor structure', () => {
|
|
38
|
+
const visitors = Assertions(opts) as any;
|
|
39
|
+
expect(visitors).toMatchInlineSnapshot(`
|
|
40
|
+
Array [
|
|
41
|
+
Object {
|
|
42
|
+
"Operation": [Function],
|
|
43
|
+
},
|
|
44
|
+
Object {
|
|
45
|
+
"Operation": Object {
|
|
46
|
+
"PathItem": [Function],
|
|
47
|
+
"skip": [Function],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
Object {
|
|
51
|
+
"Operation": Object {
|
|
52
|
+
"PathItem": [Function],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
Object {
|
|
56
|
+
"Bar": Object {
|
|
57
|
+
"Baz": Object {
|
|
58
|
+
"Foo": [Function],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
]
|
|
63
|
+
`);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { isOrdered, buildVisitorObject, getIntersectionLength } from '../utils';
|
|
2
|
+
|
|
3
|
+
describe('Oas3 assertions', () => {
|
|
4
|
+
describe('Utils', () => {
|
|
5
|
+
describe('getCounts', () => {
|
|
6
|
+
it('should return the right counts', () => {
|
|
7
|
+
const arr = ['foo', 'bar', 'baz'];
|
|
8
|
+
expect(getIntersectionLength(arr, ['foo'])).toBe(1);
|
|
9
|
+
expect(getIntersectionLength(arr, ['foo', 'bar', 'baz'])).toBe(3);
|
|
10
|
+
expect(getIntersectionLength(arr, ['foo', 'test', 'baz'])).toBe(2);
|
|
11
|
+
expect(getIntersectionLength(arr, ['example', 'test'])).toBe(0);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('isOrdered', () => {
|
|
16
|
+
it('should say if array is ordered or not in specific direction', () => {
|
|
17
|
+
expect(isOrdered(['example', 'foo', 'test'], 'asc')).toBeTruthy();
|
|
18
|
+
expect(isOrdered(['example'], 'asc')).toBeTruthy();
|
|
19
|
+
expect(isOrdered(['test', 'foo', 'example'], 'desc')).toBeTruthy();
|
|
20
|
+
expect(isOrdered(['example'], 'desc')).toBeTruthy();
|
|
21
|
+
expect(isOrdered(['example', 'test', 'foo'], 'asc')).toBeFalsy();
|
|
22
|
+
expect(isOrdered(['example', 'foo', 'test'], 'desc')).toBeFalsy();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('buildVisitorObject', () => {
|
|
27
|
+
it('should return a consistent visitor structure', () => {
|
|
28
|
+
const context = [
|
|
29
|
+
{
|
|
30
|
+
type: 'Foo',
|
|
31
|
+
matchParentKeys: ['test'],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: 'Bar',
|
|
35
|
+
matchParentKeys: ['test'],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'Roof',
|
|
39
|
+
matchParentKeys: ['test'],
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const visitors = buildVisitorObject('Bar', context, () => {}) as any;
|
|
44
|
+
|
|
45
|
+
expect(visitors).toMatchInlineSnapshot(`
|
|
46
|
+
Object {
|
|
47
|
+
"Foo": Object {
|
|
48
|
+
"Bar": Object {
|
|
49
|
+
"Roof": Object {
|
|
50
|
+
"Bar": [Function],
|
|
51
|
+
"skip": [Function],
|
|
52
|
+
},
|
|
53
|
+
"skip": [Function],
|
|
54
|
+
},
|
|
55
|
+
"skip": [Function],
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should return the right visitor structure', () => {
|
|
62
|
+
const context = [
|
|
63
|
+
{
|
|
64
|
+
type: 'Operation',
|
|
65
|
+
matchParentKeys: ['put'],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: 'ResponsesMap',
|
|
69
|
+
matchParentKeys: [201, 200],
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const visitors = buildVisitorObject('MediaTypeMap', context, () => {}) as any;
|
|
74
|
+
|
|
75
|
+
expect(visitors).toMatchInlineSnapshot(`
|
|
76
|
+
Object {
|
|
77
|
+
"Operation": Object {
|
|
78
|
+
"ResponsesMap": Object {
|
|
79
|
+
"MediaTypeMap": [Function],
|
|
80
|
+
"skip": [Function],
|
|
81
|
+
},
|
|
82
|
+
"skip": [Function],
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
`);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { OrderOptions, OrderDirection, isOrdered, getIntersectionLength } from './utils';
|
|
2
|
+
|
|
3
|
+
type Asserts = Record<string, (value: any, condition: any) => boolean>;
|
|
4
|
+
|
|
5
|
+
export const 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
|
+
export const runOnValuesSet = new Set([
|
|
18
|
+
'pattern',
|
|
19
|
+
'enum',
|
|
20
|
+
'defined',
|
|
21
|
+
'undefined',
|
|
22
|
+
'nonEmpty',
|
|
23
|
+
'minLength',
|
|
24
|
+
'maxLength',
|
|
25
|
+
'casing',
|
|
26
|
+
'sortOrder',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
export const asserts: Asserts = {
|
|
30
|
+
pattern: (value: string | string[], condition: string): boolean => {
|
|
31
|
+
if (typeof value === 'undefined') 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: string | string[], condition: string[]): boolean => {
|
|
44
|
+
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
|
45
|
+
const values = typeof value === 'string' ? [value] : value;
|
|
46
|
+
for (let _val of values) {
|
|
47
|
+
if (!condition.includes(_val)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
},
|
|
53
|
+
defined: (value: string | undefined, condition: boolean = true): boolean => {
|
|
54
|
+
const isDefined = typeof value !== 'undefined';
|
|
55
|
+
return condition ? isDefined : !isDefined;
|
|
56
|
+
},
|
|
57
|
+
required: (value: string[], keys: string[]): boolean => {
|
|
58
|
+
for (const requiredKey of keys) {
|
|
59
|
+
if (!value.includes(requiredKey)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
},
|
|
65
|
+
disallowed: (value: string | string[], condition: string[]): boolean => {
|
|
66
|
+
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
|
67
|
+
const values = typeof value === 'string' ? [value] : value;
|
|
68
|
+
for (let _val of values) {
|
|
69
|
+
if (condition.includes(_val)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
},
|
|
75
|
+
undefined: (value: any, condition: boolean = true): boolean => {
|
|
76
|
+
const isUndefined = typeof value === 'undefined';
|
|
77
|
+
return condition ? isUndefined : !isUndefined;
|
|
78
|
+
},
|
|
79
|
+
nonEmpty: (value: string | undefined | null, condition: boolean = true): boolean => {
|
|
80
|
+
const isEmpty = typeof value === 'undefined' || value === null || value === '';
|
|
81
|
+
return condition ? !isEmpty : isEmpty;
|
|
82
|
+
},
|
|
83
|
+
minLength: (value: string | any[], condition: number): boolean => {
|
|
84
|
+
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
|
85
|
+
return value.length >= condition;
|
|
86
|
+
},
|
|
87
|
+
maxLength: (value: string | any[], condition: number): boolean => {
|
|
88
|
+
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
|
89
|
+
return value.length <= condition;
|
|
90
|
+
},
|
|
91
|
+
casing: (value: string | string[], condition: string): boolean => {
|
|
92
|
+
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
|
93
|
+
const values = typeof value === 'string' ? [value] : value;
|
|
94
|
+
for (let _val of values) {
|
|
95
|
+
let matchCase = false;
|
|
96
|
+
switch (condition) {
|
|
97
|
+
case 'camelCase':
|
|
98
|
+
matchCase = !!_val.match(/^[a-z][a-zA-Z0-9]+$/g);
|
|
99
|
+
break;
|
|
100
|
+
case 'kebab-case':
|
|
101
|
+
matchCase = !!_val.match(/^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/g);
|
|
102
|
+
break;
|
|
103
|
+
case 'snake_case':
|
|
104
|
+
matchCase = !!_val.match(/^([a-z][a-z0-9]*)(_[a-z0-9]+)*$/g);
|
|
105
|
+
break;
|
|
106
|
+
case 'PascalCase':
|
|
107
|
+
matchCase = !!_val.match(/^[A-Z][a-zA-Z0-9]+$/g);
|
|
108
|
+
break;
|
|
109
|
+
case 'MACRO_CASE':
|
|
110
|
+
matchCase = !!_val.match(/^([A-Z][A-Z0-9]*)(_[A-Z0-9]+)*$/g);
|
|
111
|
+
break;
|
|
112
|
+
case 'COBOL-CASE':
|
|
113
|
+
matchCase = !!_val.match(/^([A-Z][A-Z0-9]*)(-[A-Z0-9]+)*$/g);
|
|
114
|
+
break;
|
|
115
|
+
case 'flatcase':
|
|
116
|
+
matchCase = !!_val.match(/^[a-z][a-z0-9]+$/g);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
if (!matchCase) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
},
|
|
125
|
+
sortOrder: (value: any[], condition: OrderOptions | OrderDirection): boolean => {
|
|
126
|
+
if (typeof value === 'undefined') return true;
|
|
127
|
+
return isOrdered(value, condition);
|
|
128
|
+
},
|
|
129
|
+
mutuallyExclusive: (value: string[], condition: string[]): boolean => {
|
|
130
|
+
return getIntersectionLength(value, condition) < 2;
|
|
131
|
+
},
|
|
132
|
+
mutuallyRequired: (value: string[], condition: string[]): boolean => {
|
|
133
|
+
return getIntersectionLength(value, condition) > 0
|
|
134
|
+
? getIntersectionLength(value, condition) === condition.length
|
|
135
|
+
: true;
|
|
136
|
+
},
|
|
137
|
+
};
|