@redocly/openapi-core 1.8.1 → 1.9.0
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/CHANGELOG.md +12 -0
- package/lib/config/all.js +1 -0
- package/lib/config/minimal.js +1 -0
- package/lib/config/recommended-strict.js +1 -0
- package/lib/config/recommended.js +1 -0
- package/lib/ref-utils.js +3 -3
- package/lib/rules/common/no-required-schema-properties-undefined.d.ts +2 -0
- package/lib/rules/common/no-required-schema-properties-undefined.js +37 -0
- package/lib/rules/oas2/index.js +2 -0
- package/lib/rules/oas3/index.js +2 -0
- package/lib/types/redocly-yaml.d.ts +1 -1
- package/lib/types/redocly-yaml.js +1 -0
- package/package.json +1 -1
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +2 -0
- package/src/config/all.ts +1 -0
- package/src/config/minimal.ts +1 -0
- package/src/config/recommended-strict.ts +1 -0
- package/src/config/recommended.ts +1 -0
- package/src/ref-utils.ts +3 -3
- package/src/rules/common/__tests__/no-required-schema-properties-undefined.test.ts +550 -0
- package/src/rules/common/no-required-schema-properties-undefined.ts +53 -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
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import { outdent } from 'outdent';
|
|
2
|
+
import { lintDocument } from '../../../lint';
|
|
3
|
+
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
|
4
|
+
import { BaseResolver } from '../../../resolve';
|
|
5
|
+
|
|
6
|
+
describe('no-required-schema-properties-undefined', () => {
|
|
7
|
+
it('should report if one or more of the required properties are undefined', async () => {
|
|
8
|
+
const document = parseYamlToDocument(
|
|
9
|
+
outdent`
|
|
10
|
+
openapi: 3.0.0
|
|
11
|
+
components:
|
|
12
|
+
schemas:
|
|
13
|
+
Pet:
|
|
14
|
+
type: object
|
|
15
|
+
required:
|
|
16
|
+
- name
|
|
17
|
+
- id
|
|
18
|
+
- test
|
|
19
|
+
properties:
|
|
20
|
+
id:
|
|
21
|
+
type: integer
|
|
22
|
+
format: int64
|
|
23
|
+
name:
|
|
24
|
+
type: string
|
|
25
|
+
example: doggie
|
|
26
|
+
`,
|
|
27
|
+
'foobar.yaml'
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const results = await lintDocument({
|
|
31
|
+
externalRefResolver: new BaseResolver(),
|
|
32
|
+
document,
|
|
33
|
+
config: await makeConfig({ 'no-required-schema-properties-undefined': 'error' }),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
37
|
+
[
|
|
38
|
+
{
|
|
39
|
+
"location": [
|
|
40
|
+
{
|
|
41
|
+
"pointer": "#/components/schemas/Pet/required/2",
|
|
42
|
+
"reportOnKey": false,
|
|
43
|
+
"source": "foobar.yaml",
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
"message": "Required property 'test' is undefined.",
|
|
47
|
+
"ruleId": "no-required-schema-properties-undefined",
|
|
48
|
+
"severity": "error",
|
|
49
|
+
"suggest": [],
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
`);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should report if one or more of the required properties are undefined, including allOf nested schemas', async () => {
|
|
56
|
+
const document = parseYamlToDocument(
|
|
57
|
+
outdent`
|
|
58
|
+
openapi: 3.0.0
|
|
59
|
+
components:
|
|
60
|
+
schemas:
|
|
61
|
+
Pet:
|
|
62
|
+
type: object
|
|
63
|
+
allOf:
|
|
64
|
+
- properties:
|
|
65
|
+
foo:
|
|
66
|
+
type: string
|
|
67
|
+
required:
|
|
68
|
+
- id
|
|
69
|
+
- foo
|
|
70
|
+
- test
|
|
71
|
+
properties:
|
|
72
|
+
id:
|
|
73
|
+
type: integer
|
|
74
|
+
format: int64
|
|
75
|
+
`,
|
|
76
|
+
'foobar.yaml'
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const results = await lintDocument({
|
|
80
|
+
externalRefResolver: new BaseResolver(),
|
|
81
|
+
document,
|
|
82
|
+
config: await makeConfig({ 'no-required-schema-properties-undefined': 'error' }),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
86
|
+
[
|
|
87
|
+
{
|
|
88
|
+
"location": [
|
|
89
|
+
{
|
|
90
|
+
"pointer": "#/components/schemas/Pet/required/2",
|
|
91
|
+
"reportOnKey": false,
|
|
92
|
+
"source": "foobar.yaml",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
"message": "Required property 'test' is undefined.",
|
|
96
|
+
"ruleId": "no-required-schema-properties-undefined",
|
|
97
|
+
"severity": "error",
|
|
98
|
+
"suggest": [],
|
|
99
|
+
},
|
|
100
|
+
]
|
|
101
|
+
`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should report if one or more of the required properties are undefined when used in schema with allOf keyword', async () => {
|
|
105
|
+
const document = parseYamlToDocument(
|
|
106
|
+
outdent`
|
|
107
|
+
openapi: 3.0.0
|
|
108
|
+
components:
|
|
109
|
+
schemas:
|
|
110
|
+
Cat:
|
|
111
|
+
description: A representation of a cat
|
|
112
|
+
allOf:
|
|
113
|
+
- $ref: '#/components/schemas/Pet'
|
|
114
|
+
- type: object
|
|
115
|
+
properties:
|
|
116
|
+
huntingSkill:
|
|
117
|
+
type: string
|
|
118
|
+
required:
|
|
119
|
+
- huntingSkill
|
|
120
|
+
- name
|
|
121
|
+
Pet:
|
|
122
|
+
type: object
|
|
123
|
+
required:
|
|
124
|
+
- photoUrls
|
|
125
|
+
properties:
|
|
126
|
+
name:
|
|
127
|
+
type: string
|
|
128
|
+
photoUrls:
|
|
129
|
+
type: string
|
|
130
|
+
`,
|
|
131
|
+
'foobar.yaml'
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const results = await lintDocument({
|
|
135
|
+
externalRefResolver: new BaseResolver(),
|
|
136
|
+
document,
|
|
137
|
+
config: await makeConfig({ 'no-required-schema-properties-undefined': 'error' }),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
141
|
+
[
|
|
142
|
+
{
|
|
143
|
+
"location": [
|
|
144
|
+
{
|
|
145
|
+
"pointer": "#/components/schemas/Cat/allOf/1/required/1",
|
|
146
|
+
"reportOnKey": false,
|
|
147
|
+
"source": "foobar.yaml",
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
"message": "Required property 'name' is undefined.",
|
|
151
|
+
"ruleId": "no-required-schema-properties-undefined",
|
|
152
|
+
"severity": "error",
|
|
153
|
+
"suggest": [],
|
|
154
|
+
},
|
|
155
|
+
]
|
|
156
|
+
`);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should not report required properties are present after resolving $refs when used in schema with allOf keyword', async () => {
|
|
160
|
+
const document = parseYamlToDocument(
|
|
161
|
+
outdent`
|
|
162
|
+
openapi: 3.0.0
|
|
163
|
+
components:
|
|
164
|
+
schemas:
|
|
165
|
+
Cat:
|
|
166
|
+
description: A representation of a cat
|
|
167
|
+
allOf:
|
|
168
|
+
- $ref: '#/components/schemas/Pet'
|
|
169
|
+
- type: object
|
|
170
|
+
properties:
|
|
171
|
+
huntingSkill:
|
|
172
|
+
type: string
|
|
173
|
+
required:
|
|
174
|
+
- huntingSkill
|
|
175
|
+
required:
|
|
176
|
+
- name
|
|
177
|
+
Pet:
|
|
178
|
+
type: object
|
|
179
|
+
required:
|
|
180
|
+
- photoUrls
|
|
181
|
+
properties:
|
|
182
|
+
name:
|
|
183
|
+
type: string
|
|
184
|
+
photoUrls:
|
|
185
|
+
type: string
|
|
186
|
+
`,
|
|
187
|
+
'foobar.yaml'
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const results = await lintDocument({
|
|
191
|
+
externalRefResolver: new BaseResolver(),
|
|
192
|
+
document,
|
|
193
|
+
config: await makeConfig({ 'no-required-schema-properties-undefined': 'error' }),
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should report with few messages if more than one of the required properties are undefined', async () => {
|
|
200
|
+
const document = parseYamlToDocument(
|
|
201
|
+
outdent`
|
|
202
|
+
openapi: 3.0.0
|
|
203
|
+
components:
|
|
204
|
+
schemas:
|
|
205
|
+
Pet:
|
|
206
|
+
type: object
|
|
207
|
+
required:
|
|
208
|
+
- name
|
|
209
|
+
- id
|
|
210
|
+
- test
|
|
211
|
+
- test2
|
|
212
|
+
properties:
|
|
213
|
+
id:
|
|
214
|
+
type: integer
|
|
215
|
+
format: int64
|
|
216
|
+
name:
|
|
217
|
+
type: string
|
|
218
|
+
example: doggie
|
|
219
|
+
`,
|
|
220
|
+
'foobar.yaml'
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const results = await lintDocument({
|
|
224
|
+
externalRefResolver: new BaseResolver(),
|
|
225
|
+
document,
|
|
226
|
+
config: await makeConfig({ 'no-required-schema-properties-undefined': 'error' }),
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
230
|
+
[
|
|
231
|
+
{
|
|
232
|
+
"location": [
|
|
233
|
+
{
|
|
234
|
+
"pointer": "#/components/schemas/Pet/required/2",
|
|
235
|
+
"reportOnKey": false,
|
|
236
|
+
"source": "foobar.yaml",
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
"message": "Required property 'test' is undefined.",
|
|
240
|
+
"ruleId": "no-required-schema-properties-undefined",
|
|
241
|
+
"severity": "error",
|
|
242
|
+
"suggest": [],
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"location": [
|
|
246
|
+
{
|
|
247
|
+
"pointer": "#/components/schemas/Pet/required/3",
|
|
248
|
+
"reportOnKey": false,
|
|
249
|
+
"source": "foobar.yaml",
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
"message": "Required property 'test2' is undefined.",
|
|
253
|
+
"ruleId": "no-required-schema-properties-undefined",
|
|
254
|
+
"severity": "error",
|
|
255
|
+
"suggest": [],
|
|
256
|
+
},
|
|
257
|
+
]
|
|
258
|
+
`);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should not report if all of the required properties are present', async () => {
|
|
262
|
+
const document = parseYamlToDocument(
|
|
263
|
+
outdent`
|
|
264
|
+
openapi: 3.0.0
|
|
265
|
+
components:
|
|
266
|
+
schemas:
|
|
267
|
+
Pet:
|
|
268
|
+
type: object
|
|
269
|
+
required:
|
|
270
|
+
- name
|
|
271
|
+
- id
|
|
272
|
+
properties:
|
|
273
|
+
id:
|
|
274
|
+
type: integer
|
|
275
|
+
format: int64
|
|
276
|
+
name:
|
|
277
|
+
type: string
|
|
278
|
+
example: doggie
|
|
279
|
+
test:
|
|
280
|
+
type: string
|
|
281
|
+
example: test
|
|
282
|
+
`,
|
|
283
|
+
'foobar.yaml'
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const results = await lintDocument({
|
|
287
|
+
externalRefResolver: new BaseResolver(),
|
|
288
|
+
document,
|
|
289
|
+
config: await makeConfig({ 'no-required-schema-properties-undefined': 'error' }),
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should not cause stack overflow when there are circular references in schemas', async () => {
|
|
296
|
+
const document = parseYamlToDocument(
|
|
297
|
+
outdent`
|
|
298
|
+
openapi: 3.0.0
|
|
299
|
+
components:
|
|
300
|
+
schemas:
|
|
301
|
+
Cat:
|
|
302
|
+
description: A representation of a cat
|
|
303
|
+
allOf:
|
|
304
|
+
- $ref: '#/components/schemas/Pet'
|
|
305
|
+
- type: object
|
|
306
|
+
properties:
|
|
307
|
+
huntingSkill:
|
|
308
|
+
type: string
|
|
309
|
+
required:
|
|
310
|
+
- huntingSkill
|
|
311
|
+
required:
|
|
312
|
+
- name
|
|
313
|
+
Pet:
|
|
314
|
+
description: A schema of pet
|
|
315
|
+
allOf:
|
|
316
|
+
- $ref: '#/components/schemas/Cat'
|
|
317
|
+
- type: object
|
|
318
|
+
required:
|
|
319
|
+
- photoUrls
|
|
320
|
+
properties:
|
|
321
|
+
name:
|
|
322
|
+
type: string
|
|
323
|
+
photoUrls:
|
|
324
|
+
type: string
|
|
325
|
+
`,
|
|
326
|
+
'foobar.yaml'
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const results = await lintDocument({
|
|
330
|
+
externalRefResolver: new BaseResolver(),
|
|
331
|
+
document,
|
|
332
|
+
config: await makeConfig({ 'no-required-schema-properties-undefined': 'error' }),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should report if there is any required schema that is undefined, including checking nested allOf schemas', async () => {
|
|
339
|
+
const document = parseYamlToDocument(
|
|
340
|
+
outdent`
|
|
341
|
+
openapi: 3.0.0
|
|
342
|
+
components:
|
|
343
|
+
schemas:
|
|
344
|
+
Cat:
|
|
345
|
+
description: A representation of a cat
|
|
346
|
+
allOf:
|
|
347
|
+
- $ref: '#/components/schemas/Pet'
|
|
348
|
+
- type: object
|
|
349
|
+
properties:
|
|
350
|
+
huntingSkill:
|
|
351
|
+
type: string
|
|
352
|
+
required:
|
|
353
|
+
- huntingSkill
|
|
354
|
+
required:
|
|
355
|
+
- test
|
|
356
|
+
- id
|
|
357
|
+
Pet:
|
|
358
|
+
description: A schema of pet
|
|
359
|
+
allOf:
|
|
360
|
+
- type: object
|
|
361
|
+
required:
|
|
362
|
+
- photoUrls
|
|
363
|
+
properties:
|
|
364
|
+
photoUrls:
|
|
365
|
+
type: string
|
|
366
|
+
- type: object
|
|
367
|
+
required:
|
|
368
|
+
- name
|
|
369
|
+
properties:
|
|
370
|
+
name:
|
|
371
|
+
type: string
|
|
372
|
+
id:
|
|
373
|
+
type: string
|
|
374
|
+
`,
|
|
375
|
+
'foobar.yaml'
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
const results = await lintDocument({
|
|
379
|
+
externalRefResolver: new BaseResolver(),
|
|
380
|
+
document,
|
|
381
|
+
config: await makeConfig({ 'no-required-schema-properties-undefined': 'error' }),
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
385
|
+
[
|
|
386
|
+
{
|
|
387
|
+
"location": [
|
|
388
|
+
{
|
|
389
|
+
"pointer": "#/components/schemas/Cat/required/0",
|
|
390
|
+
"reportOnKey": false,
|
|
391
|
+
"source": "foobar.yaml",
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
"message": "Required property 'test' is undefined.",
|
|
395
|
+
"ruleId": "no-required-schema-properties-undefined",
|
|
396
|
+
"severity": "error",
|
|
397
|
+
"suggest": [],
|
|
398
|
+
},
|
|
399
|
+
]
|
|
400
|
+
`);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should not report if required property is present in one of the nested reference schemas', async () => {
|
|
404
|
+
const document = parseYamlToDocument(
|
|
405
|
+
outdent`
|
|
406
|
+
openapi: 3.0.0
|
|
407
|
+
components:
|
|
408
|
+
schemas:
|
|
409
|
+
Animal:
|
|
410
|
+
type: object
|
|
411
|
+
properties:
|
|
412
|
+
commonProperty:
|
|
413
|
+
type: string
|
|
414
|
+
Mammal:
|
|
415
|
+
allOf:
|
|
416
|
+
- $ref: '#/components/schemas/Animal'
|
|
417
|
+
- type: object
|
|
418
|
+
properties:
|
|
419
|
+
furColor:
|
|
420
|
+
type: string
|
|
421
|
+
required:
|
|
422
|
+
- furColor
|
|
423
|
+
Carnivore:
|
|
424
|
+
allOf:
|
|
425
|
+
- $ref: '#/components/schemas/Mammal'
|
|
426
|
+
- type: object
|
|
427
|
+
properties:
|
|
428
|
+
diet:
|
|
429
|
+
type: string
|
|
430
|
+
required:
|
|
431
|
+
- diet
|
|
432
|
+
Tiger:
|
|
433
|
+
allOf:
|
|
434
|
+
- $ref: '#/components/schemas/Carnivore'
|
|
435
|
+
- type: object
|
|
436
|
+
properties:
|
|
437
|
+
stripes:
|
|
438
|
+
type: boolean
|
|
439
|
+
required:
|
|
440
|
+
- stripes
|
|
441
|
+
required:
|
|
442
|
+
- commonProperty
|
|
443
|
+
`,
|
|
444
|
+
'foobar.yaml'
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
const results = await lintDocument({
|
|
448
|
+
externalRefResolver: new BaseResolver(),
|
|
449
|
+
document,
|
|
450
|
+
config: await makeConfig({ 'no-required-schema-properties-undefined': 'error' }),
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('should report if one or more of the required properties are undefined when used in schema with anyOf keyword', async () => {
|
|
457
|
+
const document = parseYamlToDocument(
|
|
458
|
+
outdent`
|
|
459
|
+
openapi: 3.0.0
|
|
460
|
+
components:
|
|
461
|
+
schemas:
|
|
462
|
+
Cat:
|
|
463
|
+
description: A representation of a cat
|
|
464
|
+
anyOf:
|
|
465
|
+
- $ref: '#/components/schemas/Pet'
|
|
466
|
+
- type: object
|
|
467
|
+
properties:
|
|
468
|
+
huntingSkill:
|
|
469
|
+
type: string
|
|
470
|
+
required:
|
|
471
|
+
- huntingSkill
|
|
472
|
+
- name
|
|
473
|
+
Pet:
|
|
474
|
+
type: object
|
|
475
|
+
required:
|
|
476
|
+
- photoUrls
|
|
477
|
+
properties:
|
|
478
|
+
name:
|
|
479
|
+
type: string
|
|
480
|
+
photoUrls:
|
|
481
|
+
type: string
|
|
482
|
+
`,
|
|
483
|
+
'foobar.yaml'
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
const results = await lintDocument({
|
|
487
|
+
externalRefResolver: new BaseResolver(),
|
|
488
|
+
document,
|
|
489
|
+
config: await makeConfig({ 'no-required-schema-properties-undefined': 'error' }),
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
493
|
+
[
|
|
494
|
+
{
|
|
495
|
+
"location": [
|
|
496
|
+
{
|
|
497
|
+
"pointer": "#/components/schemas/Cat/anyOf/1/required/1",
|
|
498
|
+
"reportOnKey": false,
|
|
499
|
+
"source": "foobar.yaml",
|
|
500
|
+
},
|
|
501
|
+
],
|
|
502
|
+
"message": "Required property 'name' is undefined.",
|
|
503
|
+
"ruleId": "no-required-schema-properties-undefined",
|
|
504
|
+
"severity": "error",
|
|
505
|
+
"suggest": [],
|
|
506
|
+
},
|
|
507
|
+
]
|
|
508
|
+
`);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('should report if one or more of the required properties are undefined when used in schema with allOf keyword', async () => {
|
|
512
|
+
const document = parseYamlToDocument(
|
|
513
|
+
outdent`
|
|
514
|
+
openapi: 3.0.0
|
|
515
|
+
components:
|
|
516
|
+
schemas:
|
|
517
|
+
Cat:
|
|
518
|
+
description: A representation of a cat
|
|
519
|
+
anyOf:
|
|
520
|
+
- $ref: '#/components/schemas/Pet'
|
|
521
|
+
- type: object
|
|
522
|
+
properties:
|
|
523
|
+
huntingSkill:
|
|
524
|
+
type: string
|
|
525
|
+
required:
|
|
526
|
+
- huntingSkill
|
|
527
|
+
required:
|
|
528
|
+
- name
|
|
529
|
+
Pet:
|
|
530
|
+
type: object
|
|
531
|
+
required:
|
|
532
|
+
- photoUrls
|
|
533
|
+
properties:
|
|
534
|
+
name:
|
|
535
|
+
type: string
|
|
536
|
+
photoUrls:
|
|
537
|
+
type: string
|
|
538
|
+
`,
|
|
539
|
+
'foobar.yaml'
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
const results = await lintDocument({
|
|
543
|
+
externalRefResolver: new BaseResolver(),
|
|
544
|
+
document,
|
|
545
|
+
config: await makeConfig({ 'no-required-schema-properties-undefined': 'error' }),
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
|
549
|
+
});
|
|
550
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Oas2Rule, Oas3Rule } from '../../visitors';
|
|
2
|
+
import { Oas3Schema, Oas3_1Schema } from '../../typings/openapi';
|
|
3
|
+
import { Oas2Schema } from 'core/src/typings/swagger';
|
|
4
|
+
import { UserContext } from 'core/src/walk';
|
|
5
|
+
import { isRef } from '../../ref-utils';
|
|
6
|
+
|
|
7
|
+
export const NoRequiredSchemaPropertiesUndefined: Oas3Rule | Oas2Rule = () => {
|
|
8
|
+
return {
|
|
9
|
+
Schema: {
|
|
10
|
+
enter(
|
|
11
|
+
schema: Oas3Schema | Oas3_1Schema | Oas2Schema,
|
|
12
|
+
{ location, report, resolve }: UserContext
|
|
13
|
+
) {
|
|
14
|
+
if (!schema.required) return;
|
|
15
|
+
const visitedSchemas: Set<Oas3Schema | Oas3_1Schema | Oas2Schema> = new Set();
|
|
16
|
+
|
|
17
|
+
const elevateProperties = (
|
|
18
|
+
schema: Oas3Schema | Oas3_1Schema | Oas2Schema
|
|
19
|
+
): Record<string, Oas3Schema | Oas3_1Schema | Oas2Schema> => {
|
|
20
|
+
// Check if the schema has been visited before processing it
|
|
21
|
+
if (visitedSchemas.has(schema)) {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
visitedSchemas.add(schema);
|
|
25
|
+
|
|
26
|
+
if (isRef(schema)) {
|
|
27
|
+
return elevateProperties(
|
|
28
|
+
resolve(schema).node as Oas3Schema | Oas3_1Schema | Oas2Schema
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Object.assign(
|
|
33
|
+
{},
|
|
34
|
+
schema.properties,
|
|
35
|
+
...(schema.allOf?.map(elevateProperties) ?? []),
|
|
36
|
+
...((schema as Oas3Schema).anyOf?.map(elevateProperties) ?? [])
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const allProperties = elevateProperties(schema);
|
|
41
|
+
|
|
42
|
+
for (const [i, requiredProperty] of schema.required.entries()) {
|
|
43
|
+
if (!allProperties || allProperties[requiredProperty] === undefined) {
|
|
44
|
+
report({
|
|
45
|
+
message: `Required property '${requiredProperty}' is undefined.`,
|
|
46
|
+
location: location.child(['required', i]),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
};
|
package/src/rules/oas2/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ import { ResponseContainsProperty } from './response-contains-property';
|
|
|
41
41
|
import { ScalarPropertyMissingExample } from '../common/scalar-property-missing-example';
|
|
42
42
|
import { RequiredStringPropertyMissingMinLength } from '../common/required-string-property-missing-min-length';
|
|
43
43
|
import { SpecStrictRefs } from '../common/spec-strict-refs';
|
|
44
|
+
import { NoRequiredSchemaPropertiesUndefined } from '../common/no-required-schema-properties-undefined';
|
|
44
45
|
|
|
45
46
|
import type { Oas2RuleSet } from 'core/src/oas-types';
|
|
46
47
|
|
|
@@ -88,6 +89,7 @@ export const rules: Oas2RuleSet<'built-in'> = {
|
|
|
88
89
|
'scalar-property-missing-example': ScalarPropertyMissingExample,
|
|
89
90
|
'required-string-property-missing-min-length': RequiredStringPropertyMissingMinLength,
|
|
90
91
|
'spec-strict-refs': SpecStrictRefs,
|
|
92
|
+
'no-required-schema-properties-undefined': NoRequiredSchemaPropertiesUndefined,
|
|
91
93
|
};
|
|
92
94
|
|
|
93
95
|
export const preprocessors = {};
|
package/src/rules/oas3/index.ts
CHANGED
|
@@ -53,6 +53,7 @@ import { RequiredStringPropertyMissingMinLength } from '../common/required-strin
|
|
|
53
53
|
import { SpecStrictRefs } from '../common/spec-strict-refs';
|
|
54
54
|
import { ComponentNameUnique } from './component-name-unique';
|
|
55
55
|
import { ArrayParameterSerialization } from './array-parameter-serialization';
|
|
56
|
+
import { NoRequiredSchemaPropertiesUndefined } from '../common/no-required-schema-properties-undefined';
|
|
56
57
|
|
|
57
58
|
export const rules: Oas3RuleSet<'built-in'> = {
|
|
58
59
|
spec: Spec,
|
|
@@ -110,6 +111,7 @@ export const rules: Oas3RuleSet<'built-in'> = {
|
|
|
110
111
|
'spec-strict-refs': SpecStrictRefs,
|
|
111
112
|
'component-name-unique': ComponentNameUnique,
|
|
112
113
|
'array-parameter-serialization': ArrayParameterSerialization,
|
|
114
|
+
'no-required-schema-properties-undefined': NoRequiredSchemaPropertiesUndefined,
|
|
113
115
|
};
|
|
114
116
|
|
|
115
117
|
export const preprocessors = {};
|