@redocly/openapi-core 1.1.0 → 1.2.0
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/CHANGELOG.md +10 -0
- package/lib/bundle.d.ts +2 -2
- package/lib/bundle.js +24 -22
- package/lib/config/builtIn.js +4 -0
- package/lib/config/config-resolvers.js +19 -6
- package/lib/config/config.d.ts +9 -9
- package/lib/config/config.js +32 -17
- package/lib/config/rules.d.ts +2 -2
- package/lib/config/types.d.ts +9 -3
- package/lib/index.d.ts +1 -1
- package/lib/index.js +7 -6
- package/lib/lint.js +10 -16
- package/lib/oas-types.d.ts +16 -10
- package/lib/oas-types.js +52 -26
- package/lib/rules/async2/channels-kebab-case.d.ts +2 -0
- package/lib/rules/async2/channels-kebab-case.js +19 -0
- package/lib/rules/async2/index.d.ts +12 -0
- package/lib/rules/async2/index.js +22 -0
- package/lib/rules/async2/no-channel-trailing-slash.d.ts +2 -0
- package/lib/rules/async2/no-channel-trailing-slash.js +16 -0
- package/lib/rules/common/scalar-property-missing-example.js +1 -1
- package/lib/rules/common/spec.d.ts +2 -2
- package/lib/rules/common/spec.js +3 -3
- package/lib/rules/oas2/index.js +1 -1
- package/lib/rules/oas3/index.js +1 -1
- package/lib/types/asyncapi.d.ts +2 -0
- package/lib/types/asyncapi.js +1027 -0
- package/lib/types/redocly-yaml.js +3 -0
- package/lib/typings/asyncapi.d.ts +21 -0
- package/lib/typings/asyncapi.js +2 -0
- package/lib/visitors.d.ts +12 -0
- package/lib/walk.d.ts +3 -3
- package/package.json +2 -1
- package/src/__tests__/lint.test.ts +36 -6
- package/src/bundle.ts +27 -28
- package/src/config/__tests__/__snapshots__/config.test.ts.snap +24 -0
- package/src/config/__tests__/config.test.ts +15 -4
- package/src/config/builtIn.ts +4 -0
- package/src/config/config-resolvers.ts +25 -6
- package/src/config/config.ts +51 -27
- package/src/config/rules.ts +2 -2
- package/src/config/types.ts +14 -4
- package/src/index.ts +7 -1
- package/src/lint.ts +13 -22
- package/src/oas-types.ts +59 -21
- package/src/rules/async2/__tests__/channels-kebab-case.test.ts +141 -0
- package/src/rules/async2/__tests__/no-channel-trailing-slash.test.ts +97 -0
- package/src/rules/async2/channels-kebab-case.ts +18 -0
- package/src/rules/async2/index.ts +22 -0
- package/src/rules/async2/no-channel-trailing-slash.ts +15 -0
- package/src/rules/common/scalar-property-missing-example.ts +2 -2
- package/src/rules/common/spec.ts +2 -2
- package/src/rules/oas2/index.ts +2 -2
- package/src/rules/oas3/index.ts +2 -2
- package/src/types/asyncapi.ts +1136 -0
- package/src/types/redocly-yaml.ts +3 -0
- package/src/typings/asyncapi.ts +26 -0
- package/src/visitors.ts +22 -0
- package/src/walk.ts +3 -3
- package/tsconfig.tsbuildinfo +1 -1
package/src/config/config.ts
CHANGED
|
@@ -3,7 +3,13 @@ import * as path from 'path';
|
|
|
3
3
|
import { parseYaml, stringifyYaml } from '../js-yaml';
|
|
4
4
|
import { slash, doesYamlFileExist } from '../utils';
|
|
5
5
|
import { NormalizedProblem } from '../walk';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
SpecVersion,
|
|
8
|
+
SpecMajorVersion,
|
|
9
|
+
Oas2RuleSet,
|
|
10
|
+
Oas3RuleSet,
|
|
11
|
+
Async2RuleSet,
|
|
12
|
+
} from '../oas-types';
|
|
7
13
|
import { isBrowser, env } from '../env';
|
|
8
14
|
|
|
9
15
|
import type { NodeType } from '../types';
|
|
@@ -65,12 +71,12 @@ export class StyleguideConfig {
|
|
|
65
71
|
plugins: Plugin[];
|
|
66
72
|
ignore: Record<string, Record<string, Set<string>>> = {};
|
|
67
73
|
doNotResolveExamples: boolean;
|
|
68
|
-
rules: Record<
|
|
69
|
-
preprocessors: Record<
|
|
70
|
-
decorators: Record<
|
|
74
|
+
rules: Record<SpecVersion, Record<string, RuleConfig>>;
|
|
75
|
+
preprocessors: Record<SpecVersion, Record<string, PreprocessorConfig>>;
|
|
76
|
+
decorators: Record<SpecVersion, Record<string, DecoratorConfig>>;
|
|
71
77
|
|
|
72
78
|
private _usedRules: Set<string> = new Set();
|
|
73
|
-
private _usedVersions: Set<
|
|
79
|
+
private _usedVersions: Set<SpecVersion> = new Set();
|
|
74
80
|
|
|
75
81
|
recommendedFallback: boolean;
|
|
76
82
|
|
|
@@ -83,21 +89,24 @@ export class StyleguideConfig {
|
|
|
83
89
|
this.recommendedFallback = rawConfig.recommendedFallback || false;
|
|
84
90
|
|
|
85
91
|
this.rules = {
|
|
86
|
-
[
|
|
87
|
-
[
|
|
88
|
-
[
|
|
92
|
+
[SpecVersion.OAS2]: { ...rawConfig.rules, ...rawConfig.oas2Rules },
|
|
93
|
+
[SpecVersion.OAS3_0]: { ...rawConfig.rules, ...rawConfig.oas3_0Rules },
|
|
94
|
+
[SpecVersion.OAS3_1]: { ...rawConfig.rules, ...rawConfig.oas3_1Rules },
|
|
95
|
+
[SpecVersion.Async2]: { ...rawConfig.rules, ...rawConfig.async2Rules },
|
|
89
96
|
};
|
|
90
97
|
|
|
91
98
|
this.preprocessors = {
|
|
92
|
-
[
|
|
93
|
-
[
|
|
94
|
-
[
|
|
99
|
+
[SpecVersion.OAS2]: { ...rawConfig.preprocessors, ...rawConfig.oas2Preprocessors },
|
|
100
|
+
[SpecVersion.OAS3_0]: { ...rawConfig.preprocessors, ...rawConfig.oas3_0Preprocessors },
|
|
101
|
+
[SpecVersion.OAS3_1]: { ...rawConfig.preprocessors, ...rawConfig.oas3_1Preprocessors },
|
|
102
|
+
[SpecVersion.Async2]: { ...rawConfig.preprocessors, ...rawConfig.async2Preprocessors },
|
|
95
103
|
};
|
|
96
104
|
|
|
97
105
|
this.decorators = {
|
|
98
|
-
[
|
|
99
|
-
[
|
|
100
|
-
[
|
|
106
|
+
[SpecVersion.OAS2]: { ...rawConfig.decorators, ...rawConfig.oas2Decorators },
|
|
107
|
+
[SpecVersion.OAS3_0]: { ...rawConfig.decorators, ...rawConfig.oas3_0Decorators },
|
|
108
|
+
[SpecVersion.OAS3_1]: { ...rawConfig.decorators, ...rawConfig.oas3_1Decorators },
|
|
109
|
+
[SpecVersion.Async2]: { ...rawConfig.decorators, ...rawConfig.async2Decorators },
|
|
101
110
|
};
|
|
102
111
|
|
|
103
112
|
this.extendPaths = rawConfig.extendPaths || [];
|
|
@@ -173,20 +182,24 @@ export class StyleguideConfig {
|
|
|
173
182
|
: problem;
|
|
174
183
|
}
|
|
175
184
|
|
|
176
|
-
extendTypes(types: Record<string, NodeType>, version:
|
|
185
|
+
extendTypes(types: Record<string, NodeType>, version: SpecVersion) {
|
|
177
186
|
let extendedTypes = types;
|
|
178
187
|
for (const plugin of this.plugins) {
|
|
179
188
|
if (plugin.typeExtension !== undefined) {
|
|
180
189
|
switch (version) {
|
|
181
|
-
case
|
|
182
|
-
case
|
|
190
|
+
case SpecVersion.OAS3_0:
|
|
191
|
+
case SpecVersion.OAS3_1:
|
|
183
192
|
if (!plugin.typeExtension.oas3) continue;
|
|
184
193
|
extendedTypes = plugin.typeExtension.oas3(extendedTypes, version);
|
|
185
194
|
break;
|
|
186
|
-
case
|
|
195
|
+
case SpecVersion.OAS2:
|
|
187
196
|
if (!plugin.typeExtension.oas2) continue;
|
|
188
197
|
extendedTypes = plugin.typeExtension.oas2(extendedTypes, version);
|
|
189
198
|
break;
|
|
199
|
+
case SpecVersion.Async2:
|
|
200
|
+
if (!plugin.typeExtension.async2) continue;
|
|
201
|
+
extendedTypes = plugin.typeExtension.async2(extendedTypes, version);
|
|
202
|
+
break;
|
|
190
203
|
default:
|
|
191
204
|
throw new Error('Not implemented');
|
|
192
205
|
}
|
|
@@ -195,7 +208,7 @@ export class StyleguideConfig {
|
|
|
195
208
|
return extendedTypes;
|
|
196
209
|
}
|
|
197
210
|
|
|
198
|
-
getRuleSettings(ruleId: string, oasVersion:
|
|
211
|
+
getRuleSettings(ruleId: string, oasVersion: SpecVersion): RuleSettings {
|
|
199
212
|
this._usedRules.add(ruleId);
|
|
200
213
|
this._usedVersions.add(oasVersion);
|
|
201
214
|
const settings = this.rules[oasVersion][ruleId] || 'off';
|
|
@@ -208,7 +221,7 @@ export class StyleguideConfig {
|
|
|
208
221
|
}
|
|
209
222
|
}
|
|
210
223
|
|
|
211
|
-
getPreprocessorSettings(ruleId: string, oasVersion:
|
|
224
|
+
getPreprocessorSettings(ruleId: string, oasVersion: SpecVersion): RuleSettings {
|
|
212
225
|
this._usedRules.add(ruleId);
|
|
213
226
|
this._usedVersions.add(oasVersion);
|
|
214
227
|
|
|
@@ -222,7 +235,7 @@ export class StyleguideConfig {
|
|
|
222
235
|
}
|
|
223
236
|
}
|
|
224
237
|
|
|
225
|
-
getDecoratorSettings(ruleId: string, oasVersion:
|
|
238
|
+
getDecoratorSettings(ruleId: string, oasVersion: SpecVersion): RuleSettings {
|
|
226
239
|
this._usedRules.add(ruleId);
|
|
227
240
|
this._usedVersions.add(oasVersion);
|
|
228
241
|
const settings = this.decorators[oasVersion][ruleId] || 'off';
|
|
@@ -259,28 +272,39 @@ export class StyleguideConfig {
|
|
|
259
272
|
};
|
|
260
273
|
}
|
|
261
274
|
|
|
262
|
-
getRulesForOasVersion(version:
|
|
275
|
+
getRulesForOasVersion(version: SpecMajorVersion) {
|
|
263
276
|
switch (version) {
|
|
264
|
-
case
|
|
277
|
+
case SpecMajorVersion.OAS3:
|
|
265
278
|
// eslint-disable-next-line no-case-declarations
|
|
266
279
|
const oas3Rules: Oas3RuleSet[] = []; // default ruleset
|
|
267
280
|
this.plugins.forEach((p) => p.preprocessors?.oas3 && oas3Rules.push(p.preprocessors.oas3));
|
|
268
281
|
this.plugins.forEach((p) => p.rules?.oas3 && oas3Rules.push(p.rules.oas3));
|
|
269
282
|
this.plugins.forEach((p) => p.decorators?.oas3 && oas3Rules.push(p.decorators.oas3));
|
|
270
283
|
return oas3Rules;
|
|
271
|
-
case
|
|
284
|
+
case SpecMajorVersion.OAS2:
|
|
272
285
|
// eslint-disable-next-line no-case-declarations
|
|
273
286
|
const oas2Rules: Oas2RuleSet[] = []; // default ruleset
|
|
274
287
|
this.plugins.forEach((p) => p.preprocessors?.oas2 && oas2Rules.push(p.preprocessors.oas2));
|
|
275
288
|
this.plugins.forEach((p) => p.rules?.oas2 && oas2Rules.push(p.rules.oas2));
|
|
276
289
|
this.plugins.forEach((p) => p.decorators?.oas2 && oas2Rules.push(p.decorators.oas2));
|
|
277
290
|
return oas2Rules;
|
|
291
|
+
case SpecMajorVersion.Async2:
|
|
292
|
+
// eslint-disable-next-line no-case-declarations
|
|
293
|
+
const asyncApiRules: Async2RuleSet[] = []; // default ruleset
|
|
294
|
+
this.plugins.forEach(
|
|
295
|
+
(p) => p.preprocessors?.async2 && asyncApiRules.push(p.preprocessors.async2)
|
|
296
|
+
);
|
|
297
|
+
this.plugins.forEach((p) => p.rules?.async2 && asyncApiRules.push(p.rules.async2));
|
|
298
|
+
this.plugins.forEach(
|
|
299
|
+
(p) => p.decorators?.async2 && asyncApiRules.push(p.decorators.async2)
|
|
300
|
+
);
|
|
301
|
+
return asyncApiRules;
|
|
278
302
|
}
|
|
279
303
|
}
|
|
280
304
|
|
|
281
305
|
skipRules(rules?: string[]) {
|
|
282
306
|
for (const ruleId of rules || []) {
|
|
283
|
-
for (const version of Object.values(
|
|
307
|
+
for (const version of Object.values(SpecVersion)) {
|
|
284
308
|
if (this.rules[version][ruleId]) {
|
|
285
309
|
this.rules[version][ruleId] = 'off';
|
|
286
310
|
}
|
|
@@ -290,7 +314,7 @@ export class StyleguideConfig {
|
|
|
290
314
|
|
|
291
315
|
skipPreprocessors(preprocessors?: string[]) {
|
|
292
316
|
for (const preprocessorId of preprocessors || []) {
|
|
293
|
-
for (const version of Object.values(
|
|
317
|
+
for (const version of Object.values(SpecVersion)) {
|
|
294
318
|
if (this.preprocessors[version][preprocessorId]) {
|
|
295
319
|
this.preprocessors[version][preprocessorId] = 'off';
|
|
296
320
|
}
|
|
@@ -300,7 +324,7 @@ export class StyleguideConfig {
|
|
|
300
324
|
|
|
301
325
|
skipDecorators(decorators?: string[]) {
|
|
302
326
|
for (const decoratorId of decorators || []) {
|
|
303
|
-
for (const version of Object.values(
|
|
327
|
+
for (const version of Object.values(SpecVersion)) {
|
|
304
328
|
if (this.decorators[version][decoratorId]) {
|
|
305
329
|
this.decorators[version][decoratorId] = 'off';
|
|
306
330
|
}
|
package/src/config/rules.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RuleSet,
|
|
1
|
+
import { RuleSet, SpecVersion } from '../oas-types';
|
|
2
2
|
import { StyleguideConfig } from './config';
|
|
3
3
|
import { isDefined } from '../utils';
|
|
4
4
|
import type { ProblemSeverity } from '../walk';
|
|
@@ -13,7 +13,7 @@ export function initRules<T extends Function, P extends RuleSet<T>>(
|
|
|
13
13
|
rules: P[],
|
|
14
14
|
config: StyleguideConfig,
|
|
15
15
|
type: 'rules' | 'preprocessors' | 'decorators',
|
|
16
|
-
oasVersion:
|
|
16
|
+
oasVersion: SpecVersion
|
|
17
17
|
): InitializedRule[] {
|
|
18
18
|
return rules
|
|
19
19
|
.flatMap((ruleset) =>
|
package/src/config/types.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import type { ProblemSeverity, UserContext } from '../walk';
|
|
2
2
|
import type {
|
|
3
3
|
Oas3PreprocessorsSet,
|
|
4
|
-
|
|
4
|
+
SpecMajorVersion,
|
|
5
5
|
Oas3DecoratorsSet,
|
|
6
6
|
Oas2RuleSet,
|
|
7
7
|
Oas2PreprocessorsSet,
|
|
8
8
|
Oas2DecoratorsSet,
|
|
9
9
|
Oas3RuleSet,
|
|
10
|
-
|
|
10
|
+
SpecVersion,
|
|
11
|
+
Async2PreprocessorsSet,
|
|
12
|
+
Async2DecoratorsSet,
|
|
13
|
+
Async2RuleSet,
|
|
11
14
|
} from '../oas-types';
|
|
15
|
+
|
|
12
16
|
import type { NodeType } from '../types';
|
|
13
17
|
import { Location } from '../ref-utils';
|
|
14
18
|
import type { SkipFunctionContext } from '../visitors';
|
|
@@ -43,16 +47,19 @@ export type StyleguideRawConfig = {
|
|
|
43
47
|
oas2Rules?: Record<string, RuleConfig>;
|
|
44
48
|
oas3_0Rules?: Record<string, RuleConfig>;
|
|
45
49
|
oas3_1Rules?: Record<string, RuleConfig>;
|
|
50
|
+
async2Rules?: Record<string, RuleConfig>;
|
|
46
51
|
|
|
47
52
|
preprocessors?: Record<string, PreprocessorConfig>;
|
|
48
53
|
oas2Preprocessors?: Record<string, PreprocessorConfig>;
|
|
49
54
|
oas3_0Preprocessors?: Record<string, PreprocessorConfig>;
|
|
50
55
|
oas3_1Preprocessors?: Record<string, PreprocessorConfig>;
|
|
56
|
+
async2Preprocessors?: Record<string, PreprocessorConfig>;
|
|
51
57
|
|
|
52
58
|
decorators?: Record<string, DecoratorConfig>;
|
|
53
59
|
oas2Decorators?: Record<string, DecoratorConfig>;
|
|
54
60
|
oas3_0Decorators?: Record<string, DecoratorConfig>;
|
|
55
61
|
oas3_1Decorators?: Record<string, DecoratorConfig>;
|
|
62
|
+
async2Decorators?: Record<string, DecoratorConfig>;
|
|
56
63
|
};
|
|
57
64
|
|
|
58
65
|
export type ApiStyleguideRawConfig = Omit<StyleguideRawConfig, 'plugins'>;
|
|
@@ -68,23 +75,26 @@ export type ResolvedStyleguideConfig = PluginStyleguideConfig & {
|
|
|
68
75
|
export type PreprocessorsConfig = {
|
|
69
76
|
oas3?: Oas3PreprocessorsSet;
|
|
70
77
|
oas2?: Oas2PreprocessorsSet;
|
|
78
|
+
async2?: Async2PreprocessorsSet;
|
|
71
79
|
};
|
|
72
80
|
|
|
73
81
|
export type DecoratorsConfig = {
|
|
74
82
|
oas3?: Oas3DecoratorsSet;
|
|
75
83
|
oas2?: Oas2DecoratorsSet;
|
|
84
|
+
async2?: Async2DecoratorsSet;
|
|
76
85
|
};
|
|
77
86
|
|
|
78
87
|
export type TypesExtensionFn = (
|
|
79
88
|
types: Record<string, NodeType>,
|
|
80
|
-
oasVersion:
|
|
89
|
+
oasVersion: SpecVersion
|
|
81
90
|
) => Record<string, NodeType>;
|
|
82
91
|
|
|
83
|
-
export type TypeExtensionsConfig = Partial<Record<
|
|
92
|
+
export type TypeExtensionsConfig = Partial<Record<SpecMajorVersion, TypesExtensionFn>>;
|
|
84
93
|
|
|
85
94
|
export type CustomRulesConfig = {
|
|
86
95
|
oas3?: Oas3RuleSet;
|
|
87
96
|
oas2?: Oas2RuleSet;
|
|
97
|
+
async2?: Async2RuleSet;
|
|
88
98
|
};
|
|
89
99
|
|
|
90
100
|
export type AssertionContext = Partial<UserContext> & SkipFunctionContext & { node: any };
|
package/src/index.ts
CHANGED
|
@@ -52,7 +52,13 @@ export {
|
|
|
52
52
|
} from './resolve';
|
|
53
53
|
export { parseYaml, stringifyYaml } from './js-yaml';
|
|
54
54
|
export { unescapePointer, isRef, isAbsoluteUrl } from './ref-utils';
|
|
55
|
-
export {
|
|
55
|
+
export {
|
|
56
|
+
SpecMajorVersion,
|
|
57
|
+
getMajorSpecVersion,
|
|
58
|
+
SpecVersion,
|
|
59
|
+
detectSpec,
|
|
60
|
+
getTypes,
|
|
61
|
+
} from './oas-types';
|
|
56
62
|
export { normalizeVisitors } from './visitors';
|
|
57
63
|
|
|
58
64
|
export {
|
package/src/lint.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { BaseResolver, resolveDocument, Document, makeDocumentFromString } from './resolve';
|
|
2
2
|
import { normalizeVisitors } from './visitors';
|
|
3
|
-
import { Oas3_1Types } from './types/oas3_1';
|
|
4
|
-
import { Oas3Types } from './types/oas3';
|
|
5
|
-
import { Oas2Types } from './types/oas2';
|
|
6
3
|
import { NodeType } from './types';
|
|
7
4
|
import { ProblemSeverity, WalkContext, walkDocument } from './walk';
|
|
8
5
|
import { StyleguideConfig, Config, initRules, defaultPlugin, resolvePlugins } from './config';
|
|
9
6
|
import { normalizeTypes } from './types';
|
|
10
7
|
import { releaseAjvInstance } from './rules/ajv';
|
|
11
|
-
import {
|
|
8
|
+
import { Oas3RuleSet, SpecVersion, getMajorSpecVersion, detectSpec, getTypes } from './oas-types';
|
|
12
9
|
import { ConfigTypes } from './types/redocly-yaml';
|
|
13
|
-
import {
|
|
10
|
+
import { Spec } from './rules/common/spec';
|
|
14
11
|
|
|
15
12
|
export async function lint(opts: {
|
|
16
13
|
ref: string;
|
|
@@ -54,29 +51,22 @@ export async function lintDocument(opts: {
|
|
|
54
51
|
releaseAjvInstance(); // FIXME: preprocessors can modify nodes which are then cached to ajv-instance by absolute path
|
|
55
52
|
|
|
56
53
|
const { document, customTypes, externalRefResolver, config } = opts;
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const rules = config.getRulesForOasVersion(
|
|
54
|
+
const specVersion = detectSpec(document.parsed);
|
|
55
|
+
const specMajorVersion = getMajorSpecVersion(specVersion);
|
|
56
|
+
const rules = config.getRulesForOasVersion(specMajorVersion);
|
|
60
57
|
const types = normalizeTypes(
|
|
61
|
-
config.extendTypes(
|
|
62
|
-
customTypes ?? oasMajorVersion === OasMajorVersion.Version3
|
|
63
|
-
? oasVersion === OasVersion.Version3_1
|
|
64
|
-
? Oas3_1Types
|
|
65
|
-
: Oas3Types
|
|
66
|
-
: Oas2Types,
|
|
67
|
-
oasVersion
|
|
68
|
-
),
|
|
58
|
+
config.extendTypes(customTypes ?? getTypes(specVersion), specVersion),
|
|
69
59
|
config
|
|
70
60
|
);
|
|
71
61
|
|
|
72
62
|
const ctx: WalkContext = {
|
|
73
63
|
problems: [],
|
|
74
|
-
oasVersion:
|
|
64
|
+
oasVersion: specVersion,
|
|
75
65
|
visitorsData: {},
|
|
76
66
|
};
|
|
77
67
|
|
|
78
|
-
const preprocessors = initRules(rules as any, config, 'preprocessors',
|
|
79
|
-
const regularRules = initRules(rules as Oas3RuleSet[], config, 'rules',
|
|
68
|
+
const preprocessors = initRules(rules as any, config, 'preprocessors', specVersion);
|
|
69
|
+
const regularRules = initRules(rules as Oas3RuleSet[], config, 'rules', specVersion);
|
|
80
70
|
|
|
81
71
|
let resolvedRefMap = await resolveDocument({
|
|
82
72
|
rootDocument: document,
|
|
@@ -117,7 +107,7 @@ export async function lintConfig(opts: { document: Document; severity?: ProblemS
|
|
|
117
107
|
|
|
118
108
|
const ctx: WalkContext = {
|
|
119
109
|
problems: [],
|
|
120
|
-
oasVersion:
|
|
110
|
+
oasVersion: SpecVersion.OAS3_0,
|
|
121
111
|
visitorsData: {},
|
|
122
112
|
};
|
|
123
113
|
const plugins = resolvePlugins([defaultPlugin]);
|
|
@@ -131,10 +121,11 @@ export async function lintConfig(opts: { document: Document; severity?: ProblemS
|
|
|
131
121
|
{
|
|
132
122
|
severity: severity || 'error',
|
|
133
123
|
ruleId: 'configuration spec',
|
|
134
|
-
visitor:
|
|
124
|
+
visitor: Spec({ severity: 'error' }),
|
|
135
125
|
},
|
|
136
126
|
];
|
|
137
|
-
|
|
127
|
+
// TODO: check why any is needed
|
|
128
|
+
const normalizedVisitors = normalizeVisitors(rules as any, types);
|
|
138
129
|
|
|
139
130
|
walkDocument({
|
|
140
131
|
document,
|
package/src/oas-types.ts
CHANGED
|
@@ -1,57 +1,95 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Oas3Rule,
|
|
3
|
+
Oas3Preprocessor,
|
|
4
|
+
Oas2Rule,
|
|
5
|
+
Oas2Preprocessor,
|
|
6
|
+
Async2Preprocessor,
|
|
7
|
+
Async2Rule,
|
|
8
|
+
} from './visitors';
|
|
9
|
+
import { Oas2Types } from './types/oas2';
|
|
10
|
+
import { Oas3Types } from './types/oas3';
|
|
11
|
+
import { Oas3_1Types } from './types/oas3_1';
|
|
12
|
+
import { AsyncApi2Types } from './types/asyncapi';
|
|
2
13
|
|
|
3
14
|
export type RuleSet<T> = Record<string, T>;
|
|
4
15
|
|
|
5
|
-
export enum
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
export enum SpecVersion {
|
|
17
|
+
OAS2 = 'oas2',
|
|
18
|
+
OAS3_0 = 'oas3_0',
|
|
19
|
+
OAS3_1 = 'oas3_1',
|
|
20
|
+
Async2 = 'async2', // todo split into 2.x maybe?
|
|
9
21
|
}
|
|
10
22
|
|
|
11
|
-
export enum
|
|
12
|
-
|
|
13
|
-
|
|
23
|
+
export enum SpecMajorVersion {
|
|
24
|
+
OAS2 = 'oas2',
|
|
25
|
+
OAS3 = 'oas3',
|
|
26
|
+
Async2 = 'async2',
|
|
14
27
|
}
|
|
15
28
|
|
|
29
|
+
const typesMap = {
|
|
30
|
+
[SpecVersion.OAS2]: Oas2Types,
|
|
31
|
+
[SpecVersion.OAS3_0]: Oas3Types,
|
|
32
|
+
[SpecVersion.OAS3_1]: Oas3_1Types,
|
|
33
|
+
[SpecVersion.Async2]: AsyncApi2Types,
|
|
34
|
+
};
|
|
35
|
+
|
|
16
36
|
export type Oas3RuleSet = Record<string, Oas3Rule>;
|
|
17
37
|
export type Oas2RuleSet = Record<string, Oas2Rule>;
|
|
38
|
+
export type Async2RuleSet = Record<string, Async2Rule>;
|
|
18
39
|
export type Oas3PreprocessorsSet = Record<string, Oas3Preprocessor>;
|
|
19
40
|
export type Oas2PreprocessorsSet = Record<string, Oas2Preprocessor>;
|
|
41
|
+
export type Async2PreprocessorsSet = Record<string, Async2Preprocessor>;
|
|
20
42
|
export type Oas3DecoratorsSet = Record<string, Oas3Preprocessor>;
|
|
21
43
|
export type Oas2DecoratorsSet = Record<string, Oas2Preprocessor>;
|
|
44
|
+
export type Async2DecoratorsSet = Record<string, Async2Preprocessor>;
|
|
22
45
|
|
|
23
|
-
export function
|
|
46
|
+
export function detectSpec(root: any): SpecVersion {
|
|
24
47
|
if (typeof root !== 'object') {
|
|
25
48
|
throw new Error(`Document must be JSON object, got ${typeof root}`);
|
|
26
49
|
}
|
|
27
50
|
|
|
28
|
-
if (!(root.openapi || root.swagger)) {
|
|
29
|
-
throw new Error('This doesn’t look like an OpenAPI document.\n');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
51
|
if (root.openapi && typeof root.openapi !== 'string') {
|
|
33
52
|
throw new Error(`Invalid OpenAPI version: should be a string but got "${typeof root.openapi}"`);
|
|
34
53
|
}
|
|
35
54
|
|
|
36
55
|
if (root.openapi && root.openapi.startsWith('3.0')) {
|
|
37
|
-
return
|
|
56
|
+
return SpecVersion.OAS3_0;
|
|
38
57
|
}
|
|
39
58
|
|
|
40
59
|
if (root.openapi && root.openapi.startsWith('3.1')) {
|
|
41
|
-
return
|
|
60
|
+
return SpecVersion.OAS3_1;
|
|
42
61
|
}
|
|
43
62
|
|
|
44
63
|
if (root.swagger && root.swagger === '2.0') {
|
|
45
|
-
return
|
|
64
|
+
return SpecVersion.OAS2;
|
|
46
65
|
}
|
|
47
66
|
|
|
48
|
-
|
|
67
|
+
// if not detected yet
|
|
68
|
+
if (root.openapi || root.swagger) {
|
|
69
|
+
throw new Error(`Unsupported OpenAPI version: ${root.openapi || root.swagger}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (root.asyncapi && root.asyncapi.startsWith('2.')) {
|
|
73
|
+
return SpecVersion.Async2;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (root.asyncapi) {
|
|
77
|
+
throw new Error(`Unsupported AsyncAPI version: ${root.asyncapi}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw new Error(`Unsupported specification`);
|
|
49
81
|
}
|
|
50
82
|
|
|
51
|
-
export function
|
|
52
|
-
if (version ===
|
|
53
|
-
return
|
|
83
|
+
export function getMajorSpecVersion(version: SpecVersion): SpecMajorVersion {
|
|
84
|
+
if (version === SpecVersion.OAS2) {
|
|
85
|
+
return SpecMajorVersion.OAS2;
|
|
86
|
+
} else if (version === SpecVersion.Async2) {
|
|
87
|
+
return SpecMajorVersion.Async2;
|
|
54
88
|
} else {
|
|
55
|
-
return
|
|
89
|
+
return SpecMajorVersion.OAS3;
|
|
56
90
|
}
|
|
57
91
|
}
|
|
92
|
+
|
|
93
|
+
export function getTypes(spec: SpecVersion) {
|
|
94
|
+
return typesMap[spec];
|
|
95
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
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('Async2 channels-kebab-case', () => {
|
|
7
|
+
it('should report on no kebab-case channel path', async () => {
|
|
8
|
+
const document = parseYamlToDocument(
|
|
9
|
+
outdent`
|
|
10
|
+
asyncapi: '2.6.0'
|
|
11
|
+
info:
|
|
12
|
+
title: Cool API
|
|
13
|
+
version: 1.0.0
|
|
14
|
+
channels:
|
|
15
|
+
NOT_A_KEBAB:
|
|
16
|
+
subscribe:
|
|
17
|
+
message:
|
|
18
|
+
messageId: Message1
|
|
19
|
+
`,
|
|
20
|
+
'asyncapi.yaml'
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const results = await lintDocument({
|
|
24
|
+
externalRefResolver: new BaseResolver(),
|
|
25
|
+
document,
|
|
26
|
+
config: await makeConfig({ 'channels-kebab-case': 'error' }),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
30
|
+
Array [
|
|
31
|
+
Object {
|
|
32
|
+
"location": Array [
|
|
33
|
+
Object {
|
|
34
|
+
"pointer": "#/channels/NOT_A_KEBAB",
|
|
35
|
+
"reportOnKey": true,
|
|
36
|
+
"source": "asyncapi.yaml",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
"message": "\`NOT_A_KEBAB\` does not use kebab-case.",
|
|
40
|
+
"ruleId": "channels-kebab-case",
|
|
41
|
+
"severity": "error",
|
|
42
|
+
"suggest": Array [],
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
`);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should report on snake_case in channel path', async () => {
|
|
49
|
+
const document = parseYamlToDocument(
|
|
50
|
+
outdent`
|
|
51
|
+
asyncapi: '2.6.0'
|
|
52
|
+
info:
|
|
53
|
+
title: Cool API
|
|
54
|
+
version: 1.0.0
|
|
55
|
+
channels:
|
|
56
|
+
snake_kebab:
|
|
57
|
+
subscribe:
|
|
58
|
+
message:
|
|
59
|
+
messageId: Message1
|
|
60
|
+
`,
|
|
61
|
+
'asyncapi.yaml'
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const results = await lintDocument({
|
|
65
|
+
externalRefResolver: new BaseResolver(),
|
|
66
|
+
document,
|
|
67
|
+
config: await makeConfig({ 'channels-kebab-case': 'error' }),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
71
|
+
Array [
|
|
72
|
+
Object {
|
|
73
|
+
"location": Array [
|
|
74
|
+
Object {
|
|
75
|
+
"pointer": "#/channels/snake_kebab",
|
|
76
|
+
"reportOnKey": true,
|
|
77
|
+
"source": "asyncapi.yaml",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
"message": "\`snake_kebab\` does not use kebab-case.",
|
|
81
|
+
"ruleId": "channels-kebab-case",
|
|
82
|
+
"severity": "error",
|
|
83
|
+
"suggest": Array [],
|
|
84
|
+
},
|
|
85
|
+
]
|
|
86
|
+
`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should allow trailing slash in channel path with "channels-kebab-case" rule', async () => {
|
|
90
|
+
const document = parseYamlToDocument(
|
|
91
|
+
outdent`
|
|
92
|
+
asyncapi: '2.6.0'
|
|
93
|
+
info:
|
|
94
|
+
title: Cool API
|
|
95
|
+
version: 1.0.0
|
|
96
|
+
channels:
|
|
97
|
+
kebab/:
|
|
98
|
+
subscribe:
|
|
99
|
+
message:
|
|
100
|
+
messageId: Message1
|
|
101
|
+
`,
|
|
102
|
+
'asyncapi.yaml'
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const results = await lintDocument({
|
|
106
|
+
externalRefResolver: new BaseResolver(),
|
|
107
|
+
document,
|
|
108
|
+
config: await makeConfig({
|
|
109
|
+
'paths-kebab-case': 'error',
|
|
110
|
+
'no-path-trailing-slash': 'off',
|
|
111
|
+
}),
|
|
112
|
+
});
|
|
113
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('words with hyphens are allowed with "channels-kebab-case" rule', async () => {
|
|
117
|
+
const document = parseYamlToDocument(
|
|
118
|
+
outdent`
|
|
119
|
+
asyncapi: '2.6.0'
|
|
120
|
+
info:
|
|
121
|
+
title: Cool API
|
|
122
|
+
version: 1.0.0
|
|
123
|
+
channels:
|
|
124
|
+
kebab-with-longer-channel-path:
|
|
125
|
+
subscribe:
|
|
126
|
+
message:
|
|
127
|
+
messageId: Message1
|
|
128
|
+
`,
|
|
129
|
+
'asyncapi.yaml'
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const results = await lintDocument({
|
|
133
|
+
externalRefResolver: new BaseResolver(),
|
|
134
|
+
document,
|
|
135
|
+
config: await makeConfig({
|
|
136
|
+
'paths-kebab-case': 'error',
|
|
137
|
+
}),
|
|
138
|
+
});
|
|
139
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
|
|
140
|
+
});
|
|
141
|
+
});
|