@redocly/openapi-core 1.0.0-beta.87 → 1.0.0-beta.90

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.
@@ -46,7 +46,7 @@ describe('lint', () => {
46
46
  it('lintConfig should work', async () => {
47
47
  const document = parseYamlToDocument(
48
48
  outdent`
49
- apiDefinitions: error string
49
+ apis: error string
50
50
  lint:
51
51
  plugins:
52
52
  - './local-plugin.js'
@@ -58,7 +58,7 @@ describe('lint', () => {
58
58
  no-invalid-media-type-examples: error
59
59
  path-http-verbs-order: error
60
60
  boolean-parameter-prefixes: off
61
- referenceDocs:
61
+ features.openapi:
62
62
  showConsole: true
63
63
  layout:
64
64
  scope: section
@@ -78,12 +78,12 @@ describe('lint', () => {
78
78
  Object {
79
79
  "location": Array [
80
80
  Object {
81
- "pointer": "#/apiDefinitions",
81
+ "pointer": "#/apis",
82
82
  "reportOnKey": false,
83
83
  "source": "",
84
84
  },
85
85
  ],
86
- "message": "Expected type \`object\` but got \`string\`.",
86
+ "message": "Expected type \`ConfigApis\` (object) but got \`string\`",
87
87
  "ruleId": "spec",
88
88
  "severity": "error",
89
89
  "suggest": Array [],
@@ -91,7 +91,7 @@ describe('lint', () => {
91
91
  Object {
92
92
  "location": Array [
93
93
  Object {
94
- "pointer": "#/referenceDocs/layout",
94
+ "pointer": "#/features.openapi/layout",
95
95
  "reportOnKey": false,
96
96
  "source": "",
97
97
  },
@@ -105,6 +105,40 @@ describe('lint', () => {
105
105
  `);
106
106
  });
107
107
 
108
+ it("'plugins' shouldn't be allowed in 'apis' -> 'lint' field", async () => {
109
+ const document = parseYamlToDocument(
110
+ outdent`
111
+ apis:
112
+ lint:
113
+ plugins:
114
+ - './local-plugin.js'
115
+ lint:
116
+ plugins:
117
+ - './local-plugin.js'
118
+ `,
119
+ '',
120
+ );
121
+ const results = await lintConfig({ document });
122
+
123
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
124
+ Array [
125
+ Object {
126
+ "location": Array [
127
+ Object {
128
+ "pointer": "#/apis/lint/plugins",
129
+ "reportOnKey": true,
130
+ "source": "",
131
+ },
132
+ ],
133
+ "message": "Property \`plugins\` is not expected here.",
134
+ "ruleId": "spec",
135
+ "severity": "error",
136
+ "suggest": Array [],
137
+ },
138
+ ]
139
+ `);
140
+ });
141
+
108
142
  it("'const' can have any type", async () => {
109
143
  const document = parseYamlToDocument(
110
144
  outdent`
@@ -140,7 +174,7 @@ describe('lint', () => {
140
174
  const results = await lintDocument({
141
175
  externalRefResolver: new BaseResolver(),
142
176
  document,
143
- config: makeConfig({ spec: 'error', }),
177
+ config: makeConfig({ spec: 'error' }),
144
178
  });
145
179
 
146
180
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
@@ -2,6 +2,7 @@ import { loadConfig, findConfig } from '../load';
2
2
  import { RedoclyClient } from '../../redocly';
3
3
 
4
4
  const fs = require('fs');
5
+ const path = require('path');
5
6
 
6
7
  describe('loadConfig', () => {
7
8
  it('should resolve config http header by US region', async () => {
@@ -66,4 +67,10 @@ describe('findConfig', () => {
66
67
  Please use 'redocly.yaml' instead.
67
68
  `);
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');
75
+ });
69
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,
@@ -14,11 +12,9 @@ import {
14
12
  Oas2RuleSet,
15
13
  Oas2PreprocessorsSet,
16
14
  Oas2DecoratorsSet,
17
- Oas3RuleSet
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
 
@@ -124,13 +120,12 @@ export type ResolveConfig = {
124
120
 
125
121
  export const DEFAULT_REGION = 'us';
126
122
  export type Region = 'us' | 'eu';
127
- export type AccessTokens = {[region in Region]?: string };
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
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?: Record<string, any>;
145
+ };
146
+
147
+ export type Api = {
148
+ root: string;
149
+ lint?: Omit<LintRawConfig, 'plugins'>;
150
+ 'features.openapi'?: Record<string, any>;
151
+ 'features.mockServer'?: Record<string, any>;
152
+ };
153
+
154
+ export type RawConfig = {
155
+ apis?: Record<string, Api>;
156
+ lint?: LintRawConfig;
157
+ resolve?: RawResolveConfig;
158
+ region?: Region;
159
+ 'features.openapi'?: Record<string, any>;
160
+ 'features.mockServer'?: Record<string, any>;
161
+ organization?: string;
149
162
  };
150
163
 
151
164
  export class LintConfig {
@@ -201,7 +214,9 @@ export class LintConfig {
201
214
  [OasVersion.Version3_1]: { ...merged.decorators, ...merged.oas3_1Decorators },
202
215
  };
203
216
 
204
- const dir = this.configFile ? path.dirname(this.configFile) : (typeof process !== 'undefined' && process.cwd() || '');
217
+ const dir = this.configFile
218
+ ? path.dirname(this.configFile)
219
+ : (typeof process !== 'undefined' && process.cwd()) || '';
205
220
  const ignoreFile = path.join(dir, IGNORE_FILE);
206
221
 
207
222
  /* no crash when using it on the client */
@@ -229,7 +244,8 @@ export class LintConfig {
229
244
  const ignoreFile = path.join(dir, IGNORE_FILE);
230
245
  const mapped: Record<string, any> = {};
231
246
  for (const absFileName of Object.keys(this.ignore)) {
232
- const ignoredRules = (mapped[slash(path.relative(dir, absFileName))] = this.ignore[absFileName]);
247
+ const ignoredRules = (mapped[slash(path.relative(dir, absFileName))] =
248
+ this.ignore[absFileName]);
233
249
  for (const ruleId of Object.keys(ignoredRules)) {
234
250
  ignoredRules[ruleId] = Array.from(ignoredRules[ruleId]) as any;
235
251
  }
@@ -398,16 +414,19 @@ export class LintConfig {
398
414
  }
399
415
 
400
416
  export class Config {
401
- referenceDocs: any;
402
- apiDefinitions: Record<string, string>;
417
+ apis: Record<string, Api>;
403
418
  lint: LintConfig;
404
419
  resolve: ResolveConfig;
405
420
  licenseKey?: string;
406
421
  region?: Region;
422
+ 'features.openapi': Record<string, any>;
423
+ 'features.mockServer'?: Record<string, any>;
424
+ organization?: string;
407
425
  constructor(public rawConfig: RawConfig, public configFile?: string) {
408
- this.apiDefinitions = rawConfig.apiDefinitions || {};
426
+ this.apis = rawConfig.apis || {};
409
427
  this.lint = new LintConfig(rawConfig.lint || {}, configFile);
410
- this.referenceDocs = rawConfig.referenceDocs || {};
428
+ this['features.openapi'] = rawConfig['features.openapi'] || {};
429
+ this['features.mockServer'] = rawConfig['features.mockServer'] || {};
411
430
  this.resolve = {
412
431
  http: {
413
432
  headers: rawConfig?.resolve?.http?.headers ?? [],
@@ -415,13 +434,13 @@ export class Config {
415
434
  },
416
435
  };
417
436
  this.region = rawConfig.region;
437
+ this.organization = rawConfig.organization;
418
438
  }
419
439
  }
420
440
 
421
441
  function resolvePresets(presets: string[], plugins: Plugin[]) {
422
442
  return presets.map((presetName) => {
423
443
  const { pluginId, configName } = parsePresetName(presetName);
424
-
425
444
  const plugin = plugins.find((p) => p.id === pluginId);
426
445
  if (!plugin) {
427
446
  throw new Error(`Invalid config ${red(presetName)}: plugin ${pluginId} is not included.`);
@@ -624,3 +643,72 @@ function assignExisting<T>(target: Record<string, T>, obj: Record<string, T>) {
624
643
  }
625
644
  }
626
645
  }
646
+
647
+ export function getMergedConfig(config: Config, entrypointAlias?: string): Config {
648
+ return entrypointAlias
649
+ ? new Config({
650
+ ...config.rawConfig,
651
+ lint: getMergedLintConfig(config, entrypointAlias),
652
+ 'features.openapi': {
653
+ ...config['features.openapi'],
654
+ ...config.apis[entrypointAlias]?.['features.openapi'],
655
+ },
656
+ 'features.mockServer': {
657
+ ...config['features.mockServer'],
658
+ ...config.apis[entrypointAlias]?.['features.mockServer'],
659
+ },
660
+ // TODO: merge everything else here
661
+ })
662
+ : config;
663
+ }
664
+
665
+ export function getMergedLintConfig(config: Config, entrypointAlias?: string) {
666
+ const apiLint = entrypointAlias ? config.apis[entrypointAlias]?.lint : {};
667
+ const mergedLint = {
668
+ ...config.rawConfig.lint,
669
+ ...apiLint,
670
+ rules: { ...config.rawConfig.lint?.rules, ...apiLint?.rules },
671
+ preprocessors: { ...config.rawConfig.lint?.preprocessors, ...apiLint?.preprocessors },
672
+ decorators: { ...config.rawConfig.lint?.decorators, ...apiLint?.decorators },
673
+ };
674
+ return mergedLint;
675
+ }
676
+
677
+ function transformApiDefinitionsToApis(
678
+ apiDefinitions: Record<string, string> = {},
679
+ ): 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 (
692
+ (rawConfig as RawConfig)['features.openapi'] &&
693
+ (rawConfig as DeprecatedRawConfig).referenceDocs
694
+ ) {
695
+ throw new Error("Do not use 'referenceDocs' field. Use 'features.openapi' instead.\n");
696
+ }
697
+ const { apiDefinitions, referenceDocs, ...rest } = rawConfig as DeprecatedRawConfig & RawConfig;
698
+ // TODO: put links to the changelog and uncomment this after successful release of ReferenceDocs/Redoc, Portal and Workflows
699
+ // if (apiDefinitions) {
700
+ // process.stderr.write(
701
+ // `The ${yellow('apiDefinitions')} field is deprecated. Use ${green('apis')} instead, see changelog: <link>\n`
702
+ // );
703
+ // }
704
+ // if (referenceDocs) {
705
+ // process.stderr.write(
706
+ // `The ${yellow('referenceDocs')} field is deprecated. Use ${green('features.openapi')} instead, see changelog: <link>\n`
707
+ // );
708
+ // }
709
+ return {
710
+ 'features.openapi': referenceDocs,
711
+ apis: transformApiDefinitionsToApis(apiDefinitions),
712
+ ...rest,
713
+ };
714
+ }
@@ -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;
@@ -48,7 +38,6 @@ export async function loadConfig(configPath?: string, customExtends?: string[]):
48
38
  }] : []));
49
39
  }
50
40
  }
51
-
52
41
  return new Config(
53
42
  {
54
43
  ...rawConfig,
@@ -63,12 +52,11 @@ export async function loadConfig(configPath?: string, customExtends?: string[]):
63
52
 
64
53
  export const CONFIG_FILE_NAMES = ['redocly.yaml', 'redocly.yml', '.redocly.yaml', '.redocly.yml'];
65
54
 
66
- export function findConfig(): string | undefined {
55
+ export function findConfig(dir?: string): string | undefined {
67
56
  if (!fs.hasOwnProperty('existsSync')) return;
68
-
69
- const existingConfigFiles = CONFIG_FILE_NAMES.map((name) => fs.existsSync(name) && name).filter(
70
- Boolean,
71
- ) as Array<string | never>;
57
+ const existingConfigFiles = CONFIG_FILE_NAMES
58
+ .map(name => dir ? path.resolve(dir, name) : name)
59
+ .filter(fs.existsSync);
72
60
  if (existingConfigFiles.length > 1) {
73
61
  throw new Error(`
74
62
  Multiple configuration files are not allowed.
@@ -78,3 +66,13 @@ export function findConfig(): string | undefined {
78
66
  }
79
67
  return existingConfigFiles[0];
80
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}`);
77
+ }
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,24 @@ 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';
24
+ export {
25
+ Config,
26
+ LintConfig,
27
+ RawConfig,
28
+ IGNORE_FILE,
29
+ Region,
30
+ getMergedConfig,
31
+ transformConfig,
32
+ } from './config/config';
25
33
 
26
- export { loadConfig, findConfig, CONFIG_FILE_NAMES } from './config/load';
34
+ export { loadConfig, getConfig, findConfig, CONFIG_FILE_NAMES } from './config/load';
27
35
  export { RedoclyClient, isRedoclyRegistryURL } from './redocly';
28
36
 
29
37
  export {
@@ -19,9 +19,7 @@ export class RedoclyClient {
19
19
  constructor(region?: Region) {
20
20
  this.region = this.loadRegion(region);
21
21
  this.loadTokens();
22
- this.domain = region
23
- ? DOMAINS[region]
24
- : process.env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
22
+ this.domain = region ? DOMAINS[region] : process.env.REDOCLY_DOMAIN || DOMAINS[DEFAULT_REGION];
25
23
 
26
24
  /*
27
25
  * We can't use process.env here because it is replaced by a const in some client-side bundles,
@@ -34,7 +32,11 @@ export class RedoclyClient {
34
32
  loadRegion(region?: Region) {
35
33
  if (region && !DOMAINS[region]) {
36
34
  process.stdout.write(
37
- red(`Invalid argument: region in config file.\nGiven: ${green(region)}, choices: "us", "eu".\n`),
35
+ red(
36
+ `Invalid argument: region in config file.\nGiven: ${green(
37
+ region,
38
+ )}, choices: "us", "eu".\n`,
39
+ ),
38
40
  );
39
41
  process.exit(1);
40
42
  }
@@ -86,37 +88,36 @@ export class RedoclyClient {
86
88
  if (isNotEmptyObject(credentials)) {
87
89
  this.setAccessTokens({
88
90
  ...credentials,
89
- ...(credentials.token && !credentials[this.region] && {
90
- [this.region]: credentials.token
91
- })
92
- })
91
+ ...(credentials.token &&
92
+ !credentials[this.region] && {
93
+ [this.region]: credentials.token,
94
+ }),
95
+ });
93
96
  }
94
97
  if (process.env.REDOCLY_AUTHORIZATION) {
95
98
  this.setAccessTokens({
96
99
  ...this.accessTokens,
97
- [this.region]: process.env.REDOCLY_AUTHORIZATION
98
- })
100
+ [this.region]: process.env.REDOCLY_AUTHORIZATION,
101
+ });
99
102
  }
100
103
  }
101
104
 
102
- getAllTokens (): RegionalToken[] {
103
- return (<[Region, string][]>Object.entries(this.accessTokens)).filter(
104
- ([region]) => AVAILABLE_REGIONS.includes(region)
105
- ).map(
106
- ([region, token]) => ({ region, token })
107
- );
105
+ getAllTokens(): RegionalToken[] {
106
+ return (<[Region, string][]>Object.entries(this.accessTokens))
107
+ .filter(([region]) => AVAILABLE_REGIONS.includes(region))
108
+ .map(([region, token]) => ({ region, token }));
108
109
  }
109
110
 
110
111
  async getValidTokens(): Promise<RegionalTokenWithValidity[]> {
111
- const validTokens = <RegionalTokenWithValidity[]>[];
112
+ const allTokens = this.getAllTokens();
112
113
 
113
- for (const { token, region } of this.getAllTokens()) {
114
- if (await this.verifyToken(token, region)) {
115
- validTokens.push({ token, region, valid: true });
116
- }
117
- }
118
-
119
- return validTokens;
114
+ const verifiedTokens = await Promise.allSettled(
115
+ allTokens.map(({ token, region }) => this.verifyToken(token, region)),
116
+ );
117
+
118
+ return allTokens
119
+ .filter((_, index) => verifiedTokens[index].status === 'fulfilled')
120
+ .map(({ token, region }) => ({ token, region, valid: true }));
120
121
  }
121
122
 
122
123
  async getTokens() {
@@ -124,9 +125,23 @@ export class RedoclyClient {
124
125
  }
125
126
 
126
127
  async isAuthorizedWithRedoclyByRegion(): Promise<boolean> {
127
- if (!this.hasTokens()) return false;
128
+ if (!this.hasTokens()) {
129
+ return false;
130
+ }
131
+
128
132
  const accessToken = this.accessTokens[this.region];
129
- return !!accessToken && await this.verifyToken(accessToken, this.region);
133
+
134
+ if (!accessToken) {
135
+ return false;
136
+ }
137
+
138
+ try {
139
+ await this.verifyToken(accessToken, this.region);
140
+
141
+ return true;
142
+ } catch (err) {
143
+ return false;
144
+ }
130
145
  }
131
146
 
132
147
  async isAuthorizedWithRedocly(): Promise<boolean> {
@@ -137,8 +152,11 @@ export class RedoclyClient {
137
152
  return existsSync(credentialsPath) ? JSON.parse(readFileSync(credentialsPath, 'utf-8')) : {};
138
153
  }
139
154
 
140
- async verifyToken(accessToken: string, region: Region, verbose: boolean = false): Promise<boolean> {
141
- if (!accessToken) return false;
155
+ async verifyToken(
156
+ accessToken: string,
157
+ region: Region,
158
+ verbose: boolean = false,
159
+ ): Promise<{ viewerId: string; organizations: string[] }> {
142
160
  return this.registryApi.authStatus(accessToken, region, verbose);
143
161
  }
144
162
 
@@ -146,8 +164,9 @@ export class RedoclyClient {
146
164
  const credentialsPath = resolve(homedir(), TOKEN_FILENAME);
147
165
  process.stdout.write(gray('\n Logging in...\n'));
148
166
 
149
- const authorized = await this.verifyToken(accessToken, this.region, verbose);
150
- if (!authorized) {
167
+ try {
168
+ await this.verifyToken(accessToken, this.region, verbose);
169
+ } catch (err) {
151
170
  process.stdout.write(
152
171
  red('Authorization failed. Please check if you entered a valid API key.\n'),
153
172
  );
@@ -22,28 +22,43 @@ export class RegistryApi {
22
22
 
23
23
  private async request(path = '', options: RequestInit = {}, region?: Region) {
24
24
  const headers = Object.assign({}, options.headers || {}, { 'x-redocly-cli-version': version });
25
- if (!headers.hasOwnProperty('authorization')) { throw new Error('Unauthorized'); }
25
+
26
+ if (!headers.hasOwnProperty('authorization')) {
27
+ throw new Error('Unauthorized');
28
+ }
29
+
26
30
  const response = await fetch(
27
31
  `${this.getBaseUrl(region)}${path}`,
28
32
  Object.assign({}, options, { headers }),
29
33
  );
30
- if (response.status === 401) { throw new Error('Unauthorized'); }
34
+
35
+ if (response.status === 401) {
36
+ throw new Error('Unauthorized');
37
+ }
38
+
31
39
  if (response.status === 404) {
32
40
  const body: RegistryApiTypes.NotFoundProblemResponse = await response.json();
33
41
  throw new Error(body.code);
34
42
  }
43
+
35
44
  return response;
36
45
  }
37
46
 
38
- async authStatus(accessToken: string, region: Region, verbose = false) {
47
+ async authStatus(
48
+ accessToken: string,
49
+ region: Region,
50
+ verbose = false,
51
+ ): Promise<{ viewerId: string; organizations: string[] }> {
39
52
  try {
40
- const response = await this.request('', { headers: { authorization: accessToken }}, region);
41
- return response.ok;
53
+ const response = await this.request('', { headers: { authorization: accessToken } }, region);
54
+
55
+ return await response.json();
42
56
  } catch (error) {
43
57
  if (verbose) {
44
58
  console.log(error);
45
59
  }
46
- return false;
60
+
61
+ throw error;
47
62
  }
48
63
  }
49
64
 
@@ -69,7 +84,7 @@ export class RegistryApi {
69
84
  isUpsert,
70
85
  }),
71
86
  },
72
- this.region
87
+ this.region,
73
88
  );
74
89
 
75
90
  if (response.ok) {
@@ -88,20 +103,22 @@ export class RegistryApi {
88
103
  branch,
89
104
  isUpsert,
90
105
  }: RegistryApiTypes.PushApiParams) {
91
- const response = await this.request(`/${organizationId}/${name}/${version}`, {
92
- method: 'PUT',
93
- headers: {
94
- 'content-type': 'application/json',
95
- authorization: this.accessToken
96
- } as HeadersInit,
97
- body: JSON.stringify({
98
- rootFilePath,
99
- filePaths,
100
- branch,
101
- isUpsert,
102
- }),
103
- },
104
- this.region
106
+ const response = await this.request(
107
+ `/${organizationId}/${name}/${version}`,
108
+ {
109
+ method: 'PUT',
110
+ headers: {
111
+ 'content-type': 'application/json',
112
+ authorization: this.accessToken,
113
+ } as HeadersInit,
114
+ body: JSON.stringify({
115
+ rootFilePath,
116
+ filePaths,
117
+ branch,
118
+ isUpsert,
119
+ }),
120
+ },
121
+ this.region,
105
122
  );
106
123
 
107
124
  if (response.ok) {
@@ -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
  }