@redocly/openapi-core 1.0.0-beta.108 → 1.0.0-beta.109
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/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 +1 -1
- 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 +7 -0
- package/lib/config/utils.d.ts +2 -2
- package/lib/config/utils.js +42 -4
- 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/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/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 +46 -55
- 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 +1 -1
- 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 +106 -131
- 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__/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-file.yaml +18 -19
- package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +9 -10
- 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 +12 -15
- package/src/config/__tests__/utils.test.ts +64 -4
- package/src/config/all.ts +3 -1
- package/src/config/config-resolvers.ts +2 -2
- package/src/config/load.ts +3 -2
- 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 +11 -0
- package/src/config/utils.ts +102 -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/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__/utils.test.ts +2 -2
- 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__/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/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 +52 -40
- 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/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
|
@@ -110,6 +110,7 @@ export type ResolveHeader =
|
|
|
110
110
|
|
|
111
111
|
export type RawResolveConfig = {
|
|
112
112
|
http?: Partial<HttpResolveConfig>;
|
|
113
|
+
doNotResolveExamples?: boolean;
|
|
113
114
|
};
|
|
114
115
|
|
|
115
116
|
export type HttpResolveConfig = {
|
|
@@ -128,6 +129,7 @@ export type AccessTokens = { [region in Region]?: string };
|
|
|
128
129
|
export type DeprecatedInRawConfig = {
|
|
129
130
|
apiDefinitions?: Record<string, string>;
|
|
130
131
|
lint?: StyleguideRawConfig;
|
|
132
|
+
styleguide?: StyleguideRawConfig;
|
|
131
133
|
referenceDocs?: Record<string, any>;
|
|
132
134
|
apis?: Record<string, Api & DeprecatedInApi>;
|
|
133
135
|
};
|
|
@@ -153,6 +155,15 @@ export type RawConfig = {
|
|
|
153
155
|
organization?: string;
|
|
154
156
|
} & FeaturesConfig;
|
|
155
157
|
|
|
158
|
+
export type FlatApi = Omit<Api, 'styleguide'> &
|
|
159
|
+
Omit<ApiStyleguideRawConfig, 'doNotResolveExamples'>;
|
|
160
|
+
|
|
161
|
+
export type FlatRawConfig = Omit<RawConfig, 'styleguide' | 'resolve' | 'apis'> &
|
|
162
|
+
Omit<StyleguideRawConfig, 'doNotResolveExamples'> & {
|
|
163
|
+
resolve?: RawResolveConfig;
|
|
164
|
+
apis?: Record<string, FlatApi>;
|
|
165
|
+
};
|
|
166
|
+
|
|
156
167
|
export type ResolvedConfig = Omit<RawConfig, 'apis' | 'styleguide'> & {
|
|
157
168
|
apis: Record<string, ResolvedApi>;
|
|
158
169
|
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
|
}
|
|
@@ -165,17 +241,22 @@ export function getMergedConfig(config: Config, apiName?: string): Config {
|
|
|
165
241
|
}
|
|
166
242
|
|
|
167
243
|
function checkForDeprecatedFields(
|
|
168
|
-
deprecatedField: keyof DeprecatedInRawConfig,
|
|
169
|
-
updatedField: keyof
|
|
170
|
-
rawConfig: DeprecatedInRawConfig & RawConfig
|
|
244
|
+
deprecatedField: keyof (DeprecatedInRawConfig & RawConfig),
|
|
245
|
+
updatedField: keyof FlatRawConfig | undefined,
|
|
246
|
+
rawConfig: DeprecatedInRawConfig & RawConfig & FlatRawConfig
|
|
171
247
|
): void {
|
|
172
248
|
const isDeprecatedFieldInApis =
|
|
173
249
|
rawConfig.apis &&
|
|
174
250
|
Object.values(rawConfig.apis).some(
|
|
175
|
-
(api: Api & DeprecatedInApi & DeprecatedInRawConfig) =>
|
|
251
|
+
(api: Api & FlatApi & DeprecatedInApi & DeprecatedInRawConfig & RawConfig & FlatRawConfig) =>
|
|
252
|
+
api[deprecatedField]
|
|
176
253
|
);
|
|
177
254
|
|
|
178
|
-
if (rawConfig[deprecatedField] &&
|
|
255
|
+
if (rawConfig[deprecatedField] && updatedField === null) {
|
|
256
|
+
showWarningForDeprecatedField(deprecatedField);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (rawConfig[deprecatedField] && updatedField && rawConfig[updatedField]) {
|
|
179
260
|
showErrorForDeprecatedField(deprecatedField, updatedField);
|
|
180
261
|
}
|
|
181
262
|
|
|
@@ -184,11 +265,17 @@ function checkForDeprecatedFields(
|
|
|
184
265
|
}
|
|
185
266
|
}
|
|
186
267
|
|
|
187
|
-
export function transformConfig(
|
|
188
|
-
|
|
268
|
+
export function transformConfig(
|
|
269
|
+
rawConfig: DeprecatedInRawConfig & RawConfig & FlatRawConfig
|
|
270
|
+
): RawConfig {
|
|
271
|
+
const migratedFields: [
|
|
272
|
+
keyof (DeprecatedInRawConfig & RawConfig),
|
|
273
|
+
keyof FlatRawConfig | undefined
|
|
274
|
+
][] = [
|
|
189
275
|
['apiDefinitions', 'apis'],
|
|
190
276
|
['referenceDocs', 'features.openapi'],
|
|
191
|
-
['lint',
|
|
277
|
+
['lint', undefined],
|
|
278
|
+
['styleguide', undefined],
|
|
192
279
|
];
|
|
193
280
|
|
|
194
281
|
for (const [deprecatedField, updatedField] of migratedFields) {
|
|
@@ -197,11 +284,13 @@ export function transformConfig(rawConfig: DeprecatedInRawConfig & RawConfig): R
|
|
|
197
284
|
|
|
198
285
|
const { apis, apiDefinitions, referenceDocs, lint, ...rest } = rawConfig;
|
|
199
286
|
|
|
287
|
+
const { styleguideConfig, rawConfigRest } = extractFlatConfig(rest);
|
|
288
|
+
|
|
200
289
|
return {
|
|
201
290
|
'features.openapi': referenceDocs,
|
|
202
291
|
apis: transformApis(apis) || transformApiDefinitionsToApis(apiDefinitions),
|
|
203
|
-
styleguide: lint,
|
|
204
|
-
...
|
|
292
|
+
styleguide: styleguideConfig || lint,
|
|
293
|
+
...rawConfigRest,
|
|
205
294
|
};
|
|
206
295
|
}
|
|
207
296
|
|
|
@@ -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
|
});
|
|
@@ -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 [],
|
|
@@ -0,0 +1,175 @@
|
|
|
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 security-defined', () => {
|
|
7
|
+
it('should report on securityRequirements object if security scheme is not defined in components', async () => {
|
|
8
|
+
const document = parseYamlToDocument(
|
|
9
|
+
outdent`
|
|
10
|
+
openapi: 3.0.0
|
|
11
|
+
paths:
|
|
12
|
+
/pets:
|
|
13
|
+
get:
|
|
14
|
+
security:
|
|
15
|
+
- some: []`,
|
|
16
|
+
'foobar.yaml'
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const results = await lintDocument({
|
|
20
|
+
externalRefResolver: new BaseResolver(),
|
|
21
|
+
document,
|
|
22
|
+
config: await makeConfig({ 'security-defined': 'error' }),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
26
|
+
Array [
|
|
27
|
+
Object {
|
|
28
|
+
"location": Array [
|
|
29
|
+
Object {
|
|
30
|
+
"pointer": "#/paths/~1pets/get/security/0/some",
|
|
31
|
+
"reportOnKey": true,
|
|
32
|
+
"source": "foobar.yaml",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
"message": "There is no \`some\` security scheme defined.",
|
|
36
|
+
"ruleId": "security-defined",
|
|
37
|
+
"severity": "error",
|
|
38
|
+
"suggest": Array [],
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
`);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should not report if security defined with an empty array', async () => {
|
|
45
|
+
const document = parseYamlToDocument(
|
|
46
|
+
outdent`
|
|
47
|
+
openapi: 3.0.0
|
|
48
|
+
security: []
|
|
49
|
+
paths:`,
|
|
50
|
+
'foobar.yaml'
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const results = await lintDocument({
|
|
54
|
+
externalRefResolver: new BaseResolver(),
|
|
55
|
+
document,
|
|
56
|
+
config: await makeConfig({ 'security-defined': 'error' }),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should report if security not defined at all', async () => {
|
|
63
|
+
const document = parseYamlToDocument(
|
|
64
|
+
outdent`
|
|
65
|
+
openapi: 3.0.0
|
|
66
|
+
paths:
|
|
67
|
+
/pets:
|
|
68
|
+
get:
|
|
69
|
+
requestBody:`,
|
|
70
|
+
'foobar.yaml'
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const results = await lintDocument({
|
|
74
|
+
externalRefResolver: new BaseResolver(),
|
|
75
|
+
document,
|
|
76
|
+
config: await makeConfig({ 'security-defined': 'error' }),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
80
|
+
Array [
|
|
81
|
+
Object {
|
|
82
|
+
"location": Array [
|
|
83
|
+
Object {
|
|
84
|
+
"pointer": "#/",
|
|
85
|
+
"reportOnKey": false,
|
|
86
|
+
"source": "foobar.yaml",
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
"message": "Every API should have security defined on the root level or for each operation.",
|
|
90
|
+
"ruleId": "security-defined",
|
|
91
|
+
"severity": "error",
|
|
92
|
+
"suggest": Array [],
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
`);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should report if security not defined for each operation', async () => {
|
|
99
|
+
const document = parseYamlToDocument(
|
|
100
|
+
outdent`
|
|
101
|
+
openapi: 3.0.0
|
|
102
|
+
paths:
|
|
103
|
+
/pets:
|
|
104
|
+
get:
|
|
105
|
+
security:
|
|
106
|
+
- some: []
|
|
107
|
+
/cats:
|
|
108
|
+
get:`,
|
|
109
|
+
'foobar.yaml'
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const results = await lintDocument({
|
|
113
|
+
externalRefResolver: new BaseResolver(),
|
|
114
|
+
document,
|
|
115
|
+
config: await makeConfig({ 'security-defined': 'error' }),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
119
|
+
Array [
|
|
120
|
+
Object {
|
|
121
|
+
"location": Array [
|
|
122
|
+
Object {
|
|
123
|
+
"pointer": "#/paths/~1pets/get/security/0/some",
|
|
124
|
+
"reportOnKey": true,
|
|
125
|
+
"source": "foobar.yaml",
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
"message": "There is no \`some\` security scheme defined.",
|
|
129
|
+
"ruleId": "security-defined",
|
|
130
|
+
"severity": "error",
|
|
131
|
+
"suggest": Array [],
|
|
132
|
+
},
|
|
133
|
+
Object {
|
|
134
|
+
"location": Array [
|
|
135
|
+
Object {
|
|
136
|
+
"pointer": "#/",
|
|
137
|
+
"reportOnKey": false,
|
|
138
|
+
"source": "foobar.yaml",
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
"message": "Every API should have security defined on the root level or for each operation.",
|
|
142
|
+
"ruleId": "security-defined",
|
|
143
|
+
"severity": "error",
|
|
144
|
+
"suggest": Array [],
|
|
145
|
+
},
|
|
146
|
+
]
|
|
147
|
+
`);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should not report on securityRequirements object if security scheme is defined in components', async () => {
|
|
151
|
+
const document = parseYamlToDocument(
|
|
152
|
+
outdent`
|
|
153
|
+
openapi: 3.0.0
|
|
154
|
+
paths:
|
|
155
|
+
/pets:
|
|
156
|
+
get:
|
|
157
|
+
security:
|
|
158
|
+
some: []
|
|
159
|
+
components:
|
|
160
|
+
securitySchemes:
|
|
161
|
+
some:
|
|
162
|
+
type: http
|
|
163
|
+
scheme: basic`,
|
|
164
|
+
'foobar.yaml'
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const results = await lintDocument({
|
|
168
|
+
externalRefResolver: new BaseResolver(),
|
|
169
|
+
document,
|
|
170
|
+
config: await makeConfig({ 'security-defined': 'error' }),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
174
|
+
});
|
|
175
|
+
});
|