@redocly/openapi-core 1.0.0-beta.124 → 1.0.0-beta.126
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/bundle.d.ts +7 -12
- package/lib/config/all.js +8 -1
- package/lib/config/types.d.ts +5 -1
- package/lib/resolve.js +5 -1
- package/lib/rules/common/assertions/asserts.d.ts +6 -2
- package/lib/rules/common/assertions/asserts.js +30 -20
- package/lib/rules/common/assertions/utils.d.ts +8 -0
- package/lib/rules/common/assertions/utils.js +28 -34
- package/lib/rules/common/required-string-property-missing-min-length.d.ts +2 -0
- package/lib/rules/common/required-string-property-missing-min-length.js +37 -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 +1 -0
- package/package.json +1 -1
- package/src/__tests__/resolve.test.ts +22 -0
- package/src/bundle.ts +11 -2
- package/src/config/all.ts +8 -1
- package/src/config/types.ts +4 -1
- package/src/resolve.ts +6 -1
- package/src/rules/__tests__/no-unresolved-refs.test.ts +1 -1
- package/src/rules/common/assertions/__tests__/asserts.test.ts +264 -178
- package/src/rules/common/assertions/__tests__/index.test.ts +1 -1
- package/src/rules/common/assertions/__tests__/utils.test.ts +122 -1
- package/src/rules/common/assertions/asserts.ts +59 -28
- package/src/rules/common/assertions/utils.ts +46 -44
- package/src/rules/common/required-string-property-missing-min-length.ts +44 -0
- package/src/rules/oas2/index.ts +2 -0
- package/src/rules/oas3/index.ts +2 -0
- package/src/types/redocly-yaml.ts +1 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Assertion, AssertionDefinition } from '..';
|
|
2
|
-
import {
|
|
2
|
+
import { AssertionContext } from '../../../../config';
|
|
3
|
+
import { Location } from '../../../../ref-utils';
|
|
4
|
+
import { Source } from '../../../../resolve';
|
|
5
|
+
import { isOrdered, buildVisitorObject, getIntersectionLength, runAssertion } from '../utils';
|
|
3
6
|
|
|
4
7
|
describe('Oas3 assertions', () => {
|
|
5
8
|
describe('Utils', () => {
|
|
@@ -111,5 +114,123 @@ describe('Oas3 assertions', () => {
|
|
|
111
114
|
`);
|
|
112
115
|
});
|
|
113
116
|
});
|
|
117
|
+
|
|
118
|
+
describe('runAssertion', () => {
|
|
119
|
+
const baseLocation = new Location(jest.fn() as any as Source, 'pointer');
|
|
120
|
+
const rawLocation = new Location(jest.fn() as any as Source, 'raw-pointer');
|
|
121
|
+
// { $ref: 'text' }, true, {...assertionProperties, rawValue: { $ref: 'text' }}
|
|
122
|
+
|
|
123
|
+
const ctxStub = {
|
|
124
|
+
location: baseLocation,
|
|
125
|
+
node: {
|
|
126
|
+
property: 'test',
|
|
127
|
+
},
|
|
128
|
+
rawNode: {
|
|
129
|
+
property: 'test',
|
|
130
|
+
},
|
|
131
|
+
rawLocation: rawLocation,
|
|
132
|
+
} as AssertionContext;
|
|
133
|
+
|
|
134
|
+
it('should catch error cause property should be not defined with assertionProperty', () => {
|
|
135
|
+
const result = runAssertion({
|
|
136
|
+
assert: {
|
|
137
|
+
name: 'defined',
|
|
138
|
+
conditions: false,
|
|
139
|
+
runsOnKeys: true,
|
|
140
|
+
runsOnValues: false,
|
|
141
|
+
},
|
|
142
|
+
ctx: ctxStub,
|
|
143
|
+
assertionProperty: 'property',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const expectedLocation = new Location(jest.fn() as any as Source, 'pointer/property');
|
|
147
|
+
|
|
148
|
+
expect(JSON.stringify(result)).toEqual(
|
|
149
|
+
JSON.stringify([
|
|
150
|
+
{
|
|
151
|
+
message: 'Should be not defined',
|
|
152
|
+
location: expectedLocation,
|
|
153
|
+
},
|
|
154
|
+
])
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should pass cause property defined', () => {
|
|
159
|
+
const result = runAssertion({
|
|
160
|
+
assert: {
|
|
161
|
+
name: 'defined',
|
|
162
|
+
conditions: true,
|
|
163
|
+
runsOnKeys: true,
|
|
164
|
+
runsOnValues: false,
|
|
165
|
+
},
|
|
166
|
+
ctx: ctxStub,
|
|
167
|
+
assertionProperty: 'property',
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(result).toEqual([]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should failure cause property does not passed', () => {
|
|
174
|
+
const result = runAssertion({
|
|
175
|
+
assert: {
|
|
176
|
+
name: 'defined',
|
|
177
|
+
conditions: false,
|
|
178
|
+
runsOnKeys: true,
|
|
179
|
+
runsOnValues: false,
|
|
180
|
+
},
|
|
181
|
+
ctx: ctxStub,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(result).toEqual([
|
|
185
|
+
{
|
|
186
|
+
message: 'Should be not defined',
|
|
187
|
+
location: baseLocation,
|
|
188
|
+
},
|
|
189
|
+
]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should pass with ref assertion cause it is defined', () => {
|
|
193
|
+
const result = runAssertion({
|
|
194
|
+
assert: {
|
|
195
|
+
name: 'ref',
|
|
196
|
+
conditions: true,
|
|
197
|
+
runsOnKeys: true,
|
|
198
|
+
runsOnValues: false,
|
|
199
|
+
},
|
|
200
|
+
ctx: {
|
|
201
|
+
...ctxStub,
|
|
202
|
+
rawNode: {
|
|
203
|
+
$ref: 'test',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(result).toEqual([]);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should failure with ref assertion cause it is defined', () => {
|
|
212
|
+
const result = runAssertion({
|
|
213
|
+
assert: {
|
|
214
|
+
name: 'ref',
|
|
215
|
+
conditions: false,
|
|
216
|
+
runsOnKeys: true,
|
|
217
|
+
runsOnValues: false,
|
|
218
|
+
},
|
|
219
|
+
ctx: {
|
|
220
|
+
...ctxStub,
|
|
221
|
+
rawNode: {
|
|
222
|
+
$ref: 'test',
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(result).toEqual([
|
|
228
|
+
{
|
|
229
|
+
message: 'should not use $ref',
|
|
230
|
+
location: rawLocation,
|
|
231
|
+
},
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
114
235
|
});
|
|
115
236
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AssertResult, CustomFunction } from 'core/src/config/types';
|
|
1
|
+
import { AssertionContext, AssertResult, CustomFunction } from 'core/src/config/types';
|
|
2
2
|
import { Location } from '../../../ref-utils';
|
|
3
3
|
import { isPlainObject, isString as runOnValue, isTruthy } from '../../../utils';
|
|
4
4
|
import {
|
|
@@ -9,12 +9,9 @@ import {
|
|
|
9
9
|
regexFromString,
|
|
10
10
|
} from './utils';
|
|
11
11
|
|
|
12
|
-
export type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
baseLocation: Location,
|
|
16
|
-
rawValue?: any
|
|
17
|
-
) => AssertResult[];
|
|
12
|
+
export type AssertionFnContext = AssertionContext & { baseLocation: Location; rawValue?: any };
|
|
13
|
+
|
|
14
|
+
export type AssertionFn = (value: any, condition: any, ctx: AssertionFnContext) => AssertResult[];
|
|
18
15
|
|
|
19
16
|
export type Asserts = {
|
|
20
17
|
pattern: AssertionFn;
|
|
@@ -69,7 +66,7 @@ export const runOnValuesSet = new Set<keyof Asserts>([
|
|
|
69
66
|
]);
|
|
70
67
|
|
|
71
68
|
export const asserts: Asserts = {
|
|
72
|
-
pattern: (value: string | string[], condition: string, baseLocation:
|
|
69
|
+
pattern: (value: string | string[], condition: string, { baseLocation }: AssertionFnContext) => {
|
|
73
70
|
if (typeof value === 'undefined' || isPlainObject(value)) return []; // property doesn't exist or is an object, no need to lint it with this assert
|
|
74
71
|
const values = Array.isArray(value) ? value : [value];
|
|
75
72
|
const regex = regexFromString(condition);
|
|
@@ -84,7 +81,11 @@ export const asserts: Asserts = {
|
|
|
84
81
|
)
|
|
85
82
|
.filter(isTruthy);
|
|
86
83
|
},
|
|
87
|
-
notPattern: (
|
|
84
|
+
notPattern: (
|
|
85
|
+
value: string | string[],
|
|
86
|
+
condition: string,
|
|
87
|
+
{ baseLocation }: AssertionFnContext
|
|
88
|
+
) => {
|
|
88
89
|
if (typeof value === 'undefined' || isPlainObject(value)) return []; // property doesn't exist or is an object, no need to lint it with this assert
|
|
89
90
|
const values = Array.isArray(value) ? value : [value];
|
|
90
91
|
const regex = regexFromString(condition);
|
|
@@ -99,7 +100,7 @@ export const asserts: Asserts = {
|
|
|
99
100
|
)
|
|
100
101
|
.filter(isTruthy);
|
|
101
102
|
},
|
|
102
|
-
enum: (value: string | string[], condition: string[], baseLocation:
|
|
103
|
+
enum: (value: string | string[], condition: string[], { baseLocation }: AssertionFnContext) => {
|
|
103
104
|
if (typeof value === 'undefined' || isPlainObject(value)) return []; // property doesn't exist or is an object, no need to lint it with this assert
|
|
104
105
|
const values = Array.isArray(value) ? value : [value];
|
|
105
106
|
return values
|
|
@@ -112,7 +113,11 @@ export const asserts: Asserts = {
|
|
|
112
113
|
)
|
|
113
114
|
.filter(isTruthy);
|
|
114
115
|
},
|
|
115
|
-
defined: (
|
|
116
|
+
defined: (
|
|
117
|
+
value: string | undefined,
|
|
118
|
+
condition: boolean = true,
|
|
119
|
+
{ baseLocation }: AssertionFnContext
|
|
120
|
+
) => {
|
|
116
121
|
const isDefined = typeof value !== 'undefined';
|
|
117
122
|
const isValid = condition ? isDefined : !isDefined;
|
|
118
123
|
return isValid
|
|
@@ -124,7 +129,7 @@ export const asserts: Asserts = {
|
|
|
124
129
|
},
|
|
125
130
|
];
|
|
126
131
|
},
|
|
127
|
-
required: (value: string[], keys: string[], baseLocation:
|
|
132
|
+
required: (value: string[], keys: string[], { baseLocation }: AssertionFnContext) => {
|
|
128
133
|
return keys
|
|
129
134
|
.map(
|
|
130
135
|
(requiredKey) =>
|
|
@@ -135,7 +140,11 @@ export const asserts: Asserts = {
|
|
|
135
140
|
)
|
|
136
141
|
.filter(isTruthy);
|
|
137
142
|
},
|
|
138
|
-
disallowed: (
|
|
143
|
+
disallowed: (
|
|
144
|
+
value: string | string[],
|
|
145
|
+
condition: string[],
|
|
146
|
+
{ baseLocation }: AssertionFnContext
|
|
147
|
+
) => {
|
|
139
148
|
if (typeof value === 'undefined' || isPlainObject(value)) return []; // property doesn't exist or is an object, no need to lint it with this assert
|
|
140
149
|
const values = Array.isArray(value) ? value : [value];
|
|
141
150
|
return values
|
|
@@ -151,7 +160,7 @@ export const asserts: Asserts = {
|
|
|
151
160
|
const: (
|
|
152
161
|
value: string | number | boolean | string[] | number[],
|
|
153
162
|
condition: string | number | boolean,
|
|
154
|
-
baseLocation:
|
|
163
|
+
{ baseLocation }: AssertionFnContext
|
|
155
164
|
) => {
|
|
156
165
|
if (typeof value === 'undefined') return [];
|
|
157
166
|
|
|
@@ -176,7 +185,7 @@ export const asserts: Asserts = {
|
|
|
176
185
|
: [];
|
|
177
186
|
}
|
|
178
187
|
},
|
|
179
|
-
undefined: (value: unknown, condition: boolean = true, baseLocation:
|
|
188
|
+
undefined: (value: unknown, condition: boolean = true, { baseLocation }: AssertionFnContext) => {
|
|
180
189
|
const isUndefined = typeof value === 'undefined';
|
|
181
190
|
const isValid = condition ? isUndefined : !isUndefined;
|
|
182
191
|
return isValid
|
|
@@ -191,7 +200,7 @@ export const asserts: Asserts = {
|
|
|
191
200
|
nonEmpty: (
|
|
192
201
|
value: string | undefined | null,
|
|
193
202
|
condition: boolean = true,
|
|
194
|
-
baseLocation:
|
|
203
|
+
{ baseLocation }: AssertionFnContext
|
|
195
204
|
) => {
|
|
196
205
|
const isEmpty = typeof value === 'undefined' || value === null || value === '';
|
|
197
206
|
const isValid = condition ? !isEmpty : isEmpty;
|
|
@@ -204,15 +213,25 @@ export const asserts: Asserts = {
|
|
|
204
213
|
},
|
|
205
214
|
];
|
|
206
215
|
},
|
|
207
|
-
minLength: (value: string | any[], condition: number, baseLocation:
|
|
216
|
+
minLength: (value: string | any[], condition: number, { baseLocation }: AssertionFnContext) => {
|
|
208
217
|
if (typeof value === 'undefined' || value.length >= condition) return []; // property doesn't exist, no need to lint it with this assert
|
|
209
|
-
return [
|
|
218
|
+
return [
|
|
219
|
+
{
|
|
220
|
+
message: `Should have at least ${condition} characters`,
|
|
221
|
+
location: baseLocation,
|
|
222
|
+
},
|
|
223
|
+
];
|
|
210
224
|
},
|
|
211
|
-
maxLength: (value: string | any[], condition: number, baseLocation:
|
|
225
|
+
maxLength: (value: string | any[], condition: number, { baseLocation }: AssertionFnContext) => {
|
|
212
226
|
if (typeof value === 'undefined' || value.length <= condition) return []; // property doesn't exist, no need to lint it with this assert
|
|
213
|
-
return [
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
message: `Should have at most ${condition} characters`,
|
|
230
|
+
location: baseLocation,
|
|
231
|
+
},
|
|
232
|
+
];
|
|
214
233
|
},
|
|
215
|
-
casing: (value: string | string[], condition: string, baseLocation:
|
|
234
|
+
casing: (value: string | string[], condition: string, { baseLocation }: AssertionFnContext) => {
|
|
216
235
|
if (typeof value === 'undefined' || isPlainObject(value)) return []; // property doesn't exist or is an object, no need to lint it with this assert
|
|
217
236
|
const values = Array.isArray(value) ? value : [value];
|
|
218
237
|
const casingRegexes: Record<string, RegExp> = {
|
|
@@ -237,7 +256,7 @@ export const asserts: Asserts = {
|
|
|
237
256
|
sortOrder: (
|
|
238
257
|
value: unknown[],
|
|
239
258
|
condition: OrderOptions | OrderDirection,
|
|
240
|
-
baseLocation:
|
|
259
|
+
{ baseLocation }: AssertionFnContext
|
|
241
260
|
) => {
|
|
242
261
|
const direction = (condition as OrderOptions).direction || (condition as OrderDirection);
|
|
243
262
|
const property = (condition as OrderOptions).property;
|
|
@@ -260,7 +279,11 @@ export const asserts: Asserts = {
|
|
|
260
279
|
},
|
|
261
280
|
];
|
|
262
281
|
},
|
|
263
|
-
mutuallyExclusive: (
|
|
282
|
+
mutuallyExclusive: (
|
|
283
|
+
value: string[],
|
|
284
|
+
condition: string[],
|
|
285
|
+
{ baseLocation }: AssertionFnContext
|
|
286
|
+
) => {
|
|
264
287
|
if (getIntersectionLength(value, condition) < 2) return [];
|
|
265
288
|
return [
|
|
266
289
|
{
|
|
@@ -269,7 +292,11 @@ export const asserts: Asserts = {
|
|
|
269
292
|
},
|
|
270
293
|
];
|
|
271
294
|
},
|
|
272
|
-
mutuallyRequired: (
|
|
295
|
+
mutuallyRequired: (
|
|
296
|
+
value: string[],
|
|
297
|
+
condition: string[],
|
|
298
|
+
{ baseLocation }: AssertionFnContext
|
|
299
|
+
) => {
|
|
273
300
|
const isValid =
|
|
274
301
|
getIntersectionLength(value, condition) > 0
|
|
275
302
|
? getIntersectionLength(value, condition) === condition.length
|
|
@@ -283,7 +310,7 @@ export const asserts: Asserts = {
|
|
|
283
310
|
},
|
|
284
311
|
];
|
|
285
312
|
},
|
|
286
|
-
requireAny: (value: string[], condition: string[], baseLocation:
|
|
313
|
+
requireAny: (value: string[], condition: string[], { baseLocation }: AssertionFnContext) => {
|
|
287
314
|
return getIntersectionLength(value, condition) >= 1
|
|
288
315
|
? []
|
|
289
316
|
: [
|
|
@@ -293,7 +320,11 @@ export const asserts: Asserts = {
|
|
|
293
320
|
},
|
|
294
321
|
];
|
|
295
322
|
},
|
|
296
|
-
ref: (
|
|
323
|
+
ref: (
|
|
324
|
+
_value: unknown,
|
|
325
|
+
condition: string | boolean,
|
|
326
|
+
{ baseLocation, rawValue }: AssertionFnContext
|
|
327
|
+
) => {
|
|
297
328
|
if (typeof rawValue === 'undefined') return []; // property doesn't exist, no need to lint it with this assert
|
|
298
329
|
const hasRef = rawValue.hasOwnProperty('$ref');
|
|
299
330
|
if (typeof condition === 'boolean') {
|
|
@@ -321,6 +352,6 @@ export const asserts: Asserts = {
|
|
|
321
352
|
};
|
|
322
353
|
|
|
323
354
|
export function buildAssertCustomFunction(fn: CustomFunction): AssertionFn {
|
|
324
|
-
return (value: string[], options: any,
|
|
325
|
-
fn.call(null, value, options,
|
|
355
|
+
return (value: string[], options: any, ctx: AssertionFnContext) =>
|
|
356
|
+
fn.call(null, value, options, ctx);
|
|
326
357
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { asserts, runOnKeysSet, runOnValuesSet, Asserts } from './asserts';
|
|
2
2
|
import { colorize } from '../../../logger';
|
|
3
|
-
import { isRef
|
|
3
|
+
import { isRef } from '../../../ref-utils';
|
|
4
4
|
import { isTruthy, keysOf, isString } from '../../../utils';
|
|
5
|
-
import type { AssertResult } from '../../../config';
|
|
5
|
+
import type { AssertionContext, AssertResult } from '../../../config';
|
|
6
6
|
import type { Assertion, AssertionDefinition, AssertionLocators } from '.';
|
|
7
7
|
import type {
|
|
8
8
|
Oas2Visitor,
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
SkipFunctionContext,
|
|
11
11
|
VisitFunction,
|
|
12
12
|
} from '../../../visitors';
|
|
13
|
+
import { UserContext } from 'core/src/walk';
|
|
13
14
|
|
|
14
15
|
export type OrderDirection = 'asc' | 'desc';
|
|
15
16
|
|
|
@@ -25,8 +26,10 @@ export type AssertToApply = {
|
|
|
25
26
|
runsOnValues: boolean;
|
|
26
27
|
};
|
|
27
28
|
|
|
28
|
-
type
|
|
29
|
-
|
|
29
|
+
type RunAssertionParams = {
|
|
30
|
+
ctx: AssertionContext;
|
|
31
|
+
assert: AssertToApply;
|
|
32
|
+
assertionProperty?: string;
|
|
30
33
|
};
|
|
31
34
|
|
|
32
35
|
const assertionMessageTemplates = {
|
|
@@ -96,35 +99,27 @@ function getAssertionProperties({ subject }: AssertionDefinition): string[] {
|
|
|
96
99
|
function applyAssertions(
|
|
97
100
|
assertionDefinition: AssertionDefinition,
|
|
98
101
|
asserts: AssertToApply[],
|
|
99
|
-
|
|
102
|
+
ctx: AssertionContext
|
|
100
103
|
): AssertResult[] {
|
|
101
104
|
const properties = getAssertionProperties(assertionDefinition);
|
|
102
105
|
const assertResults: Array<AssertResult[]> = [];
|
|
103
106
|
|
|
104
107
|
for (const assert of asserts) {
|
|
105
|
-
const currentLocation = assert.name === 'ref' ? rawLocation : location;
|
|
106
|
-
|
|
107
108
|
if (properties.length) {
|
|
108
109
|
for (const property of properties) {
|
|
109
|
-
// we can have resolvable scalar so need to resolve value here.
|
|
110
|
-
const value = isRef(node[property]) ? resolve(node[property])?.node : node[property];
|
|
111
110
|
assertResults.push(
|
|
112
111
|
runAssertion({
|
|
113
|
-
values: value,
|
|
114
|
-
rawValues: rawNode[property],
|
|
115
112
|
assert,
|
|
116
|
-
|
|
113
|
+
ctx,
|
|
114
|
+
assertionProperty: property,
|
|
117
115
|
})
|
|
118
116
|
);
|
|
119
117
|
}
|
|
120
118
|
} else {
|
|
121
|
-
const value = Array.isArray(node) ? node : Object.keys(node);
|
|
122
119
|
assertResults.push(
|
|
123
120
|
runAssertion({
|
|
124
|
-
values: value,
|
|
125
|
-
rawValues: rawNode,
|
|
126
121
|
assert,
|
|
127
|
-
|
|
122
|
+
ctx,
|
|
128
123
|
})
|
|
129
124
|
);
|
|
130
125
|
}
|
|
@@ -139,7 +134,7 @@ export function buildVisitorObject(
|
|
|
139
134
|
): Oas2Visitor | Oas3Visitor {
|
|
140
135
|
const targetVisitorLocatorPredicates = getPredicatesFromLocators(assertion.subject);
|
|
141
136
|
const targetVisitorSkipFunction = targetVisitorLocatorPredicates.length
|
|
142
|
-
? (
|
|
137
|
+
? (_: any, key: string | number) =>
|
|
143
138
|
!targetVisitorLocatorPredicates.every((predicate) => predicate(key))
|
|
144
139
|
: undefined;
|
|
145
140
|
const targetVisitor: Oas2Visitor | Oas3Visitor = {
|
|
@@ -169,19 +164,9 @@ export function buildVisitorObject(
|
|
|
169
164
|
const locatorPredicates = getPredicatesFromLocators(assertionDefinitionNode.subject);
|
|
170
165
|
const assertsToApply = getAssertsToApply(assertionDefinitionNode);
|
|
171
166
|
|
|
172
|
-
const skipFunction = (
|
|
173
|
-
node: unknown,
|
|
174
|
-
key: string | number,
|
|
175
|
-
{ location, rawLocation, resolve, rawNode }: SkipFunctionContext
|
|
176
|
-
): boolean =>
|
|
167
|
+
const skipFunction = (node: unknown, key: string | number, ctx: SkipFunctionContext): boolean =>
|
|
177
168
|
!locatorPredicates.every((predicate) => predicate(key)) ||
|
|
178
|
-
!!applyAssertions(assertionDefinitionNode, assertsToApply, {
|
|
179
|
-
location,
|
|
180
|
-
node,
|
|
181
|
-
rawLocation,
|
|
182
|
-
rawNode,
|
|
183
|
-
resolve,
|
|
184
|
-
}).length;
|
|
169
|
+
!!applyAssertions(assertionDefinitionNode, assertsToApply, { ...ctx, node }).length;
|
|
185
170
|
|
|
186
171
|
const nodeVisitor = {
|
|
187
172
|
...((locatorPredicates.length || assertsToApply.length) && { skip: skipFunction }),
|
|
@@ -215,7 +200,7 @@ export function buildVisitorObject(
|
|
|
215
200
|
}
|
|
216
201
|
|
|
217
202
|
export function buildSubjectVisitor(assertId: string, assertion: Assertion): VisitFunction<any> {
|
|
218
|
-
return (node: any,
|
|
203
|
+
return (node: any, ctx: UserContext) => {
|
|
219
204
|
const properties = getAssertionProperties(assertion);
|
|
220
205
|
|
|
221
206
|
const defaultMessage = `${colorize.blue(assertId)} failed because the ${colorize.blue(
|
|
@@ -225,10 +210,7 @@ export function buildSubjectVisitor(assertId: string, assertion: Assertion): Vis
|
|
|
225
210
|
}`.replace(/ +/g, ' ');
|
|
226
211
|
|
|
227
212
|
const problems = applyAssertions(assertion, getAssertsToApply(assertion), {
|
|
228
|
-
|
|
229
|
-
rawNode,
|
|
230
|
-
resolve,
|
|
231
|
-
location,
|
|
213
|
+
...ctx,
|
|
232
214
|
node,
|
|
233
215
|
});
|
|
234
216
|
|
|
@@ -236,9 +218,9 @@ export function buildSubjectVisitor(assertId: string, assertion: Assertion): Vis
|
|
|
236
218
|
for (const problemGroup of groupProblemsByPointer(problems)) {
|
|
237
219
|
const message = assertion.message || defaultMessage;
|
|
238
220
|
const problemMessage = getProblemsMessage(problemGroup);
|
|
239
|
-
report({
|
|
221
|
+
ctx.report({
|
|
240
222
|
message: message.replace(assertionMessageTemplates.problems, problemMessage),
|
|
241
|
-
location: getProblemsLocation(problemGroup) || location,
|
|
223
|
+
location: getProblemsLocation(problemGroup) || ctx.location,
|
|
242
224
|
forceSeverity: assertion.severity || 'error',
|
|
243
225
|
suggest: assertion.suggest || [],
|
|
244
226
|
ruleId: assertId,
|
|
@@ -312,15 +294,35 @@ export function isOrdered(value: any[], options: OrderOptions | OrderDirection):
|
|
|
312
294
|
return true;
|
|
313
295
|
}
|
|
314
296
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
297
|
+
export function runAssertion({
|
|
298
|
+
assert,
|
|
299
|
+
ctx,
|
|
300
|
+
assertionProperty,
|
|
301
|
+
}: RunAssertionParams): AssertResult[] {
|
|
302
|
+
const currentLocation = assert.name === 'ref' ? ctx.rawLocation : ctx.location;
|
|
303
|
+
|
|
304
|
+
if (assertionProperty) {
|
|
305
|
+
const values = isRef(ctx.node[assertionProperty])
|
|
306
|
+
? ctx.resolve(ctx.node[assertionProperty])?.node
|
|
307
|
+
: ctx.node[assertionProperty];
|
|
308
|
+
const rawValues = ctx.rawNode[assertionProperty];
|
|
309
|
+
|
|
310
|
+
const location = currentLocation.child(assertionProperty);
|
|
311
|
+
|
|
312
|
+
return asserts[assert.name](values, assert.conditions, {
|
|
313
|
+
...ctx,
|
|
314
|
+
baseLocation: location,
|
|
315
|
+
rawValue: rawValues,
|
|
316
|
+
});
|
|
317
|
+
} else {
|
|
318
|
+
const value = Array.isArray(ctx.node) ? ctx.node : Object.keys(ctx.node);
|
|
321
319
|
|
|
322
|
-
|
|
323
|
-
|
|
320
|
+
return asserts[assert.name](value, assert.conditions, {
|
|
321
|
+
...ctx,
|
|
322
|
+
rawValue: ctx.rawNode,
|
|
323
|
+
baseLocation: currentLocation,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
324
326
|
}
|
|
325
327
|
|
|
326
328
|
export function regexFromString(input: string): RegExp | null {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { UserContext } from '../../walk';
|
|
2
|
+
import { Oas3Schema, Oas3_1Schema } from '../../typings/openapi';
|
|
3
|
+
import { Oas2Schema } from 'core/src/typings/swagger';
|
|
4
|
+
import { Oas3Rule } from 'core/src/visitors';
|
|
5
|
+
|
|
6
|
+
export const RequiredStringPropertyMissingMinLength: Oas3Rule = () => {
|
|
7
|
+
let skipSchemaProperties: boolean;
|
|
8
|
+
let requiredPropertiesSet: Set<string>;
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
Schema: {
|
|
12
|
+
enter(schema: Oas3Schema | Oas3_1Schema | Oas2Schema) {
|
|
13
|
+
if (!schema?.required) {
|
|
14
|
+
skipSchemaProperties = true;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
requiredPropertiesSet = new Set(schema.required);
|
|
18
|
+
skipSchemaProperties = false;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
SchemaProperties: {
|
|
22
|
+
skip() {
|
|
23
|
+
return skipSchemaProperties;
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
Schema: {
|
|
27
|
+
enter(
|
|
28
|
+
schema: Oas3Schema | Oas3_1Schema | Oas2Schema,
|
|
29
|
+
{ key, location, report }: UserContext
|
|
30
|
+
) {
|
|
31
|
+
if (requiredPropertiesSet.has(key as string) && schema.type === 'string') {
|
|
32
|
+
if (!schema?.minLength) {
|
|
33
|
+
report({
|
|
34
|
+
message: 'Property minLength is required.',
|
|
35
|
+
location: location.key(),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
};
|
package/src/rules/oas2/index.ts
CHANGED
|
@@ -39,6 +39,7 @@ import { PathSegmentPlural } from '../common/path-segment-plural';
|
|
|
39
39
|
import { ResponseContainsHeader } from '../common/response-contains-header';
|
|
40
40
|
import { ResponseContainsProperty } from './response-contains-property';
|
|
41
41
|
import { ScalarPropertyMissingExample } from '../common/scalar-property-missing-example';
|
|
42
|
+
import { RequiredStringPropertyMissingMinLength } from '../common/required-string-property-missing-min-length';
|
|
42
43
|
|
|
43
44
|
export const rules = {
|
|
44
45
|
spec: OasSpec as Oas2Rule,
|
|
@@ -82,6 +83,7 @@ export const rules = {
|
|
|
82
83
|
'response-contains-header': ResponseContainsHeader as Oas2Rule,
|
|
83
84
|
'response-contains-property': ResponseContainsProperty as Oas2Rule,
|
|
84
85
|
'scalar-property-missing-example': ScalarPropertyMissingExample,
|
|
86
|
+
'required-string-property-missing-min-length': RequiredStringPropertyMissingMinLength,
|
|
85
87
|
};
|
|
86
88
|
|
|
87
89
|
export const preprocessors = {};
|
package/src/rules/oas3/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ import { ResponseContainsProperty } from './response-contains-property';
|
|
|
49
49
|
import { ScalarPropertyMissingExample } from '../common/scalar-property-missing-example';
|
|
50
50
|
import { SpecComponentsInvalidMapName } from './spec-components-invalid-map-name';
|
|
51
51
|
import { Operation4xxProblemDetailsRfc7807 } from './operation-4xx-problem-details-rfc7807';
|
|
52
|
+
import { RequiredStringPropertyMissingMinLength } from '../common/required-string-property-missing-min-length';
|
|
52
53
|
|
|
53
54
|
export const rules = {
|
|
54
55
|
spec: OasSpec,
|
|
@@ -102,6 +103,7 @@ export const rules = {
|
|
|
102
103
|
'response-contains-property': ResponseContainsProperty,
|
|
103
104
|
'scalar-property-missing-example': ScalarPropertyMissingExample,
|
|
104
105
|
'spec-components-invalid-map-name': SpecComponentsInvalidMapName,
|
|
106
|
+
'required-string-property-missing-min-length': RequiredStringPropertyMissingMinLength,
|
|
105
107
|
} as Oas3RuleSet;
|
|
106
108
|
|
|
107
109
|
export const preprocessors = {};
|