@redocly/openapi-core 1.0.0-beta.102 → 1.0.0-beta.103
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/config.d.ts +3 -3
- package/lib/config/load.d.ts +1 -1
- package/lib/config/load.js +15 -2
- package/lib/config/rules.d.ts +1 -1
- package/lib/config/types.d.ts +4 -2
- package/lib/decorators/common/filters/filter-helper.d.ts +3 -0
- package/lib/decorators/common/filters/filter-helper.js +67 -0
- package/lib/decorators/common/filters/filter-in.d.ts +2 -0
- package/lib/decorators/common/filters/filter-in.js +17 -0
- package/lib/decorators/common/filters/filter-out.d.ts +2 -0
- package/lib/decorators/common/filters/filter-out.js +17 -0
- package/lib/decorators/oas2/index.d.ts +2 -0
- package/lib/decorators/oas2/index.js +5 -1
- package/lib/decorators/oas3/index.d.ts +2 -0
- package/lib/decorators/oas3/index.js +5 -1
- package/lib/index.d.ts +1 -1
- package/lib/lint.d.ts +2 -0
- package/lib/lint.js +2 -2
- package/lib/redocly/registry-api-types.d.ts +2 -0
- package/lib/redocly/registry-api.d.ts +1 -1
- package/lib/redocly/registry-api.js +3 -1
- package/lib/rules/common/assertions/asserts.d.ts +6 -1
- package/lib/rules/common/assertions/asserts.js +81 -51
- package/lib/rules/common/assertions/utils.d.ts +2 -1
- package/lib/rules/common/assertions/utils.js +27 -8
- package/lib/types/redocly-yaml.js +316 -27
- package/lib/utils.d.ts +2 -1
- package/lib/utils.js +5 -1
- package/lib/walk.d.ts +2 -0
- package/lib/walk.js +16 -7
- package/package.json +1 -1
- package/src/__tests__/lint.test.ts +17 -4
- package/src/config/__tests__/load.test.ts +6 -0
- package/src/config/load.ts +28 -5
- package/src/config/types.ts +6 -5
- package/src/decorators/__tests__/filter-in.test.ts +310 -0
- package/src/decorators/__tests__/filter-out.test.ts +227 -0
- package/src/decorators/common/filters/filter-helper.ts +72 -0
- package/src/decorators/common/filters/filter-in.ts +18 -0
- package/src/decorators/common/filters/filter-out.ts +18 -0
- package/src/decorators/oas2/index.ts +5 -1
- package/src/decorators/oas3/index.ts +5 -1
- package/src/index.ts +1 -0
- package/src/lint.ts +4 -3
- package/src/redocly/registry-api-types.ts +2 -0
- package/src/redocly/registry-api.ts +4 -0
- package/src/rules/common/assertions/__tests__/asserts.test.ts +149 -146
- package/src/rules/common/assertions/asserts.ts +97 -52
- package/src/rules/common/assertions/utils.ts +41 -16
- package/src/types/redocly-yaml.ts +321 -34
- package/src/utils.ts +10 -7
- package/src/walk.ts +39 -19
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Location } from '../../../ref-utils';
|
|
2
|
+
import { isString as runOnValue } from '../../../utils';
|
|
3
|
+
import {
|
|
4
|
+
OrderOptions,
|
|
5
|
+
OrderDirection,
|
|
6
|
+
isOrdered,
|
|
7
|
+
getIntersectionLength,
|
|
8
|
+
regexFromString,
|
|
9
|
+
} from './utils';
|
|
2
10
|
|
|
3
|
-
type
|
|
11
|
+
type AssertResult = { isValid: boolean; location?: Location };
|
|
12
|
+
type Asserts = Record<
|
|
13
|
+
string,
|
|
14
|
+
(value: any, condition: any, baseLocation: Location, rawValue?: any) => AssertResult
|
|
15
|
+
>;
|
|
4
16
|
|
|
5
17
|
export const runOnKeysSet = new Set([
|
|
6
18
|
'mutuallyExclusive',
|
|
@@ -13,6 +25,8 @@ export const runOnKeysSet = new Set([
|
|
|
13
25
|
'sortOrder',
|
|
14
26
|
'disallowed',
|
|
15
27
|
'required',
|
|
28
|
+
'requireAny',
|
|
29
|
+
'ref',
|
|
16
30
|
]);
|
|
17
31
|
export const runOnValuesSet = new Set([
|
|
18
32
|
'pattern',
|
|
@@ -24,73 +38,82 @@ export const runOnValuesSet = new Set([
|
|
|
24
38
|
'maxLength',
|
|
25
39
|
'casing',
|
|
26
40
|
'sortOrder',
|
|
41
|
+
'ref',
|
|
27
42
|
]);
|
|
28
43
|
|
|
29
44
|
export const asserts: Asserts = {
|
|
30
|
-
pattern: (value: string | string[], condition: string
|
|
31
|
-
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
|
32
|
-
const values =
|
|
33
|
-
const
|
|
34
|
-
condition = condition.slice(1).replace(regexOptions[0], '');
|
|
35
|
-
const regx = new RegExp(condition, regexOptions[0].slice(1));
|
|
45
|
+
pattern: (value: string | string[], condition: string, baseLocation: Location) => {
|
|
46
|
+
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
47
|
+
const values = runOnValue(value) ? [value] : value;
|
|
48
|
+
const regx = regexFromString(condition);
|
|
36
49
|
for (let _val of values) {
|
|
37
|
-
if (!_val
|
|
38
|
-
return false;
|
|
50
|
+
if (!regx?.test(_val)) {
|
|
51
|
+
return { isValid: false, location: runOnValue(value) ? baseLocation : baseLocation.key() };
|
|
39
52
|
}
|
|
40
53
|
}
|
|
41
|
-
return true;
|
|
54
|
+
return { isValid: true };
|
|
42
55
|
},
|
|
43
|
-
enum: (value: string | string[], condition: string[]
|
|
44
|
-
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
|
45
|
-
const values =
|
|
56
|
+
enum: (value: string | string[], condition: string[], baseLocation: Location) => {
|
|
57
|
+
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
58
|
+
const values = runOnValue(value) ? [value] : value;
|
|
46
59
|
for (let _val of values) {
|
|
47
60
|
if (!condition.includes(_val)) {
|
|
48
|
-
return
|
|
61
|
+
return {
|
|
62
|
+
isValid: false,
|
|
63
|
+
location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
|
|
64
|
+
};
|
|
49
65
|
}
|
|
50
66
|
}
|
|
51
|
-
return true;
|
|
67
|
+
return { isValid: true };
|
|
52
68
|
},
|
|
53
|
-
defined: (value: string | undefined, condition: boolean = true
|
|
69
|
+
defined: (value: string | undefined, condition: boolean = true, baseLocation: Location) => {
|
|
54
70
|
const isDefined = typeof value !== 'undefined';
|
|
55
|
-
return condition ? isDefined : !isDefined;
|
|
71
|
+
return { isValid: condition ? isDefined : !isDefined, location: baseLocation };
|
|
56
72
|
},
|
|
57
|
-
required: (value: string[], keys: string[]
|
|
73
|
+
required: (value: string[], keys: string[], baseLocation: Location) => {
|
|
58
74
|
for (const requiredKey of keys) {
|
|
59
75
|
if (!value.includes(requiredKey)) {
|
|
60
|
-
return false;
|
|
76
|
+
return { isValid: false, location: baseLocation.key() };
|
|
61
77
|
}
|
|
62
78
|
}
|
|
63
|
-
return true;
|
|
79
|
+
return { isValid: true };
|
|
64
80
|
},
|
|
65
|
-
disallowed: (value: string | string[], condition: string[]
|
|
66
|
-
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
|
67
|
-
const values =
|
|
81
|
+
disallowed: (value: string | string[], condition: string[], baseLocation: Location) => {
|
|
82
|
+
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
83
|
+
const values = runOnValue(value) ? [value] : value;
|
|
68
84
|
for (let _val of values) {
|
|
69
85
|
if (condition.includes(_val)) {
|
|
70
|
-
return
|
|
86
|
+
return {
|
|
87
|
+
isValid: false,
|
|
88
|
+
location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
|
|
89
|
+
};
|
|
71
90
|
}
|
|
72
91
|
}
|
|
73
|
-
return true;
|
|
92
|
+
return { isValid: true };
|
|
74
93
|
},
|
|
75
|
-
undefined: (value: any, condition: boolean = true
|
|
94
|
+
undefined: (value: any, condition: boolean = true, baseLocation: Location) => {
|
|
76
95
|
const isUndefined = typeof value === 'undefined';
|
|
77
|
-
return condition ? isUndefined : !isUndefined;
|
|
96
|
+
return { isValid: condition ? isUndefined : !isUndefined, location: baseLocation };
|
|
78
97
|
},
|
|
79
|
-
nonEmpty: (
|
|
98
|
+
nonEmpty: (
|
|
99
|
+
value: string | undefined | null,
|
|
100
|
+
condition: boolean = true,
|
|
101
|
+
baseLocation: Location
|
|
102
|
+
) => {
|
|
80
103
|
const isEmpty = typeof value === 'undefined' || value === null || value === '';
|
|
81
|
-
return condition ? !isEmpty : isEmpty;
|
|
104
|
+
return { isValid: condition ? !isEmpty : isEmpty, location: baseLocation };
|
|
82
105
|
},
|
|
83
|
-
minLength: (value: string | any[], condition: number
|
|
84
|
-
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
|
85
|
-
return value.length >= condition;
|
|
106
|
+
minLength: (value: string | any[], condition: number, baseLocation: Location) => {
|
|
107
|
+
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
108
|
+
return { isValid: value.length >= condition, location: baseLocation };
|
|
86
109
|
},
|
|
87
|
-
maxLength: (value: string | any[], condition: number
|
|
88
|
-
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
|
89
|
-
return value.length <= condition;
|
|
110
|
+
maxLength: (value: string | any[], condition: number, baseLocation: Location) => {
|
|
111
|
+
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
112
|
+
return { isValid: value.length <= condition, location: baseLocation };
|
|
90
113
|
},
|
|
91
|
-
casing: (value: string | string[], condition: string
|
|
92
|
-
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
|
93
|
-
const values =
|
|
114
|
+
casing: (value: string | string[], condition: string, baseLocation: Location) => {
|
|
115
|
+
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
116
|
+
const values: string[] = runOnValue(value) ? [value] : value;
|
|
94
117
|
for (let _val of values) {
|
|
95
118
|
let matchCase = false;
|
|
96
119
|
switch (condition) {
|
|
@@ -117,24 +140,46 @@ export const asserts: Asserts = {
|
|
|
117
140
|
break;
|
|
118
141
|
}
|
|
119
142
|
if (!matchCase) {
|
|
120
|
-
return
|
|
143
|
+
return {
|
|
144
|
+
isValid: false,
|
|
145
|
+
location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
|
|
146
|
+
};
|
|
121
147
|
}
|
|
122
148
|
}
|
|
123
|
-
return true;
|
|
149
|
+
return { isValid: true };
|
|
124
150
|
},
|
|
125
|
-
sortOrder: (value: any[], condition: OrderOptions | OrderDirection
|
|
126
|
-
if (typeof value === 'undefined') return true;
|
|
127
|
-
return isOrdered(value, condition);
|
|
151
|
+
sortOrder: (value: any[], condition: OrderOptions | OrderDirection, baseLocation: Location) => {
|
|
152
|
+
if (typeof value === 'undefined') return { isValid: true };
|
|
153
|
+
return { isValid: isOrdered(value, condition), location: baseLocation };
|
|
128
154
|
},
|
|
129
|
-
mutuallyExclusive: (value: string[], condition: string[]
|
|
130
|
-
return getIntersectionLength(value, condition) < 2;
|
|
155
|
+
mutuallyExclusive: (value: string[], condition: string[], baseLocation: Location) => {
|
|
156
|
+
return { isValid: getIntersectionLength(value, condition) < 2, location: baseLocation.key() };
|
|
131
157
|
},
|
|
132
|
-
mutuallyRequired: (value: string[], condition: string[]
|
|
133
|
-
return
|
|
134
|
-
|
|
135
|
-
|
|
158
|
+
mutuallyRequired: (value: string[], condition: string[], baseLocation: Location) => {
|
|
159
|
+
return {
|
|
160
|
+
isValid:
|
|
161
|
+
getIntersectionLength(value, condition) > 0
|
|
162
|
+
? getIntersectionLength(value, condition) === condition.length
|
|
163
|
+
: true,
|
|
164
|
+
location: baseLocation.key(),
|
|
165
|
+
};
|
|
136
166
|
},
|
|
137
|
-
requireAny: (value: string[], condition: string[]
|
|
138
|
-
return getIntersectionLength(value, condition) >= 1;
|
|
167
|
+
requireAny: (value: string[], condition: string[], baseLocation: Location) => {
|
|
168
|
+
return { isValid: getIntersectionLength(value, condition) >= 1, location: baseLocation.key() };
|
|
169
|
+
},
|
|
170
|
+
ref: (_value: any, condition: string | boolean, baseLocation, rawValue: any) => {
|
|
171
|
+
if (typeof rawValue === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
|
172
|
+
const hasRef = rawValue.hasOwnProperty('$ref');
|
|
173
|
+
if (typeof condition === 'boolean') {
|
|
174
|
+
return {
|
|
175
|
+
isValid: condition ? hasRef : !hasRef,
|
|
176
|
+
location: hasRef ? baseLocation : baseLocation.key(),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const regex = regexFromString(condition);
|
|
180
|
+
return {
|
|
181
|
+
isValid: hasRef && regex?.test(rawValue['$ref']),
|
|
182
|
+
location: hasRef ? baseLocation : baseLocation.key(),
|
|
183
|
+
};
|
|
139
184
|
},
|
|
140
185
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRef } from '../../../ref-utils';
|
|
1
|
+
import { isRef, Location } from '../../../ref-utils';
|
|
2
2
|
import { Problem, ProblemSeverity, UserContext } from '../../../walk';
|
|
3
3
|
import { asserts } from './asserts';
|
|
4
4
|
|
|
@@ -23,7 +23,7 @@ export type AssertToApply = {
|
|
|
23
23
|
export function buildVisitorObject(
|
|
24
24
|
subject: string,
|
|
25
25
|
context: Record<string, any>[],
|
|
26
|
-
subjectVisitor: any
|
|
26
|
+
subjectVisitor: any
|
|
27
27
|
) {
|
|
28
28
|
if (!context) {
|
|
29
29
|
return { [subject]: subjectVisitor };
|
|
@@ -46,7 +46,7 @@ export function buildVisitorObject(
|
|
|
46
46
|
|
|
47
47
|
if (matchParentKeys && excludeParentKeys) {
|
|
48
48
|
throw new Error(
|
|
49
|
-
`Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item
|
|
49
|
+
`Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item`
|
|
50
50
|
);
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -75,9 +75,12 @@ export function buildVisitorObject(
|
|
|
75
75
|
export function buildSubjectVisitor(
|
|
76
76
|
properties: string | string[],
|
|
77
77
|
asserts: AssertToApply[],
|
|
78
|
-
context?: Record<string, any>[]
|
|
78
|
+
context?: Record<string, any>[]
|
|
79
79
|
) {
|
|
80
|
-
return
|
|
80
|
+
return (
|
|
81
|
+
node: any,
|
|
82
|
+
{ report, location, rawLocation, key, type, resolve, rawNode }: UserContext
|
|
83
|
+
) => {
|
|
81
84
|
// We need to check context's last node if it has the same type as subject node;
|
|
82
85
|
// if yes - that means we didn't create context's last node visitor,
|
|
83
86
|
// so we need to handle 'matchParentKeys' and 'excludeParentKeys' conditions here;
|
|
@@ -101,14 +104,28 @@ export function buildSubjectVisitor(
|
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
for (const assert of asserts) {
|
|
107
|
+
const currentLocation = assert.name === 'ref' ? rawLocation : location;
|
|
104
108
|
if (properties) {
|
|
105
109
|
for (const property of properties) {
|
|
106
110
|
// we can have resolvable scalar so need to resolve value here.
|
|
107
111
|
const value = isRef(node[property]) ? resolve(node[property])?.node : node[property];
|
|
108
|
-
runAssertion(
|
|
112
|
+
runAssertion({
|
|
113
|
+
values: value,
|
|
114
|
+
rawValues: rawNode[property],
|
|
115
|
+
assert,
|
|
116
|
+
location: currentLocation.child(property),
|
|
117
|
+
report,
|
|
118
|
+
});
|
|
109
119
|
}
|
|
110
120
|
} else {
|
|
111
|
-
|
|
121
|
+
const value = assert.name === 'ref' ? rawNode : Object.keys(node);
|
|
122
|
+
runAssertion({
|
|
123
|
+
values: Object.keys(node),
|
|
124
|
+
rawValues: value,
|
|
125
|
+
assert,
|
|
126
|
+
location: currentLocation,
|
|
127
|
+
report,
|
|
128
|
+
});
|
|
112
129
|
}
|
|
113
130
|
}
|
|
114
131
|
};
|
|
@@ -148,20 +165,28 @@ export function isOrdered(value: any[], options: OrderOptions | OrderDirection):
|
|
|
148
165
|
return true;
|
|
149
166
|
}
|
|
150
167
|
|
|
151
|
-
|
|
152
|
-
values: string | string[]
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
type RunAssertionParams = {
|
|
169
|
+
values: string | string[];
|
|
170
|
+
rawValues: any;
|
|
171
|
+
assert: AssertToApply;
|
|
172
|
+
location: Location;
|
|
173
|
+
report: (problem: Problem) => void;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
function runAssertion({ values, rawValues, assert, location, report }: RunAssertionParams) {
|
|
177
|
+
const lintResult = asserts[assert.name](values, assert.conditions, location, rawValues);
|
|
178
|
+
if (!lintResult.isValid) {
|
|
159
179
|
report({
|
|
160
180
|
message: assert.message || `The ${assert.assertId} doesn't meet required conditions`,
|
|
161
|
-
location,
|
|
181
|
+
location: lintResult.location || location,
|
|
162
182
|
forceSeverity: assert.severity,
|
|
163
183
|
suggest: assert.suggest,
|
|
164
184
|
ruleId: assert.assertId,
|
|
165
185
|
});
|
|
166
186
|
}
|
|
167
187
|
}
|
|
188
|
+
|
|
189
|
+
export function regexFromString(input: string): RegExp | null {
|
|
190
|
+
const matches = input.match(/^\/(.*)\/(.*)|(.*)/);
|
|
191
|
+
return matches && new RegExp(matches[1] || matches[3], matches[2]);
|
|
192
|
+
}
|