@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.
Files changed (93) hide show
  1. package/lib/config/all.js +0 -1
  2. package/lib/config/config-resolvers.js +42 -34
  3. package/lib/config/load.d.ts +1 -1
  4. package/lib/config/load.js +5 -5
  5. package/lib/config/minimal.js +0 -1
  6. package/lib/config/recommended.js +0 -1
  7. package/lib/rules/common/assertions/asserts.d.ts +22 -5
  8. package/lib/rules/common/assertions/asserts.js +25 -0
  9. package/lib/rules/common/assertions/index.d.ts +27 -2
  10. package/lib/rules/common/assertions/index.js +6 -29
  11. package/lib/rules/common/assertions/utils.d.ts +7 -14
  12. package/lib/rules/common/assertions/utils.js +129 -97
  13. package/lib/rules/common/no-ambiguous-paths.js +1 -1
  14. package/lib/rules/common/no-identical-paths.js +4 -4
  15. package/lib/rules/common/operation-2xx-response.js +2 -2
  16. package/lib/rules/common/operation-4xx-response.js +2 -2
  17. package/lib/rules/common/path-not-include-query.js +1 -1
  18. package/lib/rules/common/path-params-defined.js +7 -2
  19. package/lib/rules/common/response-contains-header.js +2 -2
  20. package/lib/rules/common/security-defined.js +10 -5
  21. package/lib/rules/common/spec.js +14 -12
  22. package/lib/rules/oas2/index.d.ts +0 -1
  23. package/lib/rules/oas2/index.js +0 -2
  24. package/lib/rules/oas3/index.js +0 -2
  25. package/lib/rules/oas3/request-mime-type.js +1 -1
  26. package/lib/rules/oas3/response-mime-type.js +1 -1
  27. package/lib/rules/other/stats.d.ts +1 -1
  28. package/lib/rules/other/stats.js +1 -1
  29. package/lib/rules/utils.d.ts +1 -0
  30. package/lib/rules/utils.js +17 -1
  31. package/lib/types/oas2.js +6 -6
  32. package/lib/types/oas3.js +11 -11
  33. package/lib/types/oas3_1.js +3 -3
  34. package/lib/types/redocly-yaml.js +58 -31
  35. package/lib/utils.d.ts +2 -0
  36. package/lib/utils.js +19 -1
  37. package/lib/visitors.d.ts +9 -7
  38. package/lib/visitors.js +12 -3
  39. package/lib/walk.js +7 -1
  40. package/package.json +1 -1
  41. package/src/__tests__/__snapshots__/bundle.test.ts.snap +1 -1
  42. package/src/__tests__/lint.test.ts +24 -5
  43. package/src/__tests__/utils.test.ts +11 -0
  44. package/src/__tests__/walk.test.ts +2 -2
  45. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +1 -3
  46. package/src/config/__tests__/config-resolvers.test.ts +30 -5
  47. package/src/config/__tests__/fixtures/load-redocly.yaml +4 -0
  48. package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +6 -4
  49. package/src/config/__tests__/load.test.ts +4 -1
  50. package/src/config/all.ts +0 -1
  51. package/src/config/config-resolvers.ts +44 -20
  52. package/src/config/load.ts +8 -5
  53. package/src/config/minimal.ts +0 -1
  54. package/src/config/recommended.ts +0 -1
  55. package/src/rules/common/__tests__/operation-2xx-response.test.ts +37 -0
  56. package/src/rules/common/__tests__/operation-4xx-response.test.ts +37 -0
  57. package/src/rules/common/__tests__/path-params-defined.test.ts +69 -0
  58. package/src/rules/common/__tests__/security-defined.test.ts +6 -6
  59. package/src/rules/common/__tests__/spec.test.ts +125 -0
  60. package/src/rules/common/assertions/__tests__/asserts.test.ts +7 -3
  61. package/src/rules/common/assertions/__tests__/index.test.ts +41 -20
  62. package/src/rules/common/assertions/__tests__/utils.test.ts +44 -18
  63. package/src/rules/common/assertions/asserts.ts +60 -8
  64. package/src/rules/common/assertions/index.ts +36 -46
  65. package/src/rules/common/assertions/utils.ts +204 -127
  66. package/src/rules/common/no-ambiguous-paths.ts +1 -1
  67. package/src/rules/common/no-identical-paths.ts +4 -4
  68. package/src/rules/common/operation-2xx-response.ts +2 -2
  69. package/src/rules/common/operation-4xx-response.ts +2 -2
  70. package/src/rules/common/path-not-include-query.ts +1 -1
  71. package/src/rules/common/path-params-defined.ts +9 -2
  72. package/src/rules/common/response-contains-header.ts +6 -1
  73. package/src/rules/common/security-defined.ts +10 -5
  74. package/src/rules/common/spec.ts +15 -11
  75. package/src/rules/oas2/index.ts +0 -2
  76. package/src/rules/oas3/__tests__/response-contains-header.test.ts +116 -0
  77. package/src/rules/oas3/index.ts +0 -2
  78. package/src/rules/oas3/request-mime-type.ts +1 -1
  79. package/src/rules/oas3/response-mime-type.ts +1 -1
  80. package/src/rules/other/stats.ts +1 -1
  81. package/src/rules/utils.ts +22 -0
  82. package/src/types/oas2.ts +6 -6
  83. package/src/types/oas3.ts +11 -11
  84. package/src/types/oas3_1.ts +3 -3
  85. package/src/types/redocly-yaml.ts +58 -33
  86. package/src/utils.ts +18 -0
  87. package/src/visitors.ts +32 -11
  88. package/src/walk.ts +8 -1
  89. package/tsconfig.tsbuildinfo +1 -1
  90. package/lib/rules/common/info-description.d.ts +0 -2
  91. package/lib/rules/common/info-description.js +0 -12
  92. package/src/rules/common/__tests__/info-description.test.ts +0 -102
  93. 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
  });
@@ -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
- PathsMap: {
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
- PathsMap: {
8
+ Paths: {
9
9
  Response: {
10
10
  leave(response: Oas3Response, ctx: UserContext) {
11
11
  validateMimeTypeOAS3({ type: 'produces', value: response }, ctx, allowedValues);
@@ -41,7 +41,7 @@ export const Stats = (statsAccumulator: StatsAccumulator) => {
41
41
  },
42
42
  },
43
43
  },
44
- PathsMap: {
44
+ Paths: {
45
45
  PathItem: {
46
46
  leave() {
47
47
  statsAccumulator.pathItems.total++;
@@ -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: 'PathsMap',
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 PathsMap: NodeType = {
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: 'ResponsesMap',
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 ResponsesMap: NodeType = {
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
- PathsMap,
379
+ Paths,
380
380
  PathItem,
381
381
  Parameter,
382
382
  ParameterItems,
383
383
  Operation,
384
384
  Examples,
385
385
  Header,
386
- ResponsesMap,
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: 'PathsMap',
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 PathsMap: NodeType = {
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: 'ResponsesMap',
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: 'EncodingsMap',
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 ResponsesMap: NodeType = {
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 SecuritySchemeFlows: NodeType = {
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: 'SecuritySchemeFlows',
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
- PathsMap,
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
- EncodingsMap: mapOf('Encoding'),
485
+ EncodingMap: mapOf('Encoding'),
486
486
  Header,
487
487
  HeadersMap: mapOf('Header'),
488
- ResponsesMap,
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
- SecuritySchemeFlows,
511
+ OAuth2Flows,
512
512
  SecurityScheme,
513
513
  XCodeSample,
514
514
  WebhooksMap,
@@ -9,7 +9,7 @@ const Root: NodeType = {
9
9
  security: listOf('SecurityRequirement'),
10
10
  tags: listOf('Tag'),
11
11
  externalDocs: 'ExternalDocs',
12
- paths: 'PathsMap',
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: 'ResponsesMap',
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: 'SecuritySchemeFlows',
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
- 'PathsMap',
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
- 'EncodingsMap',
80
+ 'EncodingMap',
82
81
  'Header',
83
82
  'HeadersMap',
84
- 'ResponsesMap',
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
- 'SecuritySchemeFlows',
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 Assert: NodeType = {
233
+ const AssertionDefinitionSubject: NodeType = {
243
234
  properties: {
244
- subject: (value: unknown) => {
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
- context: listOf('Context'),
261
- message: { type: 'string' },
262
- suggest: { type: 'array', items: { type: 'string' } },
263
- severity: { enum: ['error', 'warn', 'off'] },
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 Context: NodeType = {
299
+ const AssertDefinition: NodeType = {
298
300
  properties: {
299
- type: { enum: nodeTypesList },
300
- matchParentKeys: { type: 'array', items: { type: 'string' } },
301
- excludeParentKeys: { type: 'array', items: { type: 'string' } },
301
+ subject: 'AssertionDefinitionSubject',
302
+ assertions: 'AssertionDefinitionAssertions',
302
303
  },
303
- required: ['type'],
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
- Context,
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
- PathsMap?: VisitFunctionOrObject<Record<string, Oas3PathItem>>;
144
+ Paths?: VisitFunctionOrObject<Record<string, Oas3PathItem>>;
140
145
  PathItem?: VisitFunctionOrObject<Oas3PathItem>;
141
- Callback?: VisitFunctionOrObject<Record<string, Oas3PathItem>>;
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
- ResponsesMap?: VisitFunctionOrObject<Record<string, Oas3Response>>;
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
- SecuritySchemeFlows?: VisitFunctionOrObject<Oas3SecurityScheme['flows']>;
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
- PathsMap?: VisitFunctionOrObject<Record<string, Oas2PathItem>>;
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
- ResponsesMap?: VisitFunctionOrObject<Record<string, Oas2Response>>;
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
- PathsMap: 'PathMap',
210
+ Paths: ['PathMap', 'PathsMap'],
205
211
  CallbacksMap: 'CallbackMap',
206
212
  MediaTypesMap: 'MediaTypeMap',
207
213
  ExamplesMap: 'ExampleMap',
208
- EncodingsMap: 'EncodingMap',
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
- visitor[
417
+ findLegacyVisitorNode(
418
+ visitor,
398
419
  legacyTypesMap[typeName as keyof typeof legacyTypesMap] as keyof T
399
- ]) as any as NestedVisitObject<any, T>;
420
+ )) as any as NestedVisitObject<any, T>;
400
421
  const normalizedTypeVisitor = normalizedVisitors[typeName];
401
422
 
402
423
  if (!typeVisitor) continue;