@redocly/openapi-core 1.0.0-beta.91 → 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.
@@ -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
+ };
@@ -0,0 +1,75 @@
1
+ import { asserts, runOnKeysSet, runOnValuesSet } from './asserts';
2
+ import { AssertToApply, buildSubjectVisitor, buildVisitorObject } from './utils';
3
+ import { Oas2Rule, Oas3Rule } from '../../../visitors';
4
+
5
+ export const Assertions: Oas3Rule | Oas2Rule = (opts: object) => {
6
+ let visitors: any[] = [];
7
+
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: any[] = Object.values(opts).filter(
14
+ (opt: unknown) => typeof opt === 'object' && opt !== null,
15
+ );
16
+
17
+ for (const [index, assertion] of assertions.entries()) {
18
+ const assertId =
19
+ (assertion.assertionId && `${assertion.assertionId} assertion`) || `assertion #${index + 1}`;
20
+
21
+ if (!assertion.subject) {
22
+ throw new Error(`${assertId}: 'subject' is required`);
23
+ }
24
+
25
+ const subjects: string[] = Array.isArray(assertion.subject)
26
+ ? assertion.subject
27
+ : [assertion.subject];
28
+
29
+ const assertsToApply: AssertToApply[] = Object.keys(asserts)
30
+ .filter((assertName: string) => assertion[assertName] !== undefined)
31
+ .map((assertName: string) => {
32
+ return {
33
+ assertId,
34
+ name: assertName,
35
+ conditions: assertion[assertName],
36
+ message: assertion.message,
37
+ severity: assertion.severity || 'error',
38
+ suggest: assertion.suggest || [],
39
+ runsOnKeys: runOnKeysSet.has(assertName),
40
+ runsOnValues: runOnValuesSet.has(assertName),
41
+ };
42
+ });
43
+
44
+ const shouldRunOnKeys: AssertToApply | undefined = assertsToApply.find(
45
+ (assert: AssertToApply) => assert.runsOnKeys && !assert.runsOnValues,
46
+ );
47
+ const shouldRunOnValues: AssertToApply | undefined = assertsToApply.find(
48
+ (assert: AssertToApply) => assert.runsOnValues && !assert.runsOnKeys,
49
+ );
50
+
51
+ if (shouldRunOnValues && !assertion.property) {
52
+ throw new Error(
53
+ `${shouldRunOnValues.name} can't be used on all keys. Please provide a single property.`,
54
+ );
55
+ }
56
+
57
+ if (shouldRunOnKeys && assertion.property) {
58
+ throw new Error(
59
+ `${shouldRunOnKeys.name} can't be used on a single property. Please use 'property'.`,
60
+ );
61
+ }
62
+
63
+ for (const subject of subjects) {
64
+ const subjectVisitor = buildSubjectVisitor(
65
+ assertion.property,
66
+ assertsToApply,
67
+ assertion.context,
68
+ );
69
+ const visitorObject = buildVisitorObject(subject, assertion.context, subjectVisitor);
70
+ visitors.push(visitorObject);
71
+ }
72
+ }
73
+
74
+ return visitors;
75
+ };