@redocly/openapi-core 1.0.0-beta.110 → 1.0.0-beta.112
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/all.js +0 -1
- package/lib/config/config-resolvers.js +42 -34
- package/lib/config/load.d.ts +1 -1
- package/lib/config/load.js +5 -5
- package/lib/config/minimal.js +0 -1
- package/lib/config/recommended.js +0 -1
- package/lib/rules/common/assertions/asserts.d.ts +22 -5
- package/lib/rules/common/assertions/asserts.js +25 -0
- package/lib/rules/common/assertions/index.d.ts +27 -2
- package/lib/rules/common/assertions/index.js +6 -29
- package/lib/rules/common/assertions/utils.d.ts +7 -14
- package/lib/rules/common/assertions/utils.js +129 -97
- package/lib/rules/common/no-ambiguous-paths.js +1 -1
- package/lib/rules/common/no-identical-paths.js +4 -4
- package/lib/rules/common/operation-2xx-response.js +2 -2
- package/lib/rules/common/operation-4xx-response.js +2 -2
- package/lib/rules/common/path-not-include-query.js +1 -1
- package/lib/rules/common/path-params-defined.js +7 -2
- package/lib/rules/common/response-contains-header.js +2 -2
- package/lib/rules/common/security-defined.js +10 -5
- package/lib/rules/common/spec.js +14 -12
- package/lib/rules/oas2/index.d.ts +0 -1
- package/lib/rules/oas2/index.js +0 -2
- package/lib/rules/oas3/index.js +0 -2
- package/lib/rules/oas3/request-mime-type.js +1 -1
- package/lib/rules/oas3/response-mime-type.js +1 -1
- package/lib/rules/other/stats.d.ts +1 -1
- package/lib/rules/other/stats.js +1 -1
- package/lib/rules/utils.d.ts +1 -0
- package/lib/rules/utils.js +17 -1
- package/lib/types/oas2.js +6 -6
- package/lib/types/oas3.js +11 -11
- package/lib/types/oas3_1.js +3 -3
- package/lib/types/redocly-yaml.js +58 -31
- package/lib/utils.d.ts +2 -0
- package/lib/utils.js +19 -1
- package/lib/visitors.d.ts +9 -7
- package/lib/visitors.js +12 -3
- package/lib/walk.js +7 -1
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/bundle.test.ts.snap +1 -1
- package/src/__tests__/lint.test.ts +24 -5
- package/src/__tests__/utils.test.ts +11 -0
- package/src/__tests__/walk.test.ts +2 -2
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +1 -3
- package/src/config/__tests__/config-resolvers.test.ts +30 -5
- package/src/config/__tests__/fixtures/load-redocly.yaml +4 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +6 -4
- package/src/config/__tests__/load.test.ts +4 -1
- package/src/config/all.ts +0 -1
- package/src/config/config-resolvers.ts +44 -20
- package/src/config/load.ts +8 -5
- package/src/config/minimal.ts +0 -1
- package/src/config/recommended.ts +0 -1
- package/src/rules/common/__tests__/operation-2xx-response.test.ts +37 -0
- package/src/rules/common/__tests__/operation-4xx-response.test.ts +37 -0
- package/src/rules/common/__tests__/path-params-defined.test.ts +69 -0
- package/src/rules/common/__tests__/security-defined.test.ts +6 -6
- package/src/rules/common/__tests__/spec.test.ts +125 -0
- package/src/rules/common/assertions/__tests__/asserts.test.ts +7 -3
- package/src/rules/common/assertions/__tests__/index.test.ts +41 -20
- package/src/rules/common/assertions/__tests__/utils.test.ts +44 -18
- package/src/rules/common/assertions/asserts.ts +60 -8
- package/src/rules/common/assertions/index.ts +36 -46
- package/src/rules/common/assertions/utils.ts +204 -127
- package/src/rules/common/no-ambiguous-paths.ts +1 -1
- package/src/rules/common/no-identical-paths.ts +4 -4
- package/src/rules/common/operation-2xx-response.ts +2 -2
- package/src/rules/common/operation-4xx-response.ts +2 -2
- package/src/rules/common/path-not-include-query.ts +1 -1
- package/src/rules/common/path-params-defined.ts +9 -2
- package/src/rules/common/response-contains-header.ts +6 -1
- package/src/rules/common/security-defined.ts +10 -5
- package/src/rules/common/spec.ts +15 -11
- package/src/rules/oas2/index.ts +0 -2
- package/src/rules/oas3/__tests__/response-contains-header.test.ts +116 -0
- package/src/rules/oas3/index.ts +0 -2
- package/src/rules/oas3/request-mime-type.ts +1 -1
- package/src/rules/oas3/response-mime-type.ts +1 -1
- package/src/rules/other/stats.ts +1 -1
- package/src/rules/utils.ts +22 -0
- package/src/types/oas2.ts +6 -6
- package/src/types/oas3.ts +11 -11
- package/src/types/oas3_1.ts +3 -3
- package/src/types/redocly-yaml.ts +58 -33
- package/src/utils.ts +18 -0
- package/src/visitors.ts +32 -11
- package/src/walk.ts +8 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/rules/common/info-description.d.ts +0 -2
- package/lib/rules/common/info-description.js +0 -12
- package/src/rules/common/__tests__/info-description.test.ts +0 -102
- package/src/rules/common/info-description.ts +0 -10
|
@@ -270,4 +270,120 @@ describe('Oas3 response-contains-header', () => {
|
|
|
270
270
|
});
|
|
271
271
|
expect(results).toMatchInlineSnapshot(`Array []`);
|
|
272
272
|
});
|
|
273
|
+
|
|
274
|
+
it('should not report response object containing header name upper cased', async () => {
|
|
275
|
+
const document = parseYamlToDocument(outdent`
|
|
276
|
+
openapi: 3.0.3
|
|
277
|
+
info:
|
|
278
|
+
version: 3.0.0
|
|
279
|
+
paths:
|
|
280
|
+
/store/subscribe:
|
|
281
|
+
post:
|
|
282
|
+
responses:
|
|
283
|
+
'200':
|
|
284
|
+
description: successful operation
|
|
285
|
+
headers:
|
|
286
|
+
X-Test-Header:
|
|
287
|
+
description: calls per hour allowed by the user
|
|
288
|
+
schema:
|
|
289
|
+
type: integer
|
|
290
|
+
format: int32
|
|
291
|
+
`);
|
|
292
|
+
|
|
293
|
+
const results = await lintDocument({
|
|
294
|
+
externalRefResolver: new BaseResolver(),
|
|
295
|
+
document,
|
|
296
|
+
config: await makeConfig({
|
|
297
|
+
'response-contains-header': {
|
|
298
|
+
severity: 'error',
|
|
299
|
+
names: { '2XX': ['x-test-header'] },
|
|
300
|
+
},
|
|
301
|
+
}),
|
|
302
|
+
});
|
|
303
|
+
expect(results).toMatchInlineSnapshot(`Array []`);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should not report response object containing header name in the rule upper cased', async () => {
|
|
307
|
+
const document = parseYamlToDocument(outdent`
|
|
308
|
+
openapi: 3.0.3
|
|
309
|
+
info:
|
|
310
|
+
version: 3.0.0
|
|
311
|
+
paths:
|
|
312
|
+
/store/subscribe:
|
|
313
|
+
post:
|
|
314
|
+
responses:
|
|
315
|
+
'200':
|
|
316
|
+
description: successful operation
|
|
317
|
+
headers:
|
|
318
|
+
x-test-header:
|
|
319
|
+
description: calls per hour allowed by the user
|
|
320
|
+
schema:
|
|
321
|
+
type: integer
|
|
322
|
+
format: int32
|
|
323
|
+
`);
|
|
324
|
+
|
|
325
|
+
const results = await lintDocument({
|
|
326
|
+
externalRefResolver: new BaseResolver(),
|
|
327
|
+
document,
|
|
328
|
+
config: await makeConfig({
|
|
329
|
+
'response-contains-header': {
|
|
330
|
+
severity: 'error',
|
|
331
|
+
names: { '2XX': ['X-Test-Header'] },
|
|
332
|
+
},
|
|
333
|
+
}),
|
|
334
|
+
});
|
|
335
|
+
expect(results).toMatchInlineSnapshot(`Array []`);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should report even if the response is null', async () => {
|
|
339
|
+
const document = parseYamlToDocument(
|
|
340
|
+
outdent`
|
|
341
|
+
openapi: 3.0.0
|
|
342
|
+
paths:
|
|
343
|
+
'/test/':
|
|
344
|
+
put:
|
|
345
|
+
responses:
|
|
346
|
+
'200': null
|
|
347
|
+
`,
|
|
348
|
+
'foobar.yaml'
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const results = await lintDocument({
|
|
352
|
+
externalRefResolver: new BaseResolver(),
|
|
353
|
+
document,
|
|
354
|
+
config: await makeConfig({
|
|
355
|
+
'response-contains-header': {
|
|
356
|
+
severity: 'error',
|
|
357
|
+
names: { '2XX': ['X-Test-Header'] },
|
|
358
|
+
},
|
|
359
|
+
}),
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
expect(results).toMatchInlineSnapshot(`
|
|
363
|
+
Array [
|
|
364
|
+
Object {
|
|
365
|
+
"location": Array [
|
|
366
|
+
Object {
|
|
367
|
+
"pointer": "#/paths/~1test~1/put/responses/200/headers",
|
|
368
|
+
"reportOnKey": true,
|
|
369
|
+
"source": Source {
|
|
370
|
+
"absoluteRef": "foobar.yaml",
|
|
371
|
+
"body": "openapi: 3.0.0
|
|
372
|
+
paths:
|
|
373
|
+
'/test/':
|
|
374
|
+
put:
|
|
375
|
+
responses:
|
|
376
|
+
'200': null",
|
|
377
|
+
"mimeType": undefined,
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
"message": "Response object must contain a \\"X-Test-Header\\" header.",
|
|
382
|
+
"ruleId": "response-contains-header",
|
|
383
|
+
"severity": "error",
|
|
384
|
+
"suggest": Array [],
|
|
385
|
+
},
|
|
386
|
+
]
|
|
387
|
+
`);
|
|
388
|
+
});
|
|
273
389
|
});
|
package/src/rules/oas3/index.ts
CHANGED
|
@@ -15,7 +15,6 @@ import { OperationIdUrlSafe } from '../common/operation-operationId-url-safe';
|
|
|
15
15
|
import { TagsAlphabetical } from '../common/tags-alphabetical';
|
|
16
16
|
import { NoServerExample } from './no-server-example.com';
|
|
17
17
|
import { NoServerTrailingSlash } from './no-server-trailing-slash';
|
|
18
|
-
import { InfoDescription } from '../common/info-description';
|
|
19
18
|
import { TagDescription } from '../common/tag-description';
|
|
20
19
|
import { InfoContact } from '../common/info-contact';
|
|
21
20
|
import { InfoLicense } from '../common/info-license';
|
|
@@ -53,7 +52,6 @@ import { Operation4xxProblemDetailsRfc7807 } from './operation-4xx-problem-detai
|
|
|
53
52
|
|
|
54
53
|
export const rules = {
|
|
55
54
|
spec: OasSpec,
|
|
56
|
-
'info-description': InfoDescription,
|
|
57
55
|
'info-contact': InfoContact,
|
|
58
56
|
'info-license': InfoLicense,
|
|
59
57
|
'info-license-url': InfoLicenseUrl,
|
|
@@ -5,7 +5,7 @@ import { validateMimeTypeOAS3 } from '../../utils';
|
|
|
5
5
|
|
|
6
6
|
export const RequestMimeType: Oas3Rule = ({ allowedValues }) => {
|
|
7
7
|
return {
|
|
8
|
-
|
|
8
|
+
Paths: {
|
|
9
9
|
RequestBody: {
|
|
10
10
|
leave(requestBody: Oas3RequestBody, ctx: UserContext) {
|
|
11
11
|
validateMimeTypeOAS3({ type: 'consumes', value: requestBody }, ctx, allowedValues);
|
|
@@ -5,7 +5,7 @@ import { validateMimeTypeOAS3 } from '../../utils';
|
|
|
5
5
|
|
|
6
6
|
export const ResponseMimeType: Oas3Rule = ({ allowedValues }) => {
|
|
7
7
|
return {
|
|
8
|
-
|
|
8
|
+
Paths: {
|
|
9
9
|
Response: {
|
|
10
10
|
leave(response: Oas3Response, ctx: UserContext) {
|
|
11
11
|
validateMimeTypeOAS3({ type: 'produces', value: response }, ctx, allowedValues);
|
package/src/rules/other/stats.ts
CHANGED
package/src/rules/utils.ts
CHANGED
|
@@ -138,3 +138,25 @@ export function getAdditionalPropertiesOption(opts: Record<string, any>): boolea
|
|
|
138
138
|
showWarningForDeprecatedField('disallowAdditionalProperties', 'allowAdditionalProperties');
|
|
139
139
|
return !opts.disallowAdditionalProperties;
|
|
140
140
|
}
|
|
141
|
+
|
|
142
|
+
export function validateSchemaEnumType(
|
|
143
|
+
schemaEnum: string[],
|
|
144
|
+
propertyValue: string,
|
|
145
|
+
propName: string,
|
|
146
|
+
refLocation: Location | undefined,
|
|
147
|
+
{ report, location }: UserContext
|
|
148
|
+
) {
|
|
149
|
+
if (!schemaEnum) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!schemaEnum.includes(propertyValue === null ? 'null' : propertyValue)) {
|
|
153
|
+
report({
|
|
154
|
+
location,
|
|
155
|
+
message: `\`${propName}\` can be one of the following only: ${schemaEnum
|
|
156
|
+
.map((type) => `"${type}"`)
|
|
157
|
+
.join(', ')}.`,
|
|
158
|
+
from: refLocation,
|
|
159
|
+
suggest: getSuggest(propertyValue, schemaEnum),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
package/src/types/oas2.ts
CHANGED
|
@@ -11,7 +11,7 @@ const Root: NodeType = {
|
|
|
11
11
|
schemes: { type: 'array', items: { type: 'string' } },
|
|
12
12
|
consumes: { type: 'array', items: { type: 'string' } },
|
|
13
13
|
produces: { type: 'array', items: { type: 'string' } },
|
|
14
|
-
paths: '
|
|
14
|
+
paths: 'Paths',
|
|
15
15
|
definitions: 'NamedSchemas',
|
|
16
16
|
parameters: 'NamedParameters',
|
|
17
17
|
responses: 'NamedResponses',
|
|
@@ -51,7 +51,7 @@ const License: NodeType = {
|
|
|
51
51
|
required: ['name'],
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
const
|
|
54
|
+
const Paths: NodeType = {
|
|
55
55
|
properties: {},
|
|
56
56
|
additionalProperties: (_value: any, key: string) =>
|
|
57
57
|
key.startsWith('/') ? 'PathItem' : undefined,
|
|
@@ -82,7 +82,7 @@ const Operation: NodeType = {
|
|
|
82
82
|
consumes: { type: 'array', items: { type: 'string' } },
|
|
83
83
|
produces: { type: 'array', items: { type: 'string' } },
|
|
84
84
|
parameters: listOf('Parameter'),
|
|
85
|
-
responses: '
|
|
85
|
+
responses: 'Responses',
|
|
86
86
|
schemes: { type: 'array', items: { type: 'string' } },
|
|
87
87
|
deprecated: { type: 'boolean' },
|
|
88
88
|
security: listOf('SecurityRequirement'),
|
|
@@ -180,7 +180,7 @@ const ParameterItems: NodeType = {
|
|
|
180
180
|
},
|
|
181
181
|
};
|
|
182
182
|
|
|
183
|
-
const
|
|
183
|
+
const Responses: NodeType = {
|
|
184
184
|
properties: {
|
|
185
185
|
default: 'Response',
|
|
186
186
|
},
|
|
@@ -376,14 +376,14 @@ export const Oas2Types: Record<string, NodeType> = {
|
|
|
376
376
|
Info,
|
|
377
377
|
Contact,
|
|
378
378
|
License,
|
|
379
|
-
|
|
379
|
+
Paths,
|
|
380
380
|
PathItem,
|
|
381
381
|
Parameter,
|
|
382
382
|
ParameterItems,
|
|
383
383
|
Operation,
|
|
384
384
|
Examples,
|
|
385
385
|
Header,
|
|
386
|
-
|
|
386
|
+
Responses,
|
|
387
387
|
Response,
|
|
388
388
|
Schema,
|
|
389
389
|
Xml,
|
package/src/types/oas3.ts
CHANGED
|
@@ -10,7 +10,7 @@ const Root: NodeType = {
|
|
|
10
10
|
security: listOf('SecurityRequirement'),
|
|
11
11
|
tags: listOf('Tag'),
|
|
12
12
|
externalDocs: 'ExternalDocs',
|
|
13
|
-
paths: '
|
|
13
|
+
paths: 'Paths',
|
|
14
14
|
components: 'Components',
|
|
15
15
|
'x-webhooks': 'WebhooksMap',
|
|
16
16
|
},
|
|
@@ -88,7 +88,7 @@ const License: NodeType = {
|
|
|
88
88
|
required: ['name'],
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
-
const
|
|
91
|
+
const Paths: NodeType = {
|
|
92
92
|
properties: {},
|
|
93
93
|
additionalProperties: (_value: any, key: string) =>
|
|
94
94
|
key.startsWith('/') ? 'PathItem' : undefined,
|
|
@@ -153,7 +153,7 @@ const Operation: NodeType = {
|
|
|
153
153
|
security: listOf('SecurityRequirement'),
|
|
154
154
|
servers: listOf('Server'),
|
|
155
155
|
requestBody: 'RequestBody',
|
|
156
|
-
responses: '
|
|
156
|
+
responses: 'Responses',
|
|
157
157
|
deprecated: { type: 'boolean' },
|
|
158
158
|
callbacks: 'CallbacksMap',
|
|
159
159
|
'x-codeSamples': listOf('XCodeSample'),
|
|
@@ -190,7 +190,7 @@ const MediaType: NodeType = {
|
|
|
190
190
|
schema: 'Schema',
|
|
191
191
|
example: { isExample: true },
|
|
192
192
|
examples: 'ExamplesMap',
|
|
193
|
-
encoding: '
|
|
193
|
+
encoding: 'EncodingMap',
|
|
194
194
|
},
|
|
195
195
|
};
|
|
196
196
|
|
|
@@ -234,7 +234,7 @@ const Header: NodeType = {
|
|
|
234
234
|
requiredOneOf: ['schema', 'content'],
|
|
235
235
|
};
|
|
236
236
|
|
|
237
|
-
const
|
|
237
|
+
const Responses: NodeType = {
|
|
238
238
|
properties: { default: 'Response' },
|
|
239
239
|
additionalProperties: (_v: any, key: string) =>
|
|
240
240
|
responseCodeRegexp.test(key) ? 'Response' : undefined,
|
|
@@ -408,7 +408,7 @@ const AuthorizationCode: NodeType = {
|
|
|
408
408
|
required: ['authorizationUrl', 'tokenUrl', 'scopes'],
|
|
409
409
|
};
|
|
410
410
|
|
|
411
|
-
const
|
|
411
|
+
const OAuth2Flows: NodeType = {
|
|
412
412
|
properties: {
|
|
413
413
|
implicit: 'ImplicitFlow',
|
|
414
414
|
password: 'PasswordFlow',
|
|
@@ -425,7 +425,7 @@ const SecurityScheme: NodeType = {
|
|
|
425
425
|
in: { type: 'string', enum: ['query', 'header', 'cookie'] },
|
|
426
426
|
scheme: { type: 'string' },
|
|
427
427
|
bearerFormat: { type: 'string' },
|
|
428
|
-
flows: '
|
|
428
|
+
flows: 'OAuth2Flows',
|
|
429
429
|
openIdConnectUrl: { type: 'string' },
|
|
430
430
|
},
|
|
431
431
|
required(value) {
|
|
@@ -470,7 +470,7 @@ export const Oas3Types: Record<string, NodeType> = {
|
|
|
470
470
|
Info,
|
|
471
471
|
Contact,
|
|
472
472
|
License,
|
|
473
|
-
|
|
473
|
+
Paths,
|
|
474
474
|
PathItem,
|
|
475
475
|
Parameter,
|
|
476
476
|
Operation,
|
|
@@ -482,10 +482,10 @@ export const Oas3Types: Record<string, NodeType> = {
|
|
|
482
482
|
Example,
|
|
483
483
|
ExamplesMap: mapOf('Example'),
|
|
484
484
|
Encoding,
|
|
485
|
-
|
|
485
|
+
EncodingMap: mapOf('Encoding'),
|
|
486
486
|
Header,
|
|
487
487
|
HeadersMap: mapOf('Header'),
|
|
488
|
-
|
|
488
|
+
Responses,
|
|
489
489
|
Response,
|
|
490
490
|
Link,
|
|
491
491
|
Schema,
|
|
@@ -508,7 +508,7 @@ export const Oas3Types: Record<string, NodeType> = {
|
|
|
508
508
|
PasswordFlow,
|
|
509
509
|
ClientCredentials,
|
|
510
510
|
AuthorizationCode,
|
|
511
|
-
|
|
511
|
+
OAuth2Flows,
|
|
512
512
|
SecurityScheme,
|
|
513
513
|
XCodeSample,
|
|
514
514
|
WebhooksMap,
|
package/src/types/oas3_1.ts
CHANGED
|
@@ -9,7 +9,7 @@ const Root: NodeType = {
|
|
|
9
9
|
security: listOf('SecurityRequirement'),
|
|
10
10
|
tags: listOf('Tag'),
|
|
11
11
|
externalDocs: 'ExternalDocs',
|
|
12
|
-
paths: '
|
|
12
|
+
paths: 'Paths',
|
|
13
13
|
webhooks: 'WebhooksMap',
|
|
14
14
|
components: 'Components',
|
|
15
15
|
jsonSchemaDialect: { type: 'string' },
|
|
@@ -69,7 +69,7 @@ const Operation: NodeType = {
|
|
|
69
69
|
security: listOf('SecurityRequirement'),
|
|
70
70
|
servers: listOf('Server'),
|
|
71
71
|
requestBody: 'RequestBody',
|
|
72
|
-
responses: '
|
|
72
|
+
responses: 'Responses',
|
|
73
73
|
deprecated: { type: 'boolean' },
|
|
74
74
|
callbacks: mapOf('Callback'),
|
|
75
75
|
'x-codeSamples': listOf('XCodeSample'),
|
|
@@ -176,7 +176,7 @@ const SecurityScheme: NodeType = {
|
|
|
176
176
|
in: { type: 'string', enum: ['query', 'header', 'cookie'] },
|
|
177
177
|
scheme: { type: 'string' },
|
|
178
178
|
bearerFormat: { type: 'string' },
|
|
179
|
-
flows: '
|
|
179
|
+
flows: 'OAuth2Flows',
|
|
180
180
|
openIdConnectUrl: { type: 'string' },
|
|
181
181
|
},
|
|
182
182
|
required(value) {
|
|
@@ -3,7 +3,6 @@ import { omitObjectProps, pickObjectProps, isCustomRuleId } from '../utils';
|
|
|
3
3
|
|
|
4
4
|
const builtInRulesList = [
|
|
5
5
|
'spec',
|
|
6
|
-
'info-description',
|
|
7
6
|
'info-contact',
|
|
8
7
|
'info-license',
|
|
9
8
|
'info-license-url',
|
|
@@ -66,7 +65,7 @@ const nodeTypesList = [
|
|
|
66
65
|
'Info',
|
|
67
66
|
'Contact',
|
|
68
67
|
'License',
|
|
69
|
-
'
|
|
68
|
+
'Paths',
|
|
70
69
|
'PathItem',
|
|
71
70
|
'Parameter',
|
|
72
71
|
'Operation',
|
|
@@ -78,10 +77,10 @@ const nodeTypesList = [
|
|
|
78
77
|
'Example',
|
|
79
78
|
'ExamplesMap',
|
|
80
79
|
'Encoding',
|
|
81
|
-
'
|
|
80
|
+
'EncodingMap',
|
|
82
81
|
'Header',
|
|
83
82
|
'HeadersMap',
|
|
84
|
-
'
|
|
83
|
+
'Responses',
|
|
85
84
|
'Response',
|
|
86
85
|
'Link',
|
|
87
86
|
'LinksMap',
|
|
@@ -104,7 +103,7 @@ const nodeTypesList = [
|
|
|
104
103
|
'PasswordFlow',
|
|
105
104
|
'ClientCredentials',
|
|
106
105
|
'AuthorizationCode',
|
|
107
|
-
'
|
|
106
|
+
'OAuth2Flows',
|
|
108
107
|
'SecurityScheme',
|
|
109
108
|
'XCodeSample',
|
|
110
109
|
'WebhooksMap',
|
|
@@ -147,16 +146,8 @@ const ConfigRoot: NodeType = {
|
|
|
147
146
|
properties: {
|
|
148
147
|
organization: { type: 'string' },
|
|
149
148
|
apis: 'ConfigApis',
|
|
150
|
-
apiDefinitions: {
|
|
151
|
-
type: 'object',
|
|
152
|
-
properties: {},
|
|
153
|
-
additionalProperties: { properties: { type: 'string' } },
|
|
154
|
-
}, // deprecated
|
|
155
149
|
...RootConfigStyleguide.properties,
|
|
156
|
-
styleguide: 'RootConfigStyleguide', // deprecated
|
|
157
|
-
lint: 'RootConfigStyleguide', // deprecated
|
|
158
150
|
'features.openapi': 'ConfigReferenceDocs',
|
|
159
|
-
referenceDocs: 'ConfigReferenceDocs', // deprecated
|
|
160
151
|
'features.mockServer': 'ConfigMockServer',
|
|
161
152
|
region: { enum: ['us', 'eu'] },
|
|
162
153
|
resolve: {
|
|
@@ -239,15 +230,9 @@ const ObjectRule: NodeType = {
|
|
|
239
230
|
required: ['severity'],
|
|
240
231
|
};
|
|
241
232
|
|
|
242
|
-
const
|
|
233
|
+
const AssertionDefinitionSubject: NodeType = {
|
|
243
234
|
properties: {
|
|
244
|
-
|
|
245
|
-
if (Array.isArray(value)) {
|
|
246
|
-
return { type: 'array', items: { enum: nodeTypesList } };
|
|
247
|
-
} else {
|
|
248
|
-
return { enum: nodeTypesList };
|
|
249
|
-
}
|
|
250
|
-
},
|
|
235
|
+
type: { enum: nodeTypesList },
|
|
251
236
|
property: (value: unknown) => {
|
|
252
237
|
if (Array.isArray(value)) {
|
|
253
238
|
return { type: 'array', items: { type: 'string' } };
|
|
@@ -257,10 +242,15 @@ const Assert: NodeType = {
|
|
|
257
242
|
return { type: 'string' };
|
|
258
243
|
}
|
|
259
244
|
},
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
245
|
+
filterInParentKeys: { type: 'array', items: { type: 'string' } },
|
|
246
|
+
filterOutParentKeys: { type: 'array', items: { type: 'string' } },
|
|
247
|
+
matchParentKeys: { type: 'string' },
|
|
248
|
+
},
|
|
249
|
+
required: ['type'],
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const AssertionDefinitionAssertions: NodeType = {
|
|
253
|
+
properties: {
|
|
264
254
|
enum: { type: 'array', items: { type: 'string' } },
|
|
265
255
|
pattern: { type: 'string' },
|
|
266
256
|
casing: {
|
|
@@ -280,27 +270,50 @@ const Assert: NodeType = {
|
|
|
280
270
|
requireAny: { type: 'array', items: { type: 'string' } },
|
|
281
271
|
disallowed: { type: 'array', items: { type: 'string' } },
|
|
282
272
|
defined: { type: 'boolean' },
|
|
283
|
-
undefined: { type: 'boolean' },
|
|
273
|
+
// undefined: { type: 'boolean' }, // TODO: Remove `undefined` assertion from codebase overall
|
|
284
274
|
nonEmpty: { type: 'boolean' },
|
|
285
275
|
minLength: { type: 'integer' },
|
|
286
276
|
maxLength: { type: 'integer' },
|
|
287
277
|
ref: (value: string | boolean) =>
|
|
288
278
|
typeof value === 'string' ? { type: 'string' } : { type: 'boolean' },
|
|
279
|
+
const: (value: string | boolean | number) => {
|
|
280
|
+
if (typeof value === 'string') {
|
|
281
|
+
return { type: 'string' };
|
|
282
|
+
}
|
|
283
|
+
if (typeof value === 'number') {
|
|
284
|
+
return { type: 'number' };
|
|
285
|
+
}
|
|
286
|
+
if (typeof value === 'boolean') {
|
|
287
|
+
return { type: 'boolean' };
|
|
288
|
+
} else {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
},
|
|
289
292
|
},
|
|
290
293
|
additionalProperties: (_value: unknown, key: string) => {
|
|
291
294
|
if (/^\w+\/\w+$/.test(key)) return { type: 'object' };
|
|
292
295
|
return;
|
|
293
296
|
},
|
|
294
|
-
required: ['subject'],
|
|
295
297
|
};
|
|
296
298
|
|
|
297
|
-
const
|
|
299
|
+
const AssertDefinition: NodeType = {
|
|
298
300
|
properties: {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
excludeParentKeys: { type: 'array', items: { type: 'string' } },
|
|
301
|
+
subject: 'AssertionDefinitionSubject',
|
|
302
|
+
assertions: 'AssertionDefinitionAssertions',
|
|
302
303
|
},
|
|
303
|
-
required: ['
|
|
304
|
+
required: ['subject', 'assertions'],
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const Assert: NodeType = {
|
|
308
|
+
properties: {
|
|
309
|
+
subject: 'AssertionDefinitionSubject',
|
|
310
|
+
assertions: 'AssertionDefinitionAssertions',
|
|
311
|
+
where: listOf('AssertDefinition'),
|
|
312
|
+
message: { type: 'string' },
|
|
313
|
+
suggest: { type: 'array', items: { type: 'string' } },
|
|
314
|
+
severity: { enum: ['error', 'warn', 'off'] },
|
|
315
|
+
},
|
|
316
|
+
required: ['subject', 'assertions'],
|
|
304
317
|
};
|
|
305
318
|
|
|
306
319
|
const ConfigLanguage: NodeType = {
|
|
@@ -890,6 +903,16 @@ const ConfigReferenceDocs: NodeType = {
|
|
|
890
903
|
unstable_externalDescription: { type: 'boolean' }, // deprecated
|
|
891
904
|
unstable_ignoreMimeParameters: { type: 'boolean' },
|
|
892
905
|
untrustedDefinition: { type: 'boolean' },
|
|
906
|
+
mockServer: {
|
|
907
|
+
properties: {
|
|
908
|
+
url: { type: 'string' },
|
|
909
|
+
position: { enum: ['first', 'last', 'replace', 'off'] },
|
|
910
|
+
description: { type: 'string' },
|
|
911
|
+
},
|
|
912
|
+
},
|
|
913
|
+
showAccessMode: { type: 'boolean' },
|
|
914
|
+
preserveOriginalExtensionsName: { type: 'boolean' },
|
|
915
|
+
markdownHeadingsAnchorLevel: { type: 'number' },
|
|
893
916
|
},
|
|
894
917
|
additionalProperties: { type: 'string' },
|
|
895
918
|
};
|
|
@@ -916,7 +939,7 @@ export const ConfigTypes: Record<string, NodeType> = {
|
|
|
916
939
|
ConfigSidebarLinks,
|
|
917
940
|
CommonConfigSidebarLinks,
|
|
918
941
|
ConfigTheme,
|
|
919
|
-
|
|
942
|
+
AssertDefinition,
|
|
920
943
|
ThemeColors,
|
|
921
944
|
CommonThemeColors,
|
|
922
945
|
BorderThemeColors,
|
|
@@ -963,4 +986,6 @@ export const ConfigTypes: Record<string, NodeType> = {
|
|
|
963
986
|
Sidebar,
|
|
964
987
|
Heading,
|
|
965
988
|
Typography,
|
|
989
|
+
AssertionDefinitionAssertions,
|
|
990
|
+
AssertionDefinitionSubject,
|
|
966
991
|
};
|
package/src/utils.ts
CHANGED
|
@@ -231,3 +231,21 @@ export function isTruthy<Truthy>(value: Truthy | Falsy): value is Truthy {
|
|
|
231
231
|
export function identity<T>(value: T): T {
|
|
232
232
|
return value;
|
|
233
233
|
}
|
|
234
|
+
|
|
235
|
+
export function keysOf<T>(obj: T) {
|
|
236
|
+
if (!obj) return [];
|
|
237
|
+
return Object.keys(obj) as (keyof T)[];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function pickDefined<T extends Record<string, unknown>>(
|
|
241
|
+
obj?: T
|
|
242
|
+
): Record<string, unknown> | undefined {
|
|
243
|
+
if (!obj) return undefined;
|
|
244
|
+
const res: Record<string, unknown> = {};
|
|
245
|
+
for (const key in obj) {
|
|
246
|
+
if (obj[key] !== undefined) {
|
|
247
|
+
res[key] = obj[key];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return res;
|
|
251
|
+
}
|
package/src/visitors.ts
CHANGED
|
@@ -50,6 +50,11 @@ import type { Stack } from './utils';
|
|
|
50
50
|
import type { UserContext, ResolveResult, ProblemSeverity } from './walk';
|
|
51
51
|
import type { Location } from './ref-utils';
|
|
52
52
|
|
|
53
|
+
export type SkipFunctionContext = Pick<
|
|
54
|
+
UserContext,
|
|
55
|
+
'location' | 'rawNode' | 'resolve' | 'rawLocation'
|
|
56
|
+
>;
|
|
57
|
+
|
|
53
58
|
export type VisitFunction<T> = (
|
|
54
59
|
node: T,
|
|
55
60
|
ctx: UserContext & { ignoreNextVisitorsOnNode: () => void },
|
|
@@ -59,7 +64,7 @@ export type VisitFunction<T> = (
|
|
|
59
64
|
|
|
60
65
|
type VisitRefFunction = (node: OasRef, ctx: UserContext, resolved: ResolveResult<any>) => void;
|
|
61
66
|
|
|
62
|
-
type SkipFunction<T> = (node: T, key: string | number) => boolean;
|
|
67
|
+
type SkipFunction<T> = (node: T, key: string | number, ctx: SkipFunctionContext) => boolean;
|
|
63
68
|
|
|
64
69
|
type VisitObject<T> = {
|
|
65
70
|
enter?: VisitFunction<T>;
|
|
@@ -136,9 +141,10 @@ type Oas3FlatVisitor = {
|
|
|
136
141
|
Info?: VisitFunctionOrObject<Oas3Info>;
|
|
137
142
|
Contact?: VisitFunctionOrObject<Oas3Contact>;
|
|
138
143
|
License?: VisitFunctionOrObject<Oas3License>;
|
|
139
|
-
|
|
144
|
+
Paths?: VisitFunctionOrObject<Record<string, Oas3PathItem>>;
|
|
140
145
|
PathItem?: VisitFunctionOrObject<Oas3PathItem>;
|
|
141
|
-
Callback?: VisitFunctionOrObject<
|
|
146
|
+
Callback?: VisitFunctionOrObject<Oas3Callback>;
|
|
147
|
+
CallbacksMap?: VisitFunctionOrObject<Record<string, Oas3Callback>>;
|
|
142
148
|
Parameter?: VisitFunctionOrObject<Oas3Parameter>;
|
|
143
149
|
Operation?: VisitFunctionOrObject<Oas3Operation>;
|
|
144
150
|
RequestBody?: VisitFunctionOrObject<Oas3RequestBody>;
|
|
@@ -147,7 +153,7 @@ type Oas3FlatVisitor = {
|
|
|
147
153
|
Example?: VisitFunctionOrObject<Oas3Example>;
|
|
148
154
|
Encoding?: VisitFunctionOrObject<Oas3Encoding>;
|
|
149
155
|
Header?: VisitFunctionOrObject<Oas3Header>;
|
|
150
|
-
|
|
156
|
+
Responses?: VisitFunctionOrObject<Record<string, Oas3Response>>;
|
|
151
157
|
Response?: VisitFunctionOrObject<Oas3Response>;
|
|
152
158
|
Link?: VisitFunctionOrObject<Oas3Link>;
|
|
153
159
|
Schema?: VisitFunctionOrObject<Oas3Schema>;
|
|
@@ -169,7 +175,7 @@ type Oas3FlatVisitor = {
|
|
|
169
175
|
PasswordFlow?: VisitFunctionOrObject<Oas3SecurityScheme['flows']['password']>;
|
|
170
176
|
ClientCredentials?: VisitFunctionOrObject<Oas3SecurityScheme['flows']['clientCredentials']>;
|
|
171
177
|
AuthorizationCode?: VisitFunctionOrObject<Oas3SecurityScheme['flows']['authorizationCode']>;
|
|
172
|
-
|
|
178
|
+
OAuth2Flows?: VisitFunctionOrObject<Oas3SecurityScheme['flows']>;
|
|
173
179
|
SecurityScheme?: VisitFunctionOrObject<Oas3SecurityScheme>;
|
|
174
180
|
};
|
|
175
181
|
|
|
@@ -181,13 +187,13 @@ type Oas2FlatVisitor = {
|
|
|
181
187
|
Info?: VisitFunctionOrObject<Oas2Info>;
|
|
182
188
|
Contact?: VisitFunctionOrObject<Oas2Contact>;
|
|
183
189
|
License?: VisitFunctionOrObject<Oas2License>;
|
|
184
|
-
|
|
190
|
+
Paths?: VisitFunctionOrObject<Record<string, Oas2PathItem>>;
|
|
185
191
|
PathItem?: VisitFunctionOrObject<Oas2PathItem>;
|
|
186
192
|
Parameter?: VisitFunctionOrObject<any>;
|
|
187
193
|
Operation?: VisitFunctionOrObject<Oas2Operation>;
|
|
188
194
|
Examples?: VisitFunctionOrObject<Record<string, any>>;
|
|
189
195
|
Header?: VisitFunctionOrObject<Oas2Header>;
|
|
190
|
-
|
|
196
|
+
Responses?: VisitFunctionOrObject<Record<string, Oas2Response>>;
|
|
191
197
|
Response?: VisitFunctionOrObject<Oas2Response>;
|
|
192
198
|
Schema?: VisitFunctionOrObject<Oas2Schema>;
|
|
193
199
|
Xml?: VisitFunctionOrObject<Oas2Xml>;
|
|
@@ -201,13 +207,15 @@ type Oas2FlatVisitor = {
|
|
|
201
207
|
const legacyTypesMap = {
|
|
202
208
|
Root: 'DefinitionRoot',
|
|
203
209
|
ServerVariablesMap: 'ServerVariableMap',
|
|
204
|
-
|
|
210
|
+
Paths: ['PathMap', 'PathsMap'],
|
|
205
211
|
CallbacksMap: 'CallbackMap',
|
|
206
212
|
MediaTypesMap: 'MediaTypeMap',
|
|
207
213
|
ExamplesMap: 'ExampleMap',
|
|
208
|
-
|
|
214
|
+
EncodingMap: 'EncodingsMap',
|
|
209
215
|
HeadersMap: 'HeaderMap',
|
|
210
216
|
LinksMap: 'LinkMap',
|
|
217
|
+
OAuth2Flows: 'SecuritySchemeFlows',
|
|
218
|
+
Responses: 'ResponsesMap',
|
|
211
219
|
};
|
|
212
220
|
|
|
213
221
|
type Oas3NestedVisitor = {
|
|
@@ -372,6 +380,18 @@ export function normalizeVisitors<T extends BaseVisitor>(
|
|
|
372
380
|
}
|
|
373
381
|
}
|
|
374
382
|
|
|
383
|
+
function findLegacyVisitorNode<T>(
|
|
384
|
+
visitor: NestedVisitObject<any, T>,
|
|
385
|
+
typeName: keyof T | Array<keyof T>
|
|
386
|
+
) {
|
|
387
|
+
if (Array.isArray(typeName)) {
|
|
388
|
+
const name = typeName.find((name) => visitor[name]) || undefined;
|
|
389
|
+
return name && visitor[name];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return visitor[typeName];
|
|
393
|
+
}
|
|
394
|
+
|
|
375
395
|
function normalizeVisitorLevel(
|
|
376
396
|
ruleConf: RuleInstanceConfig,
|
|
377
397
|
visitor: NestedVisitObject<any, T>,
|
|
@@ -394,9 +414,10 @@ export function normalizeVisitors<T extends BaseVisitor>(
|
|
|
394
414
|
|
|
395
415
|
for (const typeName of visitorKeys as Array<keyof T>) {
|
|
396
416
|
const typeVisitor = (visitor[typeName] ||
|
|
397
|
-
|
|
417
|
+
findLegacyVisitorNode(
|
|
418
|
+
visitor,
|
|
398
419
|
legacyTypesMap[typeName as keyof typeof legacyTypesMap] as keyof T
|
|
399
|
-
|
|
420
|
+
)) as any as NestedVisitObject<any, T>;
|
|
400
421
|
const normalizedTypeVisitor = normalizedVisitors[typeName];
|
|
401
422
|
|
|
402
423
|
if (!typeVisitor) continue;
|