Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rulesets): support 2.1.0, 2.2.0, 2.3.0 AsyncAPI versions #2067

Merged
merged 3 commits into from
Feb 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 54 additions & 7 deletions packages/formats/src/__tests__/asyncapi.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { asyncApi2 } from '../asyncapi';
import { aas2, aas2_0, aas2_1, aas2_2, aas2_3 } from '../asyncapi';

describe('AsyncApi format', () => {
describe('AsyncApi 2.{minor}.{patch}', () => {
it.each([['2.0.17'], ['2.9.0'], ['2.9.3']])('recognizes %s version correctly', (version: string) => {
expect(asyncApi2({ asyncapi: version }, null)).toBe(true);
});
describe('AsyncAPI format', () => {
describe('AsyncAPI 2.x', () => {
it.each(['2.0.0', '2.1.0', '2.2.0', '2.3.0', '2.0.17', '2.1.37', '2.9.0', '2.9.3'])(
'recognizes %s version correctly',
version => {
expect(aas2({ asyncapi: version }, null)).toBe(true);
},
);

const testCases = [
{ asyncapi: '3.0' },
Expand All @@ -15,6 +18,7 @@ describe('AsyncApi format', () => {
{ asyncapi: '2.0.01' },
{ asyncapi: '1.0' },
{ asyncapi: 2 },
{ asyncapi: null },
{ openapi: '4.0' },
{ openapi: '2.0' },
{ openapi: null },
Expand All @@ -25,7 +29,50 @@ describe('AsyncApi format', () => {
];

it.each(testCases)('does not recognize invalid document %o', document => {
expect(asyncApi2(document, null)).toBe(false);
expect(aas2(document, null)).toBe(false);
});
});

describe('AsyncAPI 2.0', () => {
it.each(['2.0.0', '2.0.3'])('recognizes %s version correctly', version => {
expect(aas2_0({ asyncapi: version }, null)).toBe(true);
});

it.each(['2', '2.0', '2.1.0', '2.1.3'])('does not recognize %s version', version => {
expect(aas2_0({ asyncapi: version }, null)).toBe(false);
});
});

describe('AsyncAPI 2.1', () => {
it.each(['2.1.0', '2.1.37'])('recognizes %s version correctly', version => {
expect(aas2_1({ asyncapi: version }, null)).toBe(true);
});

it.each(['2', '2.1', '2.0.0', '2.2.0', '2.2.3'])('does not recognize %s version', version => {
expect(aas2_1({ asyncapi: version }, null)).toBe(false);
});
});

describe('AsyncAPI 2.2', () => {
it.each(['2.2.0', '2.2.3'])('recognizes %s version correctly', version => {
expect(aas2_2({ asyncapi: version }, null)).toBe(true);
});

it.each(['2', '2.2', '2.0.0', '2.1.0', '2.1.37', '2.3.0', '2.3.3'])('does not recognize %s version', version => {
expect(aas2_2({ asyncapi: version }, null)).toBe(false);
});
});

describe('AsyncAPI 2.3', () => {
it.each(['2.3.0', '2.3.3'])('recognizes %s version correctly', version => {
expect(aas2_3({ asyncapi: version }, null)).toBe(true);
});

it.each(['2', '2.3', '2.0.0', '2.1.0', '2.1.37', '2.2.0', '2.4.0', '2.4.3'])(
'does not recognize %s version',
version => {
expect(aas2_3({ asyncapi: version }, null)).toBe(false);
},
);
});
});
40 changes: 26 additions & 14 deletions packages/formats/src/asyncapi.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import type { Format } from '@stoplight/spectral-core';
import { isPlainObject } from '@stoplight/json';

type MaybeAsyncApi2 = Partial<{ asyncapi: unknown }>;
type MaybeAAS2 = { asyncapi: unknown } & Record<string, unknown>;

const bearsAStringPropertyNamed = (document: unknown, propertyName: string): boolean => {
return isPlainObject(document) && propertyName in document && typeof document[propertyName] === 'string';
};
const aas2Regex = /^2\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;
const aas2_0Regex = /^2\.0(?:\.[0-9]*)?$/;
const aas2_1Regex = /^2\.1(?:\.[0-9]*)?$/;
const aas2_2Regex = /^2\.2(?:\.[0-9]*)?$/;
const aas2_3Regex = /^2\.3(?:\.[0-9]*)?$/;

const version2Regex = /^2\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;
const isAas2 = (document: unknown): document is { asyncapi: string } & Record<string, unknown> =>
isPlainObject(document) && 'asyncapi' in document && aas2Regex.test(String((document as MaybeAAS2).asyncapi));

export const asyncApi2: Format = document => {
if (!bearsAStringPropertyNamed(document, 'asyncapi')) {
return false;
}
export const aas2: Format = isAas2;
aas2.displayName = 'AsyncAPI 2.x';

const version = String((document as MaybeAsyncApi2).asyncapi);
// for backward compatibility
export const asyncApi2 = aas2;
export const asyncapi2 = aas2;

return version2Regex.test(version);
};
export const aas2_0: Format = (document: unknown): boolean =>
isAas2(document) && aas2_0Regex.test(String((document as MaybeAAS2).asyncapi));
aas2_0.displayName = 'AsyncAPI 2.0.x';

asyncApi2.displayName = 'AsyncAPI 2.x';
export const aas2_1: Format = (document: unknown): boolean =>
isAas2(document) && aas2_1Regex.test(String((document as MaybeAAS2).asyncapi));
aas2_1.displayName = 'AsyncAPI 2.1.x';

export { asyncApi2 as asyncapi2 };
export const aas2_2: Format = (document: unknown): boolean =>
isAas2(document) && aas2_2Regex.test(String((document as MaybeAAS2).asyncapi));
aas2_2.displayName = 'AsyncAPI 2.2.x';

export const aas2_3: Format = (document: unknown): boolean =>
isAas2(document) && aas2_3Regex.test(String((document as MaybeAAS2).asyncapi));
aas2_3.displayName = 'AsyncAPI 2.3.x';
1 change: 1 addition & 0 deletions packages/rulesets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"release": "semantic-release -e semantic-release-monorepo"
},
"dependencies": {
"@asyncapi/specs": "^2.13.0",
"@stoplight/better-ajv-errors": "1.0.1",
"@stoplight/json": "^3.17.0",
"@stoplight/spectral-core": "^1.8.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import testRule from '../../../__tests__/__helpers__/tester';
import testRule, { createWithRules } from '../../../__tests__/__helpers__/tester';

export { testRule as default };
export { testRule as default, createWithRules };
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ testRule('asyncapi-schema', [
},
errors: [],
},

{
name: 'channels property is missing',
document: {
Expand All @@ -24,8 +23,6 @@ testRule('asyncapi-schema', [
version: '1.0',
},
},
errors: [
{ message: 'Object must have required property "channels"', path: [], severity: DiagnosticSeverity.Error },
],
errors: [{ message: 'Object must have required property "channels"', severity: DiagnosticSeverity.Error }],
},
]);
Loading