@redocly/openapi-core 1.0.0-beta.85 → 1.0.0-beta.88

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.
@@ -1,35 +1,76 @@
1
- import { loadConfig } from '../load';
1
+ import { loadConfig, findConfig } from '../load';
2
2
  import { RedoclyClient } from '../../redocly';
3
3
 
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
4
7
  describe('loadConfig', () => {
5
8
  it('should resolve config http header by US region', async () => {
6
- jest.spyOn(RedoclyClient.prototype, 'getTokens').mockImplementation(
7
- () => Promise.resolve([{ region: 'us', token: "accessToken", valid: true }])
8
- );
9
+ jest
10
+ .spyOn(RedoclyClient.prototype, 'getTokens')
11
+ .mockImplementation(() =>
12
+ Promise.resolve([{ region: 'us', token: 'accessToken', valid: true }]),
13
+ );
9
14
  const config = await loadConfig();
10
- expect(config.resolve.http.headers).toStrictEqual([{
11
- "matches": 'https://api.redoc.ly/registry/**',
12
- "name": "Authorization",
13
- "envVariable": undefined,
14
- "value": "accessToken"
15
- }, {
16
- "matches": 'https://api.redocly.com/registry/**',
17
- "name": "Authorization",
18
- "envVariable": undefined,
19
- "value": "accessToken"
20
- }]);
15
+ expect(config.resolve.http.headers).toStrictEqual([
16
+ {
17
+ matches: 'https://api.redocly.com/registry/**',
18
+ name: 'Authorization',
19
+ envVariable: undefined,
20
+ value: 'accessToken',
21
+ },
22
+ {
23
+ matches: 'https://api.redoc.ly/registry/**',
24
+ name: 'Authorization',
25
+ envVariable: undefined,
26
+ value: 'accessToken',
27
+ },
28
+ ]);
21
29
  });
22
30
 
23
31
  it('should resolve config http header by EU region', async () => {
24
- jest.spyOn(RedoclyClient.prototype, 'getTokens').mockImplementation(
25
- () => Promise.resolve([{ region: 'eu', token: "accessToken", valid: true }])
26
- );
32
+ jest
33
+ .spyOn(RedoclyClient.prototype, 'getTokens')
34
+ .mockImplementation(() =>
35
+ Promise.resolve([{ region: 'eu', token: 'accessToken', valid: true }]),
36
+ );
27
37
  const config = await loadConfig();
28
- expect(config.resolve.http.headers).toStrictEqual([{
29
- "matches": 'https://api.eu.redocly.com/registry/**',
30
- "name": "Authorization",
31
- "envVariable": undefined,
32
- "value": "accessToken"
33
- }]);
38
+ expect(config.resolve.http.headers).toStrictEqual([
39
+ {
40
+ matches: 'https://api.eu.redocly.com/registry/**',
41
+ name: 'Authorization',
42
+ envVariable: undefined,
43
+ value: 'accessToken',
44
+ },
45
+ ]);
46
+ });
47
+ });
48
+
49
+ describe('findConfig', () => {
50
+ it('should find redocly.yaml', async () => {
51
+ jest.spyOn(fs, 'existsSync').mockImplementation((name) => name === 'redocly.yaml');
52
+ const configName = findConfig();
53
+ expect(configName).toStrictEqual('redocly.yaml');
54
+ });
55
+ it('should find .redocly.yaml', async () => {
56
+ jest.spyOn(fs, 'existsSync').mockImplementation((name) => name === '.redocly.yaml');
57
+ const configName = findConfig();
58
+ expect(configName).toStrictEqual('.redocly.yaml');
59
+ });
60
+ it('should throw an error when found multiple config files', async () => {
61
+ jest
62
+ .spyOn(fs, 'existsSync')
63
+ .mockImplementation((name) => name === 'redocly.yaml' || name === '.redocly.yaml');
64
+ expect(findConfig).toThrow(`
65
+ Multiple configuration files are not allowed.
66
+ Found the following files: redocly.yaml, .redocly.yaml.
67
+ Please use 'redocly.yaml' instead.
68
+ `);
69
+ });
70
+ it('should find a nested config ', async () => {
71
+ jest.spyOn(fs, 'existsSync').mockImplementation((name) => name === 'dir/redocly.yaml');
72
+ jest.spyOn(path, 'resolve').mockImplementationOnce((dir, name) => `${dir}/${name}`);
73
+ const configName = findConfig('dir');
74
+ expect(configName).toStrictEqual('dir/redocly.yaml');
34
75
  });
35
76
  });
@@ -2,10 +2,8 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { dirname } from 'path';
4
4
  import { red, blue } from 'colorette';
5
-
6
5
  import { parseYaml, stringifyYaml } from '../js-yaml';
7
6
  import { notUndefined, slash } from '../utils';
8
-
9
7
  import {
10
8
  OasVersion,
11
9
  Oas3PreprocessorsSet,
@@ -16,9 +14,7 @@ import {
16
14
  Oas2DecoratorsSet,
17
15
  Oas3RuleSet
18
16
  } from '../oas-types';
19
-
20
17
  import { ProblemSeverity, NormalizedProblem } from '../walk';
21
-
22
18
  import recommended from './recommended';
23
19
  import { NodeType } from '../types';
24
20
 
@@ -127,10 +123,9 @@ export type Region = 'us' | 'eu';
127
123
  export type AccessTokens = {[region in Region]?: string };
128
124
  const REDOCLY_DOMAIN = process.env.REDOCLY_DOMAIN;
129
125
  export const DOMAINS: { [region in Region]: string } = {
130
- us: 'redoc.ly',
126
+ us: 'redocly.com',
131
127
  eu: 'eu.redocly.com',
132
128
  };
133
- export const AVAILABLE_REGIONS = Object.keys(DOMAINS) as Region[];
134
129
 
135
130
  // FIXME: temporary fix for our lab environments
136
131
  if (REDOCLY_DOMAIN?.endsWith('.redocly.host')) {
@@ -139,13 +134,31 @@ if (REDOCLY_DOMAIN?.endsWith('.redocly.host')) {
139
134
  if (REDOCLY_DOMAIN === 'redoc.online') {
140
135
  DOMAINS[REDOCLY_DOMAIN as Region] = REDOCLY_DOMAIN;
141
136
  }
137
+ export const AVAILABLE_REGIONS = Object.keys(DOMAINS) as Region[];
142
138
 
143
- export type RawConfig = {
144
- referenceDocs?: any;
139
+ export type DeprecatedRawConfig = {
145
140
  apiDefinitions?: Record<string, string>;
146
141
  lint?: LintRawConfig;
147
142
  resolve?: RawResolveConfig;
148
143
  region?: Region;
144
+ referenceDocs?: any;
145
+ }
146
+
147
+ export type Api = {
148
+ root: string;
149
+ lint?: LintRawConfig;
150
+ 'features.openapi'?: any;
151
+ 'features.mockServer'?: any;
152
+ };
153
+
154
+ export type RawConfig = {
155
+ apis?: Record<string, Api>;
156
+ lint?: LintRawConfig;
157
+ resolve?: RawResolveConfig;
158
+ region?: Region;
159
+ 'features.openapi'?: any;
160
+ 'features.mockServer'?: any;
161
+ organization?: string;
149
162
  };
150
163
 
151
164
  export class LintConfig {
@@ -398,16 +411,19 @@ export class LintConfig {
398
411
  }
399
412
 
400
413
  export class Config {
401
- referenceDocs: any;
402
- apiDefinitions: Record<string, string>;
414
+ apis: Record<string, Api>;
403
415
  lint: LintConfig;
404
416
  resolve: ResolveConfig;
405
417
  licenseKey?: string;
406
418
  region?: Region;
419
+ 'features.openapi': Record<string, any>;
420
+ 'features.mockServer'?: Record<string, any>;
421
+ organization?: string;
407
422
  constructor(public rawConfig: RawConfig, public configFile?: string) {
408
- this.apiDefinitions = rawConfig.apiDefinitions || {};
423
+ this.apis = rawConfig.apis || {};
409
424
  this.lint = new LintConfig(rawConfig.lint || {}, configFile);
410
- this.referenceDocs = rawConfig.referenceDocs || {};
425
+ this['features.openapi'] = rawConfig['features.openapi'] || {};
426
+ this['features.mockServer'] = rawConfig['features.mockServer'] || {};
411
427
  this.resolve = {
412
428
  http: {
413
429
  headers: rawConfig?.resolve?.http?.headers ?? [],
@@ -415,13 +431,13 @@ export class Config {
415
431
  },
416
432
  };
417
433
  this.region = rawConfig.region;
434
+ this.organization = rawConfig.organization;
418
435
  }
419
436
  }
420
437
 
421
438
  function resolvePresets(presets: string[], plugins: Plugin[]) {
422
439
  return presets.map((presetName) => {
423
440
  const { pluginId, configName } = parsePresetName(presetName);
424
-
425
441
  const plugin = plugins.find((p) => p.id === pluginId);
426
442
  if (!plugin) {
427
443
  throw new Error(`Invalid config ${red(presetName)}: plugin ${pluginId} is not included.`);
@@ -624,3 +640,72 @@ function assignExisting<T>(target: Record<string, T>, obj: Record<string, T>) {
624
640
  }
625
641
  }
626
642
  }
643
+
644
+ export function getMergedConfig(config: Config, entrypointAlias?: string): Config {
645
+ return entrypointAlias
646
+ ? new Config({
647
+ ...config.rawConfig,
648
+ lint: getMergedLintConfig(config, entrypointAlias),
649
+ 'features.openapi': {
650
+ ...config['features.openapi'],
651
+ ...config.apis[entrypointAlias]?.['features.openapi'],
652
+ },
653
+ 'features.mockServer': {
654
+ ...config['features.mockServer'],
655
+ ...config.apis[entrypointAlias]?.['features.mockServer'],
656
+ },
657
+ // TODO: merge everything else here
658
+ })
659
+ : config;
660
+ }
661
+
662
+ export function getMergedLintConfig(
663
+ config: Config,
664
+ entrypointAlias?: string
665
+ ) {
666
+ const apiLint = entrypointAlias
667
+ ? config.apis[entrypointAlias]?.lint
668
+ : {};
669
+ const mergedLint = {
670
+ ...config.rawConfig.lint,
671
+ ...apiLint,
672
+ rules: { ...config.rawConfig.lint?.rules, ...apiLint?.rules },
673
+ preprocessors: { ...config.rawConfig.lint?.preprocessors, ...apiLint?.preprocessors },
674
+ decorators: { ...config.rawConfig.lint?.decorators, ...apiLint?.decorators },
675
+ };
676
+ return mergedLint;
677
+ }
678
+
679
+ function transformApiDefinitionsToApis(apiDefinitions: Record<string, string> = {}): Record<string, Api> {
680
+ let apis: Record<string, Api> = {};
681
+ for (const [apiName, apiPath] of Object.entries(apiDefinitions)) {
682
+ apis[apiName] = { root: apiPath };
683
+ }
684
+ return apis;
685
+ }
686
+
687
+ export function transformConfig(rawConfig: DeprecatedRawConfig | RawConfig): RawConfig {
688
+ if ((rawConfig as RawConfig).apis && (rawConfig as DeprecatedRawConfig).apiDefinitions) {
689
+ throw new Error("Do not use 'apiDefinitions' field. Use 'apis' instead.\n");
690
+ }
691
+ if ((rawConfig as RawConfig)['features.openapi'] && (rawConfig as DeprecatedRawConfig).referenceDocs) {
692
+ throw new Error("Do not use 'referenceDocs' field. Use 'features.openapi' instead.\n");
693
+ }
694
+ const { apiDefinitions, referenceDocs, ...rest } = rawConfig as DeprecatedRawConfig & RawConfig;
695
+ // TODO: put links to the changelog and uncomment this after successful release of ReferenceDocs/Redoc, Portal and Workflows
696
+ // if (apiDefinitions) {
697
+ // process.stderr.write(
698
+ // `The ${yellow('apiDefinitions')} field is deprecated. Use ${green('apis')} instead, see changelog: <link>\n`
699
+ // );
700
+ // }
701
+ // if (referenceDocs) {
702
+ // process.stderr.write(
703
+ // `The ${yellow('referenceDocs')} field is deprecated. Use ${green('features.openapi')} instead, see changelog: <link>\n`
704
+ // );
705
+ // }
706
+ return {
707
+ 'features.openapi': referenceDocs,
708
+ apis: transformApiDefinitionsToApis(apiDefinitions),
709
+ ...rest,
710
+ };
711
+ }
@@ -1,23 +1,13 @@
1
1
  import * as fs from 'fs';
2
+ import * as path from 'path';
2
3
  import { RedoclyClient } from '../redocly';
3
4
  import { loadYaml } from '../utils';
4
- import { Config, DOMAINS, RawConfig, Region } from './config';
5
-
5
+ import { Config, DOMAINS, RawConfig, Region, transformConfig } from './config';
6
6
  import { defaultPlugin } from './builtIn';
7
7
 
8
- export async function loadConfig(configPath?: string, customExtends?: string[]): Promise<Config> {
9
- if (configPath === undefined) {
10
- configPath = findConfig();
11
- }
12
- let rawConfig: RawConfig = {};
8
+ export async function loadConfig(configPath: string | undefined = findConfig(), customExtends?: string[]): Promise<Config> {
9
+ const rawConfig = await getConfig(configPath);
13
10
 
14
- if (configPath !== undefined) {
15
- try {
16
- rawConfig = (await loadYaml(configPath)) as RawConfig;
17
- } catch (e) {
18
- throw new Error(`Error parsing config file at \`${configPath}\`: ${e.message}`);
19
- }
20
- }
21
11
  if (customExtends !== undefined) {
22
12
  rawConfig.lint = rawConfig.lint || {};
23
13
  rawConfig.lint.extends = customExtends;
@@ -41,14 +31,13 @@ export async function loadConfig(configPath?: string, customExtends?: string[]):
41
31
  },
42
32
  //support redocly.com domain for future compatibility
43
33
  ...(item.region === 'us' ? [{
44
- matches: `https://api.redocly.com/registry/**`,
34
+ matches: `https://api.redoc.ly/registry/**`,
45
35
  name: 'Authorization',
46
36
  envVariable: undefined,
47
37
  value: item.token,
48
38
  }] : []));
49
39
  }
50
40
  }
51
-
52
41
  return new Config(
53
42
  {
54
43
  ...rawConfig,
@@ -61,11 +50,29 @@ export async function loadConfig(configPath?: string, customExtends?: string[]):
61
50
  );
62
51
  }
63
52
 
64
- function findConfig() {
65
- if (fs.existsSync('.redocly.yaml')) {
66
- return '.redocly.yaml';
67
- } else if (fs.existsSync('.redocly.yml')) {
68
- return '.redocly.yml';
53
+ export const CONFIG_FILE_NAMES = ['redocly.yaml', 'redocly.yml', '.redocly.yaml', '.redocly.yml'];
54
+
55
+ export function findConfig(dir?: string): string | undefined {
56
+ if (!fs.hasOwnProperty('existsSync')) return;
57
+ const existingConfigFiles = CONFIG_FILE_NAMES
58
+ .map(name => dir ? path.resolve(dir, name) : name)
59
+ .filter(fs.existsSync);
60
+ if (existingConfigFiles.length > 1) {
61
+ throw new Error(`
62
+ Multiple configuration files are not allowed.
63
+ Found the following files: ${existingConfigFiles.join(', ')}.
64
+ Please use 'redocly.yaml' instead.
65
+ `);
66
+ }
67
+ return existingConfigFiles[0];
68
+ }
69
+
70
+ export async function getConfig(configPath: string | undefined = findConfig()) {
71
+ if (!configPath) return {};
72
+ try {
73
+ const rawConfig = (await loadYaml(configPath)) as RawConfig;
74
+ return transformConfig(rawConfig);
75
+ } catch (e) {
76
+ throw new Error(`Error parsing config file at '${configPath}': ${e.message}`);
69
77
  }
70
- return undefined;
71
78
  }
@@ -183,8 +183,10 @@ export function getAstNodeByPointer(root: YAMLNode, pointer: string, reportOnKey
183
183
  for (const key of pointerSegments) {
184
184
  if (currentNode.kind === yamlAst.Kind.MAP) {
185
185
  const mapping = currentNode.mappings.find((m) => m.key.value === key);
186
- if (!mapping?.value) break;
187
- currentNode = mapping?.value as YAMLNode;
186
+ if (!mapping) break;
187
+ currentNode = mapping as YAMLNode;
188
+ if (!mapping?.value) break; // If node has value - return value, if not - return node itself
189
+ currentNode = mapping.value as YAMLNode;
188
190
  } else if (currentNode.kind === yamlAst.Kind.SEQ) {
189
191
  const elem = currentNode.items[parseInt(key, 10)] as YAMLNode;
190
192
  if (!elem) break;
package/src/index.ts CHANGED
@@ -14,16 +14,26 @@ export {
14
14
  Oas3_1Schema,
15
15
  Oas3Tag,
16
16
  Oas3_1Webhooks,
17
- Referenced
17
+ Referenced,
18
18
  } from './typings/openapi';
19
19
  export { Oas2Definition } from './typings/swagger';
20
20
  export { StatsAccumulator, StatsName } from './typings/common';
21
21
  export { normalizeTypes } from './types';
22
22
  export { Stats } from './rules/other/stats';
23
23
 
24
- export { Config, LintConfig, RawConfig, IGNORE_FILE, Region } from './config/config';
25
- export { loadConfig } from './config/load';
26
- export { RedoclyClient } from './redocly';
24
+ export {
25
+ Config,
26
+ LintConfig,
27
+ RawConfig,
28
+ IGNORE_FILE,
29
+ Region,
30
+ getMergedConfig,
31
+ transformConfig,
32
+ } from './config/config';
33
+
34
+ export { loadConfig, getConfig, findConfig, CONFIG_FILE_NAMES } from './config/load';
35
+ export { RedoclyClient, isRedoclyRegistryURL } from './redocly';
36
+
27
37
  export {
28
38
  Source,
29
39
  BaseResolver,
@@ -1,7 +1,7 @@
1
1
  import { RedoclyClient } from '../index';
2
2
 
3
3
  describe('RedoclyClient', () => {
4
- const REDOCLY_DOMAIN_US = 'redoc.ly';
4
+ const REDOCLY_DOMAIN_US = 'redocly.com';
5
5
  const REDOCLY_DOMAIN_EU = 'eu.redocly.com';
6
6
  const REDOCLY_AUTHORIZATION_TOKEN = 'redocly-auth-token';
7
7
  const testRedoclyDomain = 'redoclyDomain.com';
@@ -9,6 +9,7 @@ import { isNotEmptyObject } from '../utils';
9
9
 
10
10
  const TOKEN_FILENAME = '.redocly-config.json';
11
11
 
12
+ let REDOCLY_DOMAIN: string; // workaround for the isRedoclyRegistryURL, see more below
12
13
  export class RedoclyClient {
13
14
  private accessTokens: AccessTokens = {};
14
15
  private region: Region;
@@ -21,6 +22,12 @@ export class RedoclyClient {
21
22
  this.domain = region
22
23
  ? DOMAINS[region]
23
24
  : process.env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
25
+
26
+ /*
27
+ * We can't use process.env here because it is replaced by a const in some client-side bundles,
28
+ * which breaks assignment.
29
+ */
30
+ REDOCLY_DOMAIN = this.domain; // isRedoclyRegistryURL depends on the value to be set
24
31
  this.registryApi = new RegistryApi(this.accessTokens, this.region);
25
32
  }
26
33
 
@@ -168,17 +175,16 @@ export class RedoclyClient {
168
175
  }
169
176
 
170
177
  export function isRedoclyRegistryURL(link: string): boolean {
171
- const domain = process.env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
172
- if (!link.startsWith(`https://api.${domain}/registry/`)) return false;
173
- const registryPath = link.replace(`https://api.${domain}/registry/`, '');
178
+ const domain = REDOCLY_DOMAIN || process.env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
174
179
 
175
- const pathParts = registryPath.split('/');
180
+ const legacyDomain = domain === 'redocly.com' ? 'redoc.ly' : domain;
176
181
 
177
- // we can be sure, that there is job UUID present
178
- // (org, definition, version, bundle, branch, job, "openapi.yaml" 🤦‍♂️)
179
- // so skip this link.
180
- // FIXME
181
- if (pathParts.length === 7) return false;
182
+ if (
183
+ !link.startsWith(`https://api.${domain}/registry/`) &&
184
+ !link.startsWith(`https://api.${legacyDomain}/registry/`)
185
+ ) {
186
+ return false;
187
+ }
182
188
 
183
189
  return true;
184
190
  }
@@ -0,0 +1,62 @@
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('Oas3 spec', () => {
7
+ it('should report missing schema property', async () => {
8
+ const document = parseYamlToDocument(
9
+ outdent`
10
+ openapi: 3.0.0
11
+ paths:
12
+ '/test':
13
+ get:
14
+ summary: Gets a specific pet
15
+ parameters:
16
+ - name: petId
17
+ in: path
18
+ responses:
19
+ 200:
20
+ description: Ok
21
+ `,
22
+ 'foobar.yaml',
23
+ );
24
+
25
+ const results = await lintDocument({
26
+ externalRefResolver: new BaseResolver(),
27
+ document,
28
+ config: makeConfig({ 'spec': 'error' }),
29
+ });
30
+
31
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
32
+ Array [
33
+ Object {
34
+ "location": Array [
35
+ Object {
36
+ "pointer": "#/",
37
+ "reportOnKey": true,
38
+ "source": "foobar.yaml",
39
+ },
40
+ ],
41
+ "message": "The field \`info\` must be present on this level.",
42
+ "ruleId": "spec",
43
+ "severity": "error",
44
+ "suggest": Array [],
45
+ },
46
+ Object {
47
+ "location": Array [
48
+ Object {
49
+ "pointer": "#/paths/~1test/get/parameters/0",
50
+ "reportOnKey": true,
51
+ "source": "foobar.yaml",
52
+ },
53
+ ],
54
+ "message": "Must contain at least one of the following fields: schema, content.",
55
+ "ruleId": "spec",
56
+ "severity": "error",
57
+ "suggest": Array [],
58
+ },
59
+ ]
60
+ `);
61
+ });
62
+ });
@@ -63,7 +63,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
63
63
  }
64
64
  if (!hasProperty)
65
65
  report({
66
- message: 'Must contain at least one of the following fields: path, components, webhooks.',
66
+ message: `Must contain at least one of the following fields: ${type.requiredOneOf?.join(', ')}.`,
67
67
  location: [{ reportOnKey: true }],
68
68
  });
69
69
  }
@@ -192,8 +192,8 @@ describe('Oas3 Structural visitor basic', () => {
192
192
  Object {
193
193
  "location": Array [
194
194
  Object {
195
- "pointer": "#/",
196
- "reportOnKey": false,
195
+ "pointer": "#/openapi",
196
+ "reportOnKey": true,
197
197
  "source": "foobar.yaml",
198
198
  },
199
199
  ],
@@ -259,8 +259,8 @@ describe('Oas3 Structural visitor basic', () => {
259
259
  Object {
260
260
  "location": Array [
261
261
  Object {
262
- "pointer": "#/",
263
- "reportOnKey": false,
262
+ "pointer": "#/openapi",
263
+ "reportOnKey": true,
264
264
  "source": "foobar.yaml",
265
265
  },
266
266
  ],
@@ -3,9 +3,10 @@ import { Oas3Rule } from '../../visitors';
3
3
  export const NoEmptyServers: Oas3Rule = () => {
4
4
  return {
5
5
  DefinitionRoot(root, { report, location }) {
6
- if (!root.servers) {
6
+ if (!root.hasOwnProperty('servers')) {
7
7
  report({
8
8
  message: 'Servers must be present.',
9
+ location: location.child(['openapi']).key()
9
10
  });
10
11
  return;
11
12
  }
package/src/types/oas2.ts CHANGED
@@ -60,6 +60,8 @@ const PathMap: NodeType = {
60
60
  const PathItem: NodeType = {
61
61
  properties: {
62
62
  $ref: { type: 'string' }, // TODO: verify special $ref handling for Path Item
63
+ parameters: listOf('Parameter'),
64
+
63
65
  get: 'Operation',
64
66
  put: 'Operation',
65
67
  post: 'Operation',
@@ -67,7 +69,6 @@ const PathItem: NodeType = {
67
69
  options: 'Operation',
68
70
  head: 'Operation',
69
71
  patch: 'Operation',
70
- parameters: listOf('Parameter'),
71
72
  },
72
73
  };
73
74
 
package/src/types/oas3.ts CHANGED
@@ -136,6 +136,7 @@ const Parameter: NodeType = {
136
136
  content: 'MediaTypeMap',
137
137
  },
138
138
  required: ['name', 'in'],
139
+ requiredOneOf: ['schema', 'content'],
139
140
  };
140
141
 
141
142
  const Callback = {
package/src/utils.ts CHANGED
@@ -28,7 +28,6 @@ export type BundleOutputFormat = 'json' | 'yml' | 'yaml';
28
28
 
29
29
  export async function loadYaml(filename: string) {
30
30
  const contents = await fs.promises.readFile(filename, 'utf-8');
31
-
32
31
  return parseYaml(contents);
33
32
  }
34
33