@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.
- package/__tests__/codeframes.test.ts +22 -0
- package/__tests__/utils.ts +2 -2
- package/__tests__/walk.test.ts +84 -0
- package/lib/bundle.js +1 -0
- package/lib/config/config.d.ts +46 -4
- package/lib/config/config.js +63 -5
- package/lib/config/load.d.ts +5 -2
- package/lib/config/load.js +34 -22
- package/lib/format/codeframes.js +5 -2
- package/lib/index.d.ts +4 -4
- package/lib/index.js +7 -1
- package/lib/redocly/index.js +11 -10
- package/lib/rules/common/spec.js +4 -4
- package/lib/rules/oas3/no-empty-servers.js +2 -1
- package/lib/types/oas2.js +1 -1
- package/lib/types/oas3.js +1 -0
- package/package.json +1 -1
- package/src/bundle.ts +1 -0
- package/src/config/__tests__/load.test.ts +65 -24
- package/src/config/config.ts +98 -13
- package/src/config/load.ts +29 -22
- package/src/format/codeframes.ts +4 -2
- package/src/index.ts +14 -4
- package/src/redocly/__tests__/redocly-client.test.ts +1 -1
- package/src/redocly/index.ts +15 -9
- package/src/rules/common/__tests__/spec.test.ts +62 -0
- package/src/rules/common/spec.ts +1 -1
- package/src/rules/oas3/__tests__/spec/spec.test.ts +4 -4
- package/src/rules/oas3/no-empty-servers.ts +2 -1
- package/src/types/oas2.ts +2 -1
- package/src/types/oas3.ts +1 -0
- package/src/utils.ts +0 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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
|
|
7
|
-
(
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
25
|
-
(
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
});
|
package/src/config/config.ts
CHANGED
|
@@ -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: '
|
|
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
|
|
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
|
-
|
|
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.
|
|
423
|
+
this.apis = rawConfig.apis || {};
|
|
409
424
|
this.lint = new LintConfig(rawConfig.lint || {}, configFile);
|
|
410
|
-
this.
|
|
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
|
+
}
|
package/src/config/load.ts
CHANGED
|
@@ -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
|
|
9
|
-
|
|
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.
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
}
|
package/src/format/codeframes.ts
CHANGED
|
@@ -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
|
|
187
|
-
currentNode = mapping
|
|
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 {
|
|
25
|
-
|
|
26
|
-
|
|
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 = '
|
|
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';
|
package/src/redocly/index.ts
CHANGED
|
@@ -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
|
|
180
|
+
const legacyDomain = domain === 'redocly.com' ? 'redoc.ly' : domain;
|
|
176
181
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
});
|
package/src/rules/common/spec.ts
CHANGED
|
@@ -63,7 +63,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
|
|
63
63
|
}
|
|
64
64
|
if (!hasProperty)
|
|
65
65
|
report({
|
|
66
|
-
message:
|
|
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":
|
|
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":
|
|
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