@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.
Files changed (60) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/lib/bundle.d.ts +2 -2
  3. package/lib/bundle.js +24 -22
  4. package/lib/config/builtIn.js +4 -0
  5. package/lib/config/config-resolvers.js +19 -6
  6. package/lib/config/config.d.ts +9 -9
  7. package/lib/config/config.js +32 -17
  8. package/lib/config/rules.d.ts +2 -2
  9. package/lib/config/types.d.ts +9 -3
  10. package/lib/index.d.ts +1 -1
  11. package/lib/index.js +7 -6
  12. package/lib/lint.js +10 -16
  13. package/lib/oas-types.d.ts +16 -10
  14. package/lib/oas-types.js +52 -26
  15. package/lib/rules/async2/channels-kebab-case.d.ts +2 -0
  16. package/lib/rules/async2/channels-kebab-case.js +19 -0
  17. package/lib/rules/async2/index.d.ts +12 -0
  18. package/lib/rules/async2/index.js +22 -0
  19. package/lib/rules/async2/no-channel-trailing-slash.d.ts +2 -0
  20. package/lib/rules/async2/no-channel-trailing-slash.js +16 -0
  21. package/lib/rules/common/scalar-property-missing-example.js +1 -1
  22. package/lib/rules/common/spec.d.ts +2 -2
  23. package/lib/rules/common/spec.js +3 -3
  24. package/lib/rules/oas2/index.js +1 -1
  25. package/lib/rules/oas3/index.js +1 -1
  26. package/lib/types/asyncapi.d.ts +2 -0
  27. package/lib/types/asyncapi.js +1027 -0
  28. package/lib/types/redocly-yaml.js +3 -0
  29. package/lib/typings/asyncapi.d.ts +21 -0
  30. package/lib/typings/asyncapi.js +2 -0
  31. package/lib/visitors.d.ts +12 -0
  32. package/lib/walk.d.ts +3 -3
  33. package/package.json +2 -1
  34. package/src/__tests__/lint.test.ts +36 -6
  35. package/src/bundle.ts +27 -28
  36. package/src/config/__tests__/__snapshots__/config.test.ts.snap +24 -0
  37. package/src/config/__tests__/config.test.ts +15 -4
  38. package/src/config/builtIn.ts +4 -0
  39. package/src/config/config-resolvers.ts +25 -6
  40. package/src/config/config.ts +51 -27
  41. package/src/config/rules.ts +2 -2
  42. package/src/config/types.ts +14 -4
  43. package/src/index.ts +7 -1
  44. package/src/lint.ts +13 -22
  45. package/src/oas-types.ts +59 -21
  46. package/src/rules/async2/__tests__/channels-kebab-case.test.ts +141 -0
  47. package/src/rules/async2/__tests__/no-channel-trailing-slash.test.ts +97 -0
  48. package/src/rules/async2/channels-kebab-case.ts +18 -0
  49. package/src/rules/async2/index.ts +22 -0
  50. package/src/rules/async2/no-channel-trailing-slash.ts +15 -0
  51. package/src/rules/common/scalar-property-missing-example.ts +2 -2
  52. package/src/rules/common/spec.ts +2 -2
  53. package/src/rules/oas2/index.ts +2 -2
  54. package/src/rules/oas3/index.ts +2 -2
  55. package/src/types/asyncapi.ts +1136 -0
  56. package/src/types/redocly-yaml.ts +3 -0
  57. package/src/typings/asyncapi.ts +26 -0
  58. package/src/visitors.ts +22 -0
  59. package/src/walk.ts +3 -3
  60. package/tsconfig.tsbuildinfo +1 -1
@@ -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 { OasVersion, OasMajorVersion, Oas2RuleSet, Oas3RuleSet } from '../oas-types';
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<OasVersion, Record<string, RuleConfig>>;
69
- preprocessors: Record<OasVersion, Record<string, PreprocessorConfig>>;
70
- decorators: Record<OasVersion, Record<string, DecoratorConfig>>;
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<OasVersion> = new 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
- [OasVersion.Version2]: { ...rawConfig.rules, ...rawConfig.oas2Rules },
87
- [OasVersion.Version3_0]: { ...rawConfig.rules, ...rawConfig.oas3_0Rules },
88
- [OasVersion.Version3_1]: { ...rawConfig.rules, ...rawConfig.oas3_1Rules },
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
- [OasVersion.Version2]: { ...rawConfig.preprocessors, ...rawConfig.oas2Preprocessors },
93
- [OasVersion.Version3_0]: { ...rawConfig.preprocessors, ...rawConfig.oas3_0Preprocessors },
94
- [OasVersion.Version3_1]: { ...rawConfig.preprocessors, ...rawConfig.oas3_1Preprocessors },
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
- [OasVersion.Version2]: { ...rawConfig.decorators, ...rawConfig.oas2Decorators },
99
- [OasVersion.Version3_0]: { ...rawConfig.decorators, ...rawConfig.oas3_0Decorators },
100
- [OasVersion.Version3_1]: { ...rawConfig.decorators, ...rawConfig.oas3_1Decorators },
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: OasVersion) {
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 OasVersion.Version3_0:
182
- case OasVersion.Version3_1:
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 OasVersion.Version2:
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: OasVersion): RuleSettings {
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: OasVersion): RuleSettings {
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: OasVersion): RuleSettings {
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: OasMajorVersion) {
275
+ getRulesForOasVersion(version: SpecMajorVersion) {
263
276
  switch (version) {
264
- case OasMajorVersion.Version3:
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 OasMajorVersion.Version2:
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(OasVersion)) {
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(OasVersion)) {
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(OasVersion)) {
327
+ for (const version of Object.values(SpecVersion)) {
304
328
  if (this.decorators[version][decoratorId]) {
305
329
  this.decorators[version][decoratorId] = 'off';
306
330
  }
@@ -1,4 +1,4 @@
1
- import { RuleSet, OasVersion } from '../oas-types';
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: OasVersion
16
+ oasVersion: SpecVersion
17
17
  ): InitializedRule[] {
18
18
  return rules
19
19
  .flatMap((ruleset) =>
@@ -1,14 +1,18 @@
1
1
  import type { ProblemSeverity, UserContext } from '../walk';
2
2
  import type {
3
3
  Oas3PreprocessorsSet,
4
- OasMajorVersion,
4
+ SpecMajorVersion,
5
5
  Oas3DecoratorsSet,
6
6
  Oas2RuleSet,
7
7
  Oas2PreprocessorsSet,
8
8
  Oas2DecoratorsSet,
9
9
  Oas3RuleSet,
10
- OasVersion,
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: OasVersion
89
+ oasVersion: SpecVersion
81
90
  ) => Record<string, NodeType>;
82
91
 
83
- export type TypeExtensionsConfig = Partial<Record<OasMajorVersion, TypesExtensionFn>>;
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 { detectOpenAPI, OasMajorVersion, openAPIMajor, OasVersion } from './oas-types';
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 { detectOpenAPI, Oas3RuleSet, OasMajorVersion, OasVersion, openAPIMajor } from './oas-types';
8
+ import { Oas3RuleSet, SpecVersion, getMajorSpecVersion, detectSpec, getTypes } from './oas-types';
12
9
  import { ConfigTypes } from './types/redocly-yaml';
13
- import { OasSpec } from './rules/common/spec';
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 oasVersion = detectOpenAPI(document.parsed);
58
- const oasMajorVersion = openAPIMajor(oasVersion);
59
- const rules = config.getRulesForOasVersion(oasMajorVersion);
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: oasVersion,
64
+ oasVersion: specVersion,
75
65
  visitorsData: {},
76
66
  };
77
67
 
78
- const preprocessors = initRules(rules as any, config, 'preprocessors', oasVersion);
79
- const regularRules = initRules(rules as Oas3RuleSet[], config, 'rules', oasVersion);
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: OasVersion.Version3_0,
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: OasSpec({ severity: 'error' }),
124
+ visitor: Spec({ severity: 'error' }),
135
125
  },
136
126
  ];
137
- const normalizedVisitors = normalizeVisitors(rules, types);
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 { Oas3Rule, Oas3Preprocessor, Oas2Rule, Oas2Preprocessor } from './visitors';
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 OasVersion {
6
- Version2 = 'oas2',
7
- Version3_0 = 'oas3_0',
8
- Version3_1 = 'oas3_1',
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 OasMajorVersion {
12
- Version2 = 'oas2',
13
- Version3 = 'oas3',
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 detectOpenAPI(root: any): OasVersion {
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 OasVersion.Version3_0;
56
+ return SpecVersion.OAS3_0;
38
57
  }
39
58
 
40
59
  if (root.openapi && root.openapi.startsWith('3.1')) {
41
- return OasVersion.Version3_1;
60
+ return SpecVersion.OAS3_1;
42
61
  }
43
62
 
44
63
  if (root.swagger && root.swagger === '2.0') {
45
- return OasVersion.Version2;
64
+ return SpecVersion.OAS2;
46
65
  }
47
66
 
48
- throw new Error(`Unsupported OpenAPI Version: ${root.openapi || root.swagger}`);
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 openAPIMajor(version: OasVersion): OasMajorVersion {
52
- if (version === OasVersion.Version2) {
53
- return OasMajorVersion.Version2;
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 OasMajorVersion.Version3;
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
+ });