diff --git a/packages/formats/src/__tests__/asyncapi.test.ts b/packages/formats/src/__tests__/asyncapi.test.ts index 9bd0de0cb..01e22c240 100644 --- a/packages/formats/src/__tests__/asyncapi.test.ts +++ b/packages/formats/src/__tests__/asyncapi.test.ts @@ -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' }, @@ -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 }, @@ -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); + }, + ); + }); }); diff --git a/packages/formats/src/asyncapi.ts b/packages/formats/src/asyncapi.ts index 8a1a6dde3..87312ac19 100644 --- a/packages/formats/src/asyncapi.ts +++ b/packages/formats/src/asyncapi.ts @@ -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; -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 => + 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';