@redocly/openapi-core 1.0.0-beta.80 → 1.0.0-beta.81

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.
@@ -21,35 +21,19 @@ const DefinitionRoot = {
21
21
  };
22
22
  const License = {
23
23
  properties: {
24
- name: {
25
- type: 'string',
26
- },
27
- url: {
28
- type: 'string',
29
- },
30
- identifier: {
31
- type: 'string',
32
- },
24
+ name: { type: 'string' },
25
+ url: { type: 'string' },
26
+ identifier: { type: 'string' },
33
27
  },
34
28
  required: ['name'],
35
29
  };
36
30
  const Info = {
37
31
  properties: {
38
- title: {
39
- type: 'string',
40
- },
41
- version: {
42
- type: 'string',
43
- },
44
- description: {
45
- type: 'string',
46
- },
47
- termsOfService: {
48
- type: 'string',
49
- },
50
- summary: {
51
- type: 'string',
52
- },
32
+ title: { type: 'string' },
33
+ version: { type: 'string' },
34
+ description: { type: 'string' },
35
+ termsOfService: { type: 'string' },
36
+ summary: { type: 'string' },
53
37
  contact: 'Contact',
54
38
  license: 'License',
55
39
  },
@@ -84,9 +68,7 @@ const Operation = {
84
68
  servers: _1.listOf('Server'),
85
69
  requestBody: 'RequestBody',
86
70
  responses: 'ResponsesMap',
87
- deprecated: {
88
- type: 'boolean',
89
- },
71
+ deprecated: { type: 'boolean' },
90
72
  callbacks: _1.mapOf('Callback'),
91
73
  'x-codeSamples': _1.listOf('XCodeSample'),
92
74
  'x-code-samples': _1.listOf('XCodeSample'), // deprecated
@@ -137,7 +119,7 @@ const Schema = {
137
119
  then: 'Schema',
138
120
  else: 'Schema',
139
121
  dependentSchemas: _1.listOf('Schema'),
140
- prefixItems: { type: 'array' },
122
+ prefixItems: _1.listOf('Schema'),
141
123
  contains: 'Schema',
142
124
  patternProperties: { type: 'object' },
143
125
  propertyNames: 'Schema',
@@ -149,18 +131,16 @@ const Schema = {
149
131
  if (Array.isArray(value)) {
150
132
  return _1.listOf('Schema');
151
133
  }
152
- else {
153
- return 'Schema';
154
- }
155
- },
156
- additionalProperties: (value) => {
157
- if (typeof value === 'boolean') {
134
+ else if (typeof value === 'boolean') {
158
135
  return { type: 'boolean' };
159
136
  }
160
137
  else {
161
138
  return 'Schema';
162
139
  }
163
140
  },
141
+ additionalProperties: (value) => {
142
+ return typeof value === 'boolean' ? { type: 'boolean' } : 'Schema';
143
+ },
164
144
  description: { type: 'string' },
165
145
  format: { type: 'string' },
166
146
  contentEncoding: { type: 'string' },
@@ -181,30 +161,53 @@ const SecurityScheme = {
181
161
  type: { enum: ['apiKey', 'http', 'oauth2', 'openIdConnect', 'mutualTLS'] },
182
162
  description: { type: 'string' },
183
163
  name: { type: 'string' },
184
- in: { type: 'string' },
164
+ in: { type: 'string', enum: ['query', 'header', 'cookie'] },
185
165
  scheme: { type: 'string' },
186
166
  bearerFormat: { type: 'string' },
187
167
  flows: 'SecuritySchemeFlows',
188
168
  openIdConnectUrl: { type: 'string' },
189
169
  },
190
170
  required(value) {
191
- if (!(value === null || value === void 0 ? void 0 : value.type)) {
192
- return ['type'];
193
- }
194
- if (value.type === 'apiKey') {
195
- return ['type', 'name', 'in'];
171
+ switch (value === null || value === void 0 ? void 0 : value.type) {
172
+ case 'apiKey':
173
+ return ['type', 'name', 'in'];
174
+ case 'http':
175
+ return ['type', 'scheme'];
176
+ case 'oauth2':
177
+ return ['type', 'flows'];
178
+ case 'openIdConnect':
179
+ return ['type', 'openIdConnectUrl'];
180
+ default:
181
+ return ['type'];
196
182
  }
197
- else if (value.type === 'http') {
198
- return ['type', 'scheme'];
199
- }
200
- else if (value.type === 'oauth2') {
201
- return ['type', 'flows'];
202
- }
203
- else if (value.type === 'openIdConnect') {
204
- return ['type', 'openIdConnectUrl'];
183
+ },
184
+ allowed(value) {
185
+ switch (value === null || value === void 0 ? void 0 : value.type) {
186
+ case 'apiKey':
187
+ return ['type', 'name', 'in', 'description'];
188
+ case 'http':
189
+ return ['type', 'scheme', 'bearerFormat', 'description'];
190
+ case 'oauth2':
191
+ switch (value === null || value === void 0 ? void 0 : value.flows) {
192
+ case 'implicit':
193
+ return ['type', 'flows', 'authorizationUrl', 'refreshUrl', 'description', 'scopes'];
194
+ case 'password':
195
+ case 'clientCredentials':
196
+ return ['type', 'flows', 'tokenUrl', 'refreshUrl', 'description', 'scopes'];
197
+ case 'authorizationCode':
198
+ return ['type', 'flows', 'authorizationUrl', 'refreshUrl', 'tokenUrl', 'description', 'scopes'];
199
+ default:
200
+ return ['type', 'flows', 'authorizationUrl', 'refreshUrl', 'tokenUrl', 'description', 'scopes'];
201
+ }
202
+ case 'openIdConnect':
203
+ return ['type', 'openIdConnectUrl', 'description'];
204
+ case 'mutualTLS':
205
+ return ['type', 'description'];
206
+ default:
207
+ return ['type', 'description'];
205
208
  }
206
- return ['type'];
207
209
  },
210
+ extensionsPrefix: 'x-',
208
211
  };
209
212
  exports.Oas3_1Types = Object.assign(Object.assign({}, oas3_1.Oas3Types), { Info,
210
213
  DefinitionRoot,
package/lib/utils.js CHANGED
@@ -138,7 +138,7 @@ function readFileAsStringSync(filePath) {
138
138
  }
139
139
  exports.readFileAsStringSync = readFileAsStringSync;
140
140
  function isPathParameter(pathSegment) {
141
- return pathSegment.startsWith('{') && pathSegment.endsWith('{');
141
+ return pathSegment.startsWith('{') && pathSegment.endsWith('}');
142
142
  }
143
143
  exports.isPathParameter = isPathParameter;
144
144
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.0.0-beta.80",
3
+ "version": "1.0.0-beta.81",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
@@ -3,6 +3,7 @@ import { RegistryApiTypes } from './registry-api-types';
3
3
  import { AccessTokens, Region, DEFAULT_REGION, DOMAINS } from '../config/config';
4
4
  import { isNotEmptyObject } from '../utils';
5
5
  const version = require('../../package.json').version;
6
+ const HttpsProxyAgent = require('https-proxy-agent');
6
7
 
7
8
  export class RegistryApi {
8
9
  constructor(private accessTokens: AccessTokens, private region: Region) {}
@@ -23,7 +24,12 @@ export class RegistryApi {
23
24
  private async request(path = '', options: RequestInit = {}, region?: Region) {
24
25
  const headers = Object.assign({}, options.headers || {}, { 'x-redocly-cli-version': version });
25
26
  if (!headers.hasOwnProperty('authorization')) { throw new Error('Unauthorized'); }
26
- const response = await fetch(`${this.getBaseUrl(region)}${path}`, Object.assign({}, options, { headers }));
27
+ const proxy = process.env.REDOCLY_PROXY;
28
+ const agent = proxy ? new HttpsProxyAgent(proxy) : undefined;
29
+ const response = await fetch(
30
+ `${this.getBaseUrl(region)}${path}`,
31
+ Object.assign({}, options, { headers, agent }),
32
+ );
27
33
  if (response.status === 401) { throw new Error('Unauthorized'); }
28
34
  if (response.status === 404) {
29
35
  const body: RegistryApiTypes.NotFoundProblemResponse = await response.json();
@@ -1,6 +1,8 @@
1
1
  import { Oas3Rule, Oas2Rule } from '../../visitors';
2
2
  import { Location } from '../../ref-utils';
3
3
  import { UserContext } from '../../walk';
4
+ import { Oas2SecurityScheme } from '../../typings/swagger';
5
+ import { Oas3SecurityScheme } from '../../typings/openapi';
4
6
 
5
7
  export const OperationSecurityDefined: Oas3Rule | Oas2Rule = () => {
6
8
  let referencedSchemes = new Map<
@@ -25,11 +27,8 @@ export const OperationSecurityDefined: Oas3Rule | Oas2Rule = () => {
25
27
  }
26
28
  },
27
29
  },
28
- SecurityScheme(_securityScheme: object, { key }: UserContext) {
29
- referencedSchemes.set(key.toString(), {
30
- defined: true,
31
- from: [],
32
- });
30
+ SecurityScheme(_securityScheme: Oas2SecurityScheme | Oas3SecurityScheme, { key }: UserContext) {
31
+ referencedSchemes.set(key.toString(), { defined: true, from: [] });
33
32
  },
34
33
  SecurityRequirement(requirements, { location }) {
35
34
  for (const requirement of Object.keys(requirements)) {
@@ -2,6 +2,7 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors';
2
2
  import { isNamedType } from '../../types';
3
3
  import { oasTypeOf, matchesJsonSchemaType, getSuggest } from '../utils';
4
4
  import { isRef } from '../../ref-utils';
5
+ import { isPlainObject } from '../../utils';
5
6
 
6
7
  export const OasSpec: Oas3Rule | Oas2Rule = () => {
7
8
  return {
@@ -26,6 +27,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
26
27
 
27
28
  const required =
28
29
  typeof type.required === 'function' ? type.required(node, key) : type.required;
30
+
29
31
  for (let propName of required || []) {
30
32
  if (!(node as object).hasOwnProperty(propName)) {
31
33
  report({
@@ -35,6 +37,22 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
35
37
  }
36
38
  }
37
39
 
40
+ const allowed = type.allowed?.(node);
41
+ if (allowed && isPlainObject(node)) {
42
+ for (const propName in node) {
43
+ if (allowed.includes(propName) ||
44
+ (type.extensionsPrefix && propName.startsWith(type.extensionsPrefix)) ||
45
+ !Object.keys(type.properties).includes(propName)
46
+ ) {
47
+ continue;
48
+ }
49
+ report({
50
+ message: `The field \`${propName}\` is not allowed here.`,
51
+ location: location.child([propName]).key()
52
+ });
53
+ }
54
+ }
55
+
38
56
  const requiredOneOf = type.requiredOneOf || null;
39
57
  if (requiredOneOf) {
40
58
  let hasProperty = false;
@@ -13,31 +13,27 @@ export const ValidContentExamples: Oas3Rule = (opts) => {
13
13
  const { location, resolve } = ctx;
14
14
  if (!mediaType.schema) return;
15
15
  if (mediaType.example) {
16
+ resolveAndValidateExample(mediaType.example, location.child('example'));
17
+ } else if (mediaType.examples) {
18
+ for (const exampleName of Object.keys(mediaType.examples)) {
19
+ resolveAndValidateExample(mediaType.examples[exampleName], location.child(['examples', exampleName, 'value']), true);
20
+ }
21
+ }
22
+
23
+ function resolveAndValidateExample(example: Oas3Example | any, location: Location, isMultiple?: boolean) {
24
+ if (isRef(example)) {
25
+ const resolved = resolve<Oas3Example>(example);
26
+ if (!resolved.location) return;
27
+ location = isMultiple ? resolved.location.child('value') : resolved.location;
28
+ example = resolved.node;
29
+ }
16
30
  validateExample(
17
- mediaType.example,
18
- mediaType.schema,
19
- location.child('example'),
31
+ isMultiple ? example.value : example,
32
+ mediaType.schema!,
33
+ location,
20
34
  ctx,
21
35
  disallowAdditionalProperties,
22
36
  );
23
- } else if (mediaType.examples) {
24
- for (const exampleName of Object.keys(mediaType.examples)) {
25
- let example = mediaType.examples[exampleName];
26
- let dataLoc: Location = location.child(['examples', exampleName, 'value']);
27
- if (isRef(example)) {
28
- const resolved = resolve<Oas3Example>(example);
29
- if (!resolved.location) continue;
30
- dataLoc = resolved.location.child('value');
31
- example = resolved.node;
32
- }
33
- validateExample(
34
- example.value,
35
- mediaType.schema,
36
- dataLoc,
37
- ctx,
38
- disallowAdditionalProperties,
39
- );
40
- }
41
37
  }
42
38
  },
43
39
  },
@@ -24,6 +24,8 @@ export type NodeType = {
24
24
  items?: string;
25
25
  required?: string[] | ((value: any, key: string | number | undefined) => string[]);
26
26
  requiredOneOf?: string[];
27
+ allowed?: ((value: any) => string[] | undefined);
28
+ extensionsPrefix?: string;
27
29
  };
28
30
  type PropType = string | NodeType | ScalarSchema | undefined | null;
29
31
  type ResolveTypeFn = (value: any, key: string) => string | PropType;
@@ -35,6 +37,8 @@ export type NormalizedNodeType = {
35
37
  items?: NormalizedNodeType;
36
38
  required?: string[] | ((value: any, key: string | number | undefined) => string[]);
37
39
  requiredOneOf?: string[];
40
+ allowed?: ((value: any) => string[] | undefined);
41
+ extensionsPrefix?: string;
38
42
  };
39
43
 
40
44
  type NormalizedPropType = NormalizedNodeType | NormalizedScalarSchema | undefined | null;
package/src/types/oas2.ts CHANGED
@@ -12,12 +12,10 @@ const DefinitionRoot: NodeType = {
12
12
  consumes: { type: 'array', items: { type: 'string' } },
13
13
  produces: { type: 'array', items: { type: 'string' } },
14
14
  paths: 'PathMap',
15
-
16
15
  definitions: 'NamedSchemas',
17
16
  parameters: 'NamedParameters',
18
17
  responses: 'NamedResponses',
19
18
  securityDefinitions: 'NamedSecuritySchemes',
20
-
21
19
  security: listOf('SecurityRequirement'),
22
20
  tags: listOf('Tag'),
23
21
  externalDocs: 'ExternalDocs',
@@ -62,7 +60,6 @@ const PathMap: NodeType = {
62
60
  const PathItem: NodeType = {
63
61
  properties: {
64
62
  $ref: { type: 'string' }, // TODO: verify special $ref handling for Path Item
65
-
66
63
  get: 'Operation',
67
64
  put: 'Operation',
68
65
  post: 'Operation',
@@ -70,7 +67,6 @@ const PathItem: NodeType = {
70
67
  options: 'Operation',
71
68
  head: 'Operation',
72
69
  patch: 'Operation',
73
-
74
70
  parameters: listOf('Parameter'),
75
71
  },
76
72
  };
@@ -78,9 +74,7 @@ const PathItem: NodeType = {
78
74
  const Operation: NodeType = {
79
75
  properties: {
80
76
  tags: { type: 'array', items: { type: 'string' } },
81
- summary: {
82
- type: 'string',
83
- },
77
+ summary: { type: 'string' },
84
78
  description: { type: 'string' },
85
79
  externalDocs: 'ExternalDocs',
86
80
  operationId: { type: 'string' },
@@ -119,11 +113,8 @@ const Parameter: NodeType = {
119
113
  in: { type: 'string', enum: ['query', 'header', 'path', 'formData', 'body'] },
120
114
  description: { type: 'string' },
121
115
  required: { type: 'boolean' },
122
-
123
116
  schema: 'Schema',
124
-
125
117
  type: { type: 'string', enum: ['string', 'number', 'integer', 'boolean', 'array', 'file'] },
126
-
127
118
  format: { type: 'string' },
128
119
  allowEmptyValue: { type: 'boolean' },
129
120
  items: 'ParameterItems',
@@ -197,9 +188,7 @@ const ResponsesMap: NodeType = {
197
188
 
198
189
  const Response: NodeType = {
199
190
  properties: {
200
- description: {
201
- type: 'string',
202
- },
191
+ description: { type: 'string' },
203
192
  schema: 'Schema',
204
193
  headers: mapOf('Header'),
205
194
  examples: 'Examples',
@@ -217,7 +206,6 @@ const Header: NodeType = {
217
206
  description: { type: 'string' },
218
207
  type: { type: 'string', enum: ['string', 'number', 'integer', 'boolean', 'array'] },
219
208
  format: { type: 'string' },
220
-
221
209
  items: 'ParameterItems',
222
210
  collectionFormat: { type: 'string', enum: ['csv', 'ssv', 'tsv', 'pipes', 'multi'] },
223
211
  default: null,
@@ -277,7 +265,6 @@ const Schema: NodeType = {
277
265
  type: 'string',
278
266
  enum: ['object', 'array', 'string', 'number', 'integer', 'boolean', 'null'],
279
267
  },
280
-
281
268
  items: (value: any) => {
282
269
  if (Array.isArray(value)) {
283
270
  return listOf('Schema');
@@ -294,7 +281,6 @@ const Schema: NodeType = {
294
281
  return 'Schema';
295
282
  }
296
283
  },
297
-
298
284
  discriminator: { type: 'string' },
299
285
  readOnly: { type: 'boolean' },
300
286
  xml: 'Xml',
@@ -323,39 +309,55 @@ const SecurityScheme: NodeType = {
323
309
  type: { enum: ['basic', 'apiKey', 'oauth2'] },
324
310
  description: { type: 'string' },
325
311
  name: { type: 'string' },
326
- in: { type: 'string', enum: ['query', 'header', 'cookie'] },
312
+ in: { type: 'string', enum: ['query', 'header'] },
327
313
  flow: { enum: ['implicit', 'password', 'application', 'accessCode'] },
328
314
  authorizationUrl: { type: 'string' },
329
315
  tokenUrl: { type: 'string' },
330
316
  scopes: { type: 'object', additionalProperties: { type: 'string' } },
331
317
  },
332
318
  required(value) {
333
- if (!value?.type) {
334
- return ['type'];
319
+ switch (value?.type) {
320
+ case 'apiKey':
321
+ return ['type', 'name', 'in'];
322
+ case 'oauth2':
323
+ switch (value?.flow) {
324
+ case 'implicit':
325
+ return ['type', 'flow', 'authorizationUrl', 'scopes'];
326
+ case 'accessCode':
327
+ return ['type', 'flow', 'authorizationUrl', 'tokenUrl', 'scopes'];
328
+ case 'application':
329
+ case 'password':
330
+ return ['type', 'flow', 'tokenUrl', 'scopes'];
331
+ default:
332
+ return ['type', 'flow', 'scopes'];
333
+ }
334
+ default:
335
+ return ['type'];
335
336
  }
336
-
337
- if (value.type === 'apiKey') {
338
- return ['type', 'name', 'in'];
339
- } else if (value.type === 'http') {
340
- return ['type', 'scheme'];
341
- } else if (value.type === 'oauth2') {
342
- if (!value?.flow) {
343
- return ['type', 'flow'];
344
- } else if (value.flow === 'implicit') {
345
- return ['type', 'flow', 'authorizationUrl'];
346
- } else if (value.flow === 'accessCode') {
347
- return ['type', 'flow', 'authorizationUrl', 'tokenUrl'];
348
- } else if (value.flow === 'application') {
349
- return ['type', 'flow', 'tokenUrl'];
350
- } else if (value.flow === 'password') {
351
- return ['type', 'flow', 'tokenUrl'];
352
- } else {
353
- return ['type', 'flow'];
354
- }
337
+ },
338
+ allowed(value) {
339
+ switch (value?.type) {
340
+ case 'basic':
341
+ return ['type', 'description'];
342
+ case 'apiKey':
343
+ return ['type', 'name', 'in', 'description'];
344
+ case 'oauth2':
345
+ switch (value?.flow) {
346
+ case 'implicit':
347
+ return ['type', 'flow', 'authorizationUrl', 'description', 'scopes'];
348
+ case 'accessCode':
349
+ return ['type', 'flow', 'authorizationUrl', 'tokenUrl', 'description', 'scopes'];
350
+ case 'application':
351
+ case 'password':
352
+ return ['type', 'flow', 'tokenUrl', 'description', 'scopes'];
353
+ default:
354
+ return ['type', 'flow', 'tokenUrl', 'authorizationUrl', 'description', 'scopes'];
355
+ }
356
+ default:
357
+ return ['type', 'description'];
355
358
  }
356
-
357
- return ['type'];
358
359
  },
360
+ extensionsPrefix: 'x-',
359
361
  };
360
362
 
361
363
  const SecurityRequirement: NodeType = {