@redocly/openapi-core 1.0.0-beta.108 → 1.0.0-beta.110
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/README.md +2 -2
- package/lib/benchmark/benches/resolve-with-no-external.bench.js +1 -1
- package/lib/bundle.d.ts +1 -1
- package/lib/bundle.js +4 -4
- package/lib/config/all.js +3 -1
- package/lib/config/config-resolvers.js +22 -4
- package/lib/config/config.d.ts +1 -0
- package/lib/config/config.js +1 -0
- package/lib/config/load.d.ts +8 -2
- package/lib/config/load.js +4 -2
- package/lib/config/minimal.js +3 -1
- package/lib/config/recommended.js +3 -1
- package/lib/config/rules.js +1 -1
- package/lib/config/types.d.ts +17 -0
- package/lib/config/utils.d.ts +2 -2
- package/lib/config/utils.js +44 -6
- package/lib/decorators/common/registry-dependencies.js +1 -1
- package/lib/format/format.d.ts +1 -1
- package/lib/format/format.js +22 -1
- package/lib/lint.js +2 -2
- package/lib/redocly/registry-api.d.ts +0 -1
- package/lib/redocly/registry-api.js +5 -4
- package/lib/resolve.js +3 -1
- package/lib/rules/ajv.d.ts +1 -1
- package/lib/rules/ajv.js +5 -5
- package/lib/rules/common/assertions/asserts.d.ts +3 -5
- package/lib/rules/common/assertions/asserts.js +137 -97
- package/lib/rules/common/assertions/index.js +2 -6
- package/lib/rules/common/assertions/utils.d.ts +12 -6
- package/lib/rules/common/assertions/utils.js +33 -20
- package/lib/rules/common/no-ambiguous-paths.js +1 -1
- package/lib/rules/common/no-identical-paths.js +1 -1
- package/lib/rules/common/operation-2xx-response.js +1 -1
- package/lib/rules/common/operation-4xx-response.js +1 -1
- package/lib/rules/common/operation-operationId.js +1 -1
- package/lib/rules/common/operation-tag-defined.js +1 -1
- package/lib/rules/common/path-not-include-query.js +1 -1
- package/lib/rules/common/security-defined.d.ts +2 -0
- package/lib/rules/common/{operation-security-defined.js → security-defined.js} +18 -4
- package/lib/rules/common/spec.js +12 -1
- package/lib/rules/common/tags-alphabetical.js +1 -1
- package/lib/rules/oas2/index.d.ts +1 -1
- package/lib/rules/oas2/index.js +2 -2
- package/lib/rules/oas2/remove-unused-components.js +1 -1
- package/lib/rules/oas2/request-mime-type.js +1 -1
- package/lib/rules/oas2/response-mime-type.js +1 -1
- package/lib/rules/oas3/index.js +6 -2
- package/lib/rules/oas3/no-empty-servers.js +1 -1
- package/lib/rules/oas3/no-server-variables-empty-enum.js +1 -1
- package/lib/rules/oas3/no-unused-components.js +1 -1
- package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.d.ts +5 -0
- package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.js +36 -0
- package/lib/rules/oas3/remove-unused-components.js +1 -1
- package/lib/rules/oas3/request-mime-type.js +1 -1
- package/lib/rules/oas3/response-mime-type.js +1 -1
- package/lib/rules/oas3/spec-components-invalid-map-name.d.ts +2 -0
- package/lib/rules/oas3/spec-components-invalid-map-name.js +46 -0
- package/lib/rules/other/stats.d.ts +2 -2
- package/lib/rules/other/stats.js +2 -2
- package/lib/rules/utils.js +1 -1
- package/lib/types/oas2.js +5 -5
- package/lib/types/oas3.js +27 -20
- package/lib/types/oas3_1.js +3 -3
- package/lib/types/redocly-yaml.js +60 -54
- package/lib/utils.d.ts +3 -3
- package/lib/utils.js +5 -5
- package/lib/visitors.d.ts +11 -11
- package/lib/visitors.js +13 -1
- package/package.json +3 -5
- package/src/__tests__/__snapshots__/bundle.test.ts.snap +3 -3
- package/src/__tests__/fixtures/extension.js +3 -3
- package/src/__tests__/format.test.ts +76 -0
- package/src/__tests__/lint.test.ts +184 -121
- package/src/__tests__/resolve-http.test.ts +1 -1
- package/src/__tests__/resolve.test.ts +9 -9
- package/src/__tests__/walk.test.ts +78 -10
- package/src/benchmark/benches/resolve-with-no-external.bench.ts +1 -1
- package/src/bundle.ts +4 -4
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +6 -2
- package/src/config/__tests__/config-resolvers.test.ts +37 -1
- package/src/config/__tests__/config.test.ts +5 -0
- package/src/config/__tests__/fixtures/plugin-config.yaml +2 -3
- package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +11 -12
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +7 -8
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +16 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +18 -19
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-wrong-custom-function.yaml +16 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +9 -10
- package/src/config/__tests__/fixtures/resolve-config/plugin.js +11 -0
- package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +3 -4
- package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +4 -5
- package/src/config/__tests__/load.test.ts +13 -16
- package/src/config/__tests__/resolve-plugins.test.ts +3 -3
- package/src/config/__tests__/utils.test.ts +64 -4
- package/src/config/all.ts +3 -1
- package/src/config/config-resolvers.ts +30 -7
- package/src/config/config.ts +2 -0
- package/src/config/load.ts +13 -6
- package/src/config/minimal.ts +3 -1
- package/src/config/recommended.ts +3 -1
- package/src/config/rules.ts +2 -2
- package/src/config/types.ts +24 -0
- package/src/config/utils.ts +103 -13
- package/src/decorators/common/registry-dependencies.ts +1 -1
- package/src/format/format.ts +32 -2
- package/src/lint.ts +2 -2
- package/src/redocly/registry-api.ts +5 -4
- package/src/resolve.ts +3 -1
- package/src/rules/__tests__/utils.test.ts +1 -1
- package/src/rules/ajv.ts +4 -4
- package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +1 -0
- package/src/rules/common/__tests__/operation-2xx-response.test.ts +1 -1
- package/src/rules/common/__tests__/operation-4xx-response.test.ts +26 -3
- package/src/rules/common/__tests__/security-defined.test.ts +175 -0
- package/src/rules/common/__tests__/spec.test.ts +79 -0
- package/src/rules/common/assertions/__tests__/asserts.test.ts +491 -428
- package/src/rules/common/assertions/__tests__/utils.test.ts +2 -2
- package/src/rules/common/assertions/asserts.ts +155 -97
- package/src/rules/common/assertions/index.ts +2 -11
- package/src/rules/common/assertions/utils.ts +66 -36
- package/src/rules/common/no-ambiguous-paths.ts +1 -1
- package/src/rules/common/no-identical-paths.ts +1 -1
- package/src/rules/common/operation-2xx-response.ts +1 -1
- package/src/rules/common/operation-4xx-response.ts +1 -1
- package/src/rules/common/operation-operationId.ts +1 -1
- package/src/rules/common/operation-tag-defined.ts +1 -1
- package/src/rules/common/path-not-include-query.ts +1 -1
- package/src/rules/common/{operation-security-defined.ts → security-defined.ts} +19 -4
- package/src/rules/common/spec.ts +15 -1
- package/src/rules/common/tags-alphabetical.ts +1 -1
- package/src/rules/oas2/index.ts +2 -2
- package/src/rules/oas2/remove-unused-components.ts +1 -1
- package/src/rules/oas2/request-mime-type.ts +1 -1
- package/src/rules/oas2/response-mime-type.ts +1 -1
- package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +51 -2
- package/src/rules/oas3/__tests__/operation-4xx-problem-details-rfc7807.test.ts +145 -0
- package/src/rules/oas3/__tests__/spec/spec.test.ts +10 -0
- package/src/rules/oas3/__tests__/spec-components-invalid-map-name.test.ts +217 -0
- package/src/rules/oas3/index.ts +6 -2
- package/src/rules/oas3/no-empty-servers.ts +1 -1
- package/src/rules/oas3/no-server-variables-empty-enum.ts +1 -1
- package/src/rules/oas3/no-unused-components.ts +1 -1
- package/src/rules/oas3/operation-4xx-problem-details-rfc7807.ts +36 -0
- package/src/rules/oas3/remove-unused-components.ts +1 -1
- package/src/rules/oas3/request-mime-type.ts +1 -1
- package/src/rules/oas3/response-mime-type.ts +1 -1
- package/src/rules/oas3/spec-components-invalid-map-name.ts +53 -0
- package/src/rules/other/stats.ts +2 -2
- package/src/rules/utils.ts +2 -1
- package/src/types/index.ts +2 -2
- package/src/types/oas2.ts +5 -5
- package/src/types/oas3.ts +27 -20
- package/src/types/oas3_1.ts +3 -3
- package/src/types/redocly-yaml.ts +66 -38
- package/src/utils.ts +11 -7
- package/src/visitors.ts +29 -13
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/rules/common/operation-security-defined.d.ts +0 -2
- package/src/rules/common/__tests__/operation-security-defined.test.ts +0 -69
package/src/config/config.ts
CHANGED
|
@@ -306,6 +306,7 @@ export class Config {
|
|
|
306
306
|
'features.openapi': Record<string, any>;
|
|
307
307
|
'features.mockServer'?: Record<string, any>;
|
|
308
308
|
organization?: string;
|
|
309
|
+
files: string[];
|
|
309
310
|
constructor(public rawConfig: ResolvedConfig, public configFile?: string) {
|
|
310
311
|
this.apis = rawConfig.apis || {};
|
|
311
312
|
this.styleguide = new StyleguideConfig(rawConfig.styleguide || {}, configFile);
|
|
@@ -314,5 +315,6 @@ export class Config {
|
|
|
314
315
|
this.resolve = getResolveConfig(rawConfig?.resolve);
|
|
315
316
|
this.region = rawConfig.region;
|
|
316
317
|
this.organization = rawConfig.organization;
|
|
318
|
+
this.files = rawConfig.files || [];
|
|
317
319
|
}
|
|
318
320
|
}
|
package/src/config/load.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { Config, DOMAINS } from './config';
|
|
|
7
7
|
import { transformConfig } from './utils';
|
|
8
8
|
import { resolveConfig } from './config-resolvers';
|
|
9
9
|
|
|
10
|
-
import type { DeprecatedInRawConfig, RawConfig, Region } from './types';
|
|
10
|
+
import type { DeprecatedInRawConfig, FlatRawConfig, RawConfig, Region } from './types';
|
|
11
11
|
import { RegionalTokenWithValidity } from '../redocly/redocly-client-types';
|
|
12
12
|
|
|
13
13
|
async function addConfigMetadata({
|
|
@@ -62,11 +62,17 @@ async function addConfigMetadata({
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export async function loadConfig(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
options: {
|
|
66
|
+
configPath?: string;
|
|
67
|
+
customExtends?: string[];
|
|
68
|
+
processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>;
|
|
69
|
+
files?: string[];
|
|
70
|
+
region?: Region;
|
|
71
|
+
} = {}
|
|
68
72
|
): Promise<Config> {
|
|
69
|
-
const
|
|
73
|
+
const { configPath = findConfig(), customExtends, processRawConfig, files, region } = options;
|
|
74
|
+
const config = await getConfig(configPath);
|
|
75
|
+
const rawConfig = { ...config, files: files ?? config.files, region: region ?? config.region };
|
|
70
76
|
if (typeof processRawConfig === 'function') {
|
|
71
77
|
await processRawConfig(rawConfig);
|
|
72
78
|
}
|
|
@@ -102,7 +108,8 @@ export function findConfig(dir?: string): string | undefined {
|
|
|
102
108
|
export async function getConfig(configPath: string | undefined = findConfig()): Promise<RawConfig> {
|
|
103
109
|
if (!configPath || !doesYamlFileExist(configPath)) return {};
|
|
104
110
|
try {
|
|
105
|
-
const rawConfig =
|
|
111
|
+
const rawConfig =
|
|
112
|
+
(await loadYaml<RawConfig & DeprecatedInRawConfig & FlatRawConfig>(configPath)) || {};
|
|
106
113
|
return transformConfig(rawConfig);
|
|
107
114
|
} catch (e) {
|
|
108
115
|
throw new Error(`Error parsing config file at '${configPath}': ${e.message}`);
|
package/src/config/minimal.ts
CHANGED
|
@@ -24,7 +24,7 @@ export default {
|
|
|
24
24
|
'operation-operationId-unique': 'warn',
|
|
25
25
|
'operation-parameters-unique': 'warn',
|
|
26
26
|
'operation-tag-defined': 'off',
|
|
27
|
-
'
|
|
27
|
+
'security-defined': 'warn',
|
|
28
28
|
'operation-operationId-url-safe': 'warn',
|
|
29
29
|
'operation-singular-tag': 'off',
|
|
30
30
|
'no-unresolved-refs': 'error',
|
|
@@ -45,6 +45,7 @@ export default {
|
|
|
45
45
|
'no-unused-components': 'warn',
|
|
46
46
|
'no-undefined-server-variable': 'warn',
|
|
47
47
|
'no-server-variables-empty-enum': 'error',
|
|
48
|
+
'spec-components-invalid-map-name': 'warn',
|
|
48
49
|
},
|
|
49
50
|
oas3_1Rules: {
|
|
50
51
|
'no-invalid-media-type-examples': 'warn',
|
|
@@ -55,5 +56,6 @@ export default {
|
|
|
55
56
|
'no-unused-components': 'warn',
|
|
56
57
|
'no-undefined-server-variable': 'warn',
|
|
57
58
|
'no-server-variables-empty-enum': 'error',
|
|
59
|
+
'spec-components-invalid-map-name': 'warn',
|
|
58
60
|
},
|
|
59
61
|
} as PluginStyleguideConfig;
|
|
@@ -25,7 +25,7 @@ export default {
|
|
|
25
25
|
'operation-operationId-url-safe': 'error',
|
|
26
26
|
'operation-parameters-unique': 'error',
|
|
27
27
|
'operation-tag-defined': 'off',
|
|
28
|
-
'
|
|
28
|
+
'security-defined': 'error',
|
|
29
29
|
'operation-singular-tag': 'off',
|
|
30
30
|
'no-unresolved-refs': 'error',
|
|
31
31
|
'no-enum-type-mismatch': 'error',
|
|
@@ -45,6 +45,7 @@ export default {
|
|
|
45
45
|
'no-unused-components': 'warn',
|
|
46
46
|
'no-undefined-server-variable': 'error',
|
|
47
47
|
'no-server-variables-empty-enum': 'error',
|
|
48
|
+
'spec-components-invalid-map-name': 'error',
|
|
48
49
|
},
|
|
49
50
|
oas3_1Rules: {
|
|
50
51
|
'no-invalid-media-type-examples': 'warn',
|
|
@@ -55,5 +56,6 @@ export default {
|
|
|
55
56
|
'no-unused-components': 'warn',
|
|
56
57
|
'no-undefined-server-variable': 'error',
|
|
57
58
|
'no-server-variables-empty-enum': 'error',
|
|
59
|
+
'spec-components-invalid-map-name': 'error',
|
|
58
60
|
},
|
|
59
61
|
} as PluginStyleguideConfig;
|
package/src/config/rules.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RuleSet, OasVersion } from '../oas-types';
|
|
2
2
|
import { StyleguideConfig } from './config';
|
|
3
|
-
import {
|
|
3
|
+
import { isDefined } from '../utils';
|
|
4
4
|
|
|
5
5
|
export function initRules<T extends Function, P extends RuleSet<T>>(
|
|
6
6
|
rules: P[],
|
|
@@ -42,5 +42,5 @@ export function initRules<T extends Function, P extends RuleSet<T>>(
|
|
|
42
42
|
})
|
|
43
43
|
)
|
|
44
44
|
.flatMap((visitor) => visitor)
|
|
45
|
-
.filter(
|
|
45
|
+
.filter(isDefined);
|
|
46
46
|
}
|
package/src/config/types.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
OasVersion,
|
|
11
11
|
} from '../oas-types';
|
|
12
12
|
import type { NodeType } from '../types';
|
|
13
|
+
import { Location } from '../ref-utils';
|
|
13
14
|
|
|
14
15
|
export type RuleSeverity = ProblemSeverity | 'off';
|
|
15
16
|
|
|
@@ -83,6 +84,15 @@ export type CustomRulesConfig = {
|
|
|
83
84
|
oas2?: Oas2RuleSet;
|
|
84
85
|
};
|
|
85
86
|
|
|
87
|
+
export type AssertResult = { message?: string; location?: Location };
|
|
88
|
+
export type CustomFunction = (
|
|
89
|
+
value: any,
|
|
90
|
+
options: unknown,
|
|
91
|
+
baseLocation: Location
|
|
92
|
+
) => AssertResult[];
|
|
93
|
+
|
|
94
|
+
export type AssertionsConfig = Record<string, CustomFunction>;
|
|
95
|
+
|
|
86
96
|
export type Plugin = {
|
|
87
97
|
id: string;
|
|
88
98
|
configs?: Record<string, PluginStyleguideConfig>;
|
|
@@ -90,6 +100,7 @@ export type Plugin = {
|
|
|
90
100
|
preprocessors?: PreprocessorsConfig;
|
|
91
101
|
decorators?: DecoratorsConfig;
|
|
92
102
|
typeExtension?: TypeExtensionsConfig;
|
|
103
|
+
assertions?: AssertionsConfig;
|
|
93
104
|
};
|
|
94
105
|
|
|
95
106
|
export type PluginStyleguideConfig = Omit<StyleguideRawConfig, 'plugins' | 'extends'>;
|
|
@@ -110,6 +121,7 @@ export type ResolveHeader =
|
|
|
110
121
|
|
|
111
122
|
export type RawResolveConfig = {
|
|
112
123
|
http?: Partial<HttpResolveConfig>;
|
|
124
|
+
doNotResolveExamples?: boolean;
|
|
113
125
|
};
|
|
114
126
|
|
|
115
127
|
export type HttpResolveConfig = {
|
|
@@ -128,6 +140,7 @@ export type AccessTokens = { [region in Region]?: string };
|
|
|
128
140
|
export type DeprecatedInRawConfig = {
|
|
129
141
|
apiDefinitions?: Record<string, string>;
|
|
130
142
|
lint?: StyleguideRawConfig;
|
|
143
|
+
styleguide?: StyleguideRawConfig;
|
|
131
144
|
referenceDocs?: Record<string, any>;
|
|
132
145
|
apis?: Record<string, Api & DeprecatedInApi>;
|
|
133
146
|
};
|
|
@@ -143,6 +156,7 @@ export type DeprecatedInApi = {
|
|
|
143
156
|
|
|
144
157
|
export type ResolvedApi = Omit<Api, 'styleguide'> & {
|
|
145
158
|
styleguide: ResolvedStyleguideConfig;
|
|
159
|
+
files?: string[];
|
|
146
160
|
};
|
|
147
161
|
|
|
148
162
|
export type RawConfig = {
|
|
@@ -151,8 +165,18 @@ export type RawConfig = {
|
|
|
151
165
|
resolve?: RawResolveConfig;
|
|
152
166
|
region?: Region;
|
|
153
167
|
organization?: string;
|
|
168
|
+
files?: string[];
|
|
154
169
|
} & FeaturesConfig;
|
|
155
170
|
|
|
171
|
+
export type FlatApi = Omit<Api, 'styleguide'> &
|
|
172
|
+
Omit<ApiStyleguideRawConfig, 'doNotResolveExamples'>;
|
|
173
|
+
|
|
174
|
+
export type FlatRawConfig = Omit<RawConfig, 'styleguide' | 'resolve' | 'apis'> &
|
|
175
|
+
Omit<StyleguideRawConfig, 'doNotResolveExamples'> & {
|
|
176
|
+
resolve?: RawResolveConfig;
|
|
177
|
+
apis?: Record<string, FlatApi>;
|
|
178
|
+
};
|
|
179
|
+
|
|
156
180
|
export type ResolvedConfig = Omit<RawConfig, 'apis' | 'styleguide'> & {
|
|
157
181
|
apis: Record<string, ResolvedApi>;
|
|
158
182
|
styleguide: ResolvedStyleguideConfig;
|
package/src/config/utils.ts
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assignExisting,
|
|
3
|
+
isDefined,
|
|
3
4
|
isTruthy,
|
|
4
5
|
showErrorForDeprecatedField,
|
|
5
6
|
showWarningForDeprecatedField,
|
|
6
7
|
} from '../utils';
|
|
7
8
|
import { Config } from './config';
|
|
8
|
-
|
|
9
9
|
import type {
|
|
10
10
|
Api,
|
|
11
11
|
DeprecatedInApi,
|
|
12
12
|
DeprecatedInRawConfig,
|
|
13
|
+
FlatApi,
|
|
14
|
+
FlatRawConfig,
|
|
13
15
|
Plugin,
|
|
14
16
|
RawConfig,
|
|
15
17
|
RawResolveConfig,
|
|
16
18
|
ResolveConfig,
|
|
17
19
|
ResolvedStyleguideConfig,
|
|
18
20
|
RulesFields,
|
|
21
|
+
StyleguideRawConfig,
|
|
19
22
|
} from './types';
|
|
20
23
|
import { logger, colorize } from '../logger';
|
|
21
24
|
|
|
@@ -39,13 +42,86 @@ export function transformApiDefinitionsToApis(
|
|
|
39
42
|
return apis;
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
function extractFlatConfig<
|
|
46
|
+
T extends Partial<
|
|
47
|
+
(Api & DeprecatedInApi & FlatApi) & (DeprecatedInRawConfig & RawConfig & FlatRawConfig)
|
|
48
|
+
>
|
|
49
|
+
>({
|
|
50
|
+
plugins,
|
|
51
|
+
extends: _extends,
|
|
52
|
+
|
|
53
|
+
rules,
|
|
54
|
+
oas2Rules,
|
|
55
|
+
oas3_0Rules,
|
|
56
|
+
oas3_1Rules,
|
|
57
|
+
|
|
58
|
+
preprocessors,
|
|
59
|
+
oas2Preprocessors,
|
|
60
|
+
oas3_0Preprocessors,
|
|
61
|
+
oas3_1Preprocessors,
|
|
62
|
+
|
|
63
|
+
decorators,
|
|
64
|
+
oas2Decorators,
|
|
65
|
+
oas3_0Decorators,
|
|
66
|
+
oas3_1Decorators,
|
|
67
|
+
|
|
68
|
+
...rawConfigRest
|
|
69
|
+
}: T): {
|
|
70
|
+
styleguideConfig?: StyleguideRawConfig;
|
|
71
|
+
rawConfigRest: Omit<T, 'plugins' | 'extends' | RulesFields>;
|
|
72
|
+
} {
|
|
73
|
+
const styleguideConfig = {
|
|
74
|
+
plugins,
|
|
75
|
+
extends: _extends,
|
|
76
|
+
|
|
77
|
+
rules,
|
|
78
|
+
oas2Rules,
|
|
79
|
+
oas3_0Rules,
|
|
80
|
+
oas3_1Rules,
|
|
81
|
+
|
|
82
|
+
preprocessors,
|
|
83
|
+
oas2Preprocessors,
|
|
84
|
+
oas3_0Preprocessors,
|
|
85
|
+
oas3_1Preprocessors,
|
|
86
|
+
|
|
87
|
+
decorators,
|
|
88
|
+
oas2Decorators,
|
|
89
|
+
oas3_0Decorators,
|
|
90
|
+
oas3_1Decorators,
|
|
91
|
+
|
|
92
|
+
doNotResolveExamples: (rawConfigRest as FlatRawConfig).resolve?.doNotResolveExamples,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
(rawConfigRest.lint && rawConfigRest.styleguide) ||
|
|
97
|
+
(Object.values(styleguideConfig).some(isDefined) &&
|
|
98
|
+
(rawConfigRest.lint || rawConfigRest.styleguide))
|
|
99
|
+
) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Do not use 'lint', 'styleguide' and flat syntax together. \nSee more about the configuration in the docs: https://redocly.com/docs/cli/configuration/ \n`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
styleguideConfig: Object.values(styleguideConfig).some(isDefined)
|
|
107
|
+
? styleguideConfig
|
|
108
|
+
: undefined,
|
|
109
|
+
|
|
110
|
+
rawConfigRest,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
42
114
|
function transformApis(
|
|
43
|
-
legacyApis?: Record<string, Api & DeprecatedInApi>
|
|
115
|
+
legacyApis?: Record<string, Api & DeprecatedInApi & FlatApi>
|
|
44
116
|
): Record<string, Api> | undefined {
|
|
45
117
|
if (!legacyApis) return undefined;
|
|
46
118
|
const apis: Record<string, Api> = {};
|
|
47
119
|
for (const [apiName, { lint, ...apiContent }] of Object.entries(legacyApis)) {
|
|
48
|
-
|
|
120
|
+
const { styleguideConfig, rawConfigRest } = extractFlatConfig(apiContent);
|
|
121
|
+
apis[apiName] = {
|
|
122
|
+
styleguide: styleguideConfig || lint,
|
|
123
|
+
...rawConfigRest,
|
|
124
|
+
};
|
|
49
125
|
}
|
|
50
126
|
return apis;
|
|
51
127
|
}
|
|
@@ -157,6 +233,7 @@ export function getMergedConfig(config: Config, apiName?: string): Config {
|
|
|
157
233
|
...config['features.mockServer'],
|
|
158
234
|
...config.apis[apiName]?.['features.mockServer'],
|
|
159
235
|
},
|
|
236
|
+
files: [...config.files, ...(config.apis?.[apiName]?.files ?? [])],
|
|
160
237
|
// TODO: merge everything else here
|
|
161
238
|
},
|
|
162
239
|
config.configFile
|
|
@@ -165,17 +242,22 @@ export function getMergedConfig(config: Config, apiName?: string): Config {
|
|
|
165
242
|
}
|
|
166
243
|
|
|
167
244
|
function checkForDeprecatedFields(
|
|
168
|
-
deprecatedField: keyof DeprecatedInRawConfig,
|
|
169
|
-
updatedField: keyof
|
|
170
|
-
rawConfig: DeprecatedInRawConfig & RawConfig
|
|
245
|
+
deprecatedField: keyof (DeprecatedInRawConfig & RawConfig),
|
|
246
|
+
updatedField: keyof FlatRawConfig | undefined,
|
|
247
|
+
rawConfig: DeprecatedInRawConfig & RawConfig & FlatRawConfig
|
|
171
248
|
): void {
|
|
172
249
|
const isDeprecatedFieldInApis =
|
|
173
250
|
rawConfig.apis &&
|
|
174
251
|
Object.values(rawConfig.apis).some(
|
|
175
|
-
(api: Api & DeprecatedInApi & DeprecatedInRawConfig) =>
|
|
252
|
+
(api: Api & FlatApi & DeprecatedInApi & DeprecatedInRawConfig & RawConfig & FlatRawConfig) =>
|
|
253
|
+
api[deprecatedField]
|
|
176
254
|
);
|
|
177
255
|
|
|
178
|
-
if (rawConfig[deprecatedField] &&
|
|
256
|
+
if (rawConfig[deprecatedField] && updatedField === null) {
|
|
257
|
+
showWarningForDeprecatedField(deprecatedField);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (rawConfig[deprecatedField] && updatedField && rawConfig[updatedField]) {
|
|
179
261
|
showErrorForDeprecatedField(deprecatedField, updatedField);
|
|
180
262
|
}
|
|
181
263
|
|
|
@@ -184,11 +266,17 @@ function checkForDeprecatedFields(
|
|
|
184
266
|
}
|
|
185
267
|
}
|
|
186
268
|
|
|
187
|
-
export function transformConfig(
|
|
188
|
-
|
|
269
|
+
export function transformConfig(
|
|
270
|
+
rawConfig: DeprecatedInRawConfig & RawConfig & FlatRawConfig
|
|
271
|
+
): RawConfig {
|
|
272
|
+
const migratedFields: [
|
|
273
|
+
keyof (DeprecatedInRawConfig & RawConfig),
|
|
274
|
+
keyof FlatRawConfig | undefined
|
|
275
|
+
][] = [
|
|
189
276
|
['apiDefinitions', 'apis'],
|
|
190
277
|
['referenceDocs', 'features.openapi'],
|
|
191
|
-
['lint',
|
|
278
|
+
['lint', undefined],
|
|
279
|
+
['styleguide', undefined],
|
|
192
280
|
];
|
|
193
281
|
|
|
194
282
|
for (const [deprecatedField, updatedField] of migratedFields) {
|
|
@@ -197,11 +285,13 @@ export function transformConfig(rawConfig: DeprecatedInRawConfig & RawConfig): R
|
|
|
197
285
|
|
|
198
286
|
const { apis, apiDefinitions, referenceDocs, lint, ...rest } = rawConfig;
|
|
199
287
|
|
|
288
|
+
const { styleguideConfig, rawConfigRest } = extractFlatConfig(rest);
|
|
289
|
+
|
|
200
290
|
return {
|
|
201
291
|
'features.openapi': referenceDocs,
|
|
202
292
|
apis: transformApis(apis) || transformApiDefinitionsToApis(apiDefinitions),
|
|
203
|
-
styleguide: lint,
|
|
204
|
-
...
|
|
293
|
+
styleguide: styleguideConfig || lint,
|
|
294
|
+
...rawConfigRest,
|
|
205
295
|
};
|
|
206
296
|
}
|
|
207
297
|
|
|
@@ -7,7 +7,7 @@ export const RegistryDependencies: Oas3Decorator | Oas2Decorator = () => {
|
|
|
7
7
|
const registryDependencies = new Set<string>();
|
|
8
8
|
|
|
9
9
|
return {
|
|
10
|
-
|
|
10
|
+
Root: {
|
|
11
11
|
leave(_: any, ctx: UserContext) {
|
|
12
12
|
const data = ctx.getVisitorData();
|
|
13
13
|
data.links = Array.from(registryDependencies);
|
package/src/format/format.ts
CHANGED
|
@@ -44,7 +44,13 @@ function severityToNumber(severity: ProblemSeverity) {
|
|
|
44
44
|
return severity === 'error' ? 1 : 2;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export type OutputFormat =
|
|
47
|
+
export type OutputFormat =
|
|
48
|
+
| 'codeframe'
|
|
49
|
+
| 'stylish'
|
|
50
|
+
| 'json'
|
|
51
|
+
| 'checkstyle'
|
|
52
|
+
| 'codeclimate'
|
|
53
|
+
| 'summary';
|
|
48
54
|
|
|
49
55
|
export function getTotals(problems: (NormalizedProblem & { ignored?: boolean })[]): Totals {
|
|
50
56
|
let errors = 0;
|
|
@@ -143,6 +149,9 @@ export function formatProblems(
|
|
|
143
149
|
case 'codeclimate':
|
|
144
150
|
outputForCodeClimate();
|
|
145
151
|
break;
|
|
152
|
+
case 'summary':
|
|
153
|
+
formatSummary(problems);
|
|
154
|
+
break;
|
|
146
155
|
}
|
|
147
156
|
|
|
148
157
|
if (totalProblems - ignoredProblems > maxProblems) {
|
|
@@ -257,13 +266,34 @@ export function formatProblems(
|
|
|
257
266
|
}
|
|
258
267
|
}
|
|
259
268
|
|
|
269
|
+
function formatSummary(problems: NormalizedProblem[]): void {
|
|
270
|
+
const counts: Record<string, { count: number; severity: ProblemSeverity }> = {};
|
|
271
|
+
for (const problem of problems) {
|
|
272
|
+
counts[problem.ruleId] = counts[problem.ruleId] || { count: 0, severity: problem.severity };
|
|
273
|
+
counts[problem.ruleId].count++;
|
|
274
|
+
}
|
|
275
|
+
const sorted = Object.entries(counts).sort(([, a], [, b]) => {
|
|
276
|
+
const severityDiff = severityToNumber(a.severity) - severityToNumber(b.severity);
|
|
277
|
+
return severityDiff || b.count - a.count;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
for (const [ruleId, info] of sorted) {
|
|
281
|
+
const color = COLORS[info.severity];
|
|
282
|
+
const severityName = color(SEVERITY_NAMES[info.severity].toLowerCase().padEnd(7));
|
|
283
|
+
logger.info(`${severityName} ${ruleId}: ${info.count}\n`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
logger.info('\n');
|
|
287
|
+
}
|
|
288
|
+
|
|
260
289
|
function formatFrom(cwd: string, location?: LocationObject) {
|
|
261
290
|
if (!location) return '';
|
|
262
291
|
const relativePath = path.relative(cwd, location.source.absoluteRef);
|
|
263
292
|
const loc = getLineColLocation(location);
|
|
264
293
|
const fileWithLoc = `${relativePath}:${loc.start.line}:${loc.start.col}`;
|
|
294
|
+
const atPointer = location.pointer ? colorize.gray(`at ${location.pointer}`) : '';
|
|
265
295
|
|
|
266
|
-
return `referenced from ${colorize.blue(fileWithLoc)}\n\n`;
|
|
296
|
+
return `referenced from ${colorize.blue(fileWithLoc)} ${atPointer} \n\n`;
|
|
267
297
|
}
|
|
268
298
|
|
|
269
299
|
function formatDidYouMean(problem: NormalizedProblem) {
|
package/src/lint.ts
CHANGED
|
@@ -80,13 +80,13 @@ export async function lintDocument(opts: {
|
|
|
80
80
|
const normalizedVisitors = normalizeVisitors([...preprocessors, ...regularRules] as any, types);
|
|
81
81
|
const resolvedRefMap = await resolveDocument({
|
|
82
82
|
rootDocument: document,
|
|
83
|
-
rootType: types.
|
|
83
|
+
rootType: types.Root,
|
|
84
84
|
externalRefResolver,
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
walkDocument({
|
|
88
88
|
document,
|
|
89
|
-
rootType: types.
|
|
89
|
+
rootType: types.Root,
|
|
90
90
|
normalizedVisitors,
|
|
91
91
|
resolvedRefMap,
|
|
92
92
|
ctx,
|
|
@@ -10,9 +10,6 @@ import { DEFAULT_REGION, DOMAINS } from '../config/config';
|
|
|
10
10
|
import { isNotEmptyObject } from '../utils';
|
|
11
11
|
const version = require('../../package.json').version;
|
|
12
12
|
|
|
13
|
-
export const currentCommand =
|
|
14
|
-
typeof process !== 'undefined' ? process.env?.REDOCLY_CLI_COMMAND || '' : '';
|
|
15
|
-
|
|
16
13
|
export class RegistryApi {
|
|
17
14
|
constructor(private accessTokens: AccessTokens, private region: Region) {}
|
|
18
15
|
|
|
@@ -30,9 +27,13 @@ export class RegistryApi {
|
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
private async request(path = '', options: RequestInit = {}, region?: Region) {
|
|
30
|
+
const currentCommand =
|
|
31
|
+
typeof process !== 'undefined' ? process.env?.REDOCLY_CLI_COMMAND || '' : '';
|
|
32
|
+
const redoclyEnv = typeof process !== 'undefined' ? process.env?.REDOCLY_ENVIRONMENT || '' : '';
|
|
33
|
+
|
|
33
34
|
const headers = Object.assign({}, options.headers || {}, {
|
|
34
35
|
'x-redocly-cli-version': version,
|
|
35
|
-
'user-agent': `redocly-cli / ${version} ${currentCommand}`,
|
|
36
|
+
'user-agent': `redocly-cli / ${version} ${currentCommand} ${redoclyEnv}`,
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
if (!headers.hasOwnProperty('authorization')) {
|
package/src/resolve.ts
CHANGED
|
@@ -115,7 +115,9 @@ export class BaseResolver {
|
|
|
115
115
|
const { body, mimeType } = await readFileFromUrl(absoluteRef, this.config.http);
|
|
116
116
|
return new Source(absoluteRef, body, mimeType);
|
|
117
117
|
} else {
|
|
118
|
-
|
|
118
|
+
const content = await fs.promises.readFile(absoluteRef, 'utf-8');
|
|
119
|
+
// In some cases file have \r\n line delimeters like on windows, we should skip it.
|
|
120
|
+
return new Source(absoluteRef, content.replace(/\r\n/g, '\n'));
|
|
119
121
|
}
|
|
120
122
|
} catch (error) {
|
|
121
123
|
throw new ResolveError(error);
|
|
@@ -153,7 +153,7 @@ describe('get-additional-properties-option', () => {
|
|
|
153
153
|
} catch (error) {
|
|
154
154
|
expect(error).toBeInstanceOf(Error);
|
|
155
155
|
expect(error.message).toEqual(
|
|
156
|
-
"Do not use 'disallowAdditionalProperties' field. Use 'allowAdditionalProperties' instead
|
|
156
|
+
"Do not use 'disallowAdditionalProperties' field. Use 'allowAdditionalProperties' instead. \n"
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
159
|
});
|
package/src/rules/ajv.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Ajv, { ValidateFunction, ErrorObject } from '@redocly/ajv';
|
|
1
|
+
import Ajv, { ValidateFunction, ErrorObject } from '@redocly/ajv/dist/2020';
|
|
2
2
|
import { Location, escapePointer } from '../ref-utils';
|
|
3
3
|
import { ResolveFn } from '../walk';
|
|
4
4
|
|
|
@@ -20,7 +20,7 @@ function getAjv(resolve: ResolveFn, allowAdditionalProperties: boolean) {
|
|
|
20
20
|
discriminator: true,
|
|
21
21
|
allowUnionTypes: true,
|
|
22
22
|
validateFormats: false, // TODO: fix it
|
|
23
|
-
|
|
23
|
+
defaultUnevaluatedProperties: allowAdditionalProperties,
|
|
24
24
|
loadSchemaSync(base: string, $ref: string) {
|
|
25
25
|
const resolvedRef = resolve({ $ref }, base.split('#')[0]);
|
|
26
26
|
if (!resolvedRef || !resolvedRef.location) return false;
|
|
@@ -87,8 +87,8 @@ export function validateJsonSchema(
|
|
|
87
87
|
if (propName) {
|
|
88
88
|
message = `\`${propName}\` property ${message}`;
|
|
89
89
|
}
|
|
90
|
-
if (error.keyword === 'additionalProperties') {
|
|
91
|
-
const property = error.params.additionalProperty;
|
|
90
|
+
if (error.keyword === 'additionalProperties' || error.keyword === 'unevaluatedProperties') {
|
|
91
|
+
const property = error.params.additionalProperty || error.params.unevaluatedProperty;
|
|
92
92
|
message = `${message} \`${property}\``;
|
|
93
93
|
error.instancePath += '/' + escapePointer(property);
|
|
94
94
|
}
|
|
@@ -191,6 +191,7 @@ describe('Oas3 typed enum', () => {
|
|
|
191
191
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
192
192
|
Array [
|
|
193
193
|
Object {
|
|
194
|
+
"from": undefined,
|
|
194
195
|
"location": Array [
|
|
195
196
|
Object {
|
|
196
197
|
"pointer": "#/paths/~1some/get/responses/200/content/application~1json/schema",
|
|
@@ -34,7 +34,7 @@ describe('Oas3 operation-2xx-response', () => {
|
|
|
34
34
|
"source": "foobar.yaml",
|
|
35
35
|
},
|
|
36
36
|
],
|
|
37
|
-
"message": "Operation must have at least one \`
|
|
37
|
+
"message": "Operation must have at least one \`2XX\` response.",
|
|
38
38
|
"ruleId": "operation-2xx-response",
|
|
39
39
|
"severity": "error",
|
|
40
40
|
"suggest": Array [],
|
|
@@ -34,7 +34,7 @@ describe('Oas3 operation-4xx-response', () => {
|
|
|
34
34
|
"source": "foobar.yaml",
|
|
35
35
|
},
|
|
36
36
|
],
|
|
37
|
-
"message": "Operation must have at least one \`
|
|
37
|
+
"message": "Operation must have at least one \`4XX\` response.",
|
|
38
38
|
"ruleId": "operation-4xx-response",
|
|
39
39
|
"severity": "error",
|
|
40
40
|
"suggest": Array [],
|
|
@@ -43,7 +43,7 @@ describe('Oas3 operation-4xx-response', () => {
|
|
|
43
43
|
`);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
it('should not report for present
|
|
46
|
+
it('should not report for present 400 response', async () => {
|
|
47
47
|
const document = parseYamlToDocument(
|
|
48
48
|
outdent`
|
|
49
49
|
openapi: 3.0.0
|
|
@@ -66,6 +66,29 @@ describe('Oas3 operation-4xx-response', () => {
|
|
|
66
66
|
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
it('should not report for present 4XX response', async () => {
|
|
70
|
+
const document = parseYamlToDocument(
|
|
71
|
+
outdent`
|
|
72
|
+
openapi: 3.0.0
|
|
73
|
+
paths:
|
|
74
|
+
'/test/':
|
|
75
|
+
put:
|
|
76
|
+
responses:
|
|
77
|
+
4XX:
|
|
78
|
+
description: error response
|
|
79
|
+
`,
|
|
80
|
+
'foobar.yaml'
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const results = await lintDocument({
|
|
84
|
+
externalRefResolver: new BaseResolver(),
|
|
85
|
+
document,
|
|
86
|
+
config: await makeConfig({ 'operation-4xx-response': 'error' }),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
90
|
+
});
|
|
91
|
+
|
|
69
92
|
it('should report if default is present but missing 4xx response', async () => {
|
|
70
93
|
const document = parseYamlToDocument(
|
|
71
94
|
outdent`
|
|
@@ -96,7 +119,7 @@ describe('Oas3 operation-4xx-response', () => {
|
|
|
96
119
|
"source": "foobar.yaml",
|
|
97
120
|
},
|
|
98
121
|
],
|
|
99
|
-
"message": "Operation must have at least one \`
|
|
122
|
+
"message": "Operation must have at least one \`4XX\` response.",
|
|
100
123
|
"ruleId": "operation-4xx-response",
|
|
101
124
|
"severity": "error",
|
|
102
125
|
"suggest": Array [],
|