Skip to content

Commit

Permalink
feat(rulesets): support AsyncAPI 2.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
magicmatatjahu committed Feb 1, 2023
1 parent e026a85 commit 84afbef
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 27 deletions.
1 change: 1 addition & 0 deletions docs/guides/4-custom-rulesets.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Formats are an optional way to specify which API description formats a rule, or
- `aas2_3` (AsyncAPI v2.3.0)
- `aas2_4` (AsyncAPI v2.4.0)
- `aas2_5` (AsyncAPI v2.5.0)
- `aas2_6` (AsyncAPI v2.6.0)
- `oas2` (OpenAPI v2.0)
- `oas3` (OpenAPI v3.x)
- `oas3_0` (OpenAPI v3.0.x)
Expand Down
26 changes: 25 additions & 1 deletion packages/formats/src/__tests__/asyncapi.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { aas2, aas2_0, aas2_1, aas2_2, aas2_3, aas2_4, aas2_5 } from '../asyncapi';
import { aas2, aas2_0, aas2_1, aas2_2, aas2_3, aas2_4, aas2_5, aas2_6 } from '../asyncapi';

describe('AsyncAPI format', () => {
describe('AsyncAPI 2.x', () => {
Expand Down Expand Up @@ -101,4 +101,28 @@ describe('AsyncAPI format', () => {
},
);
});

describe('AsyncAPI 2.6', () => {
it.each(['2.6.0', '2.6.2'])('recognizes %s version correctly', version => {
expect(aas2_6({ asyncapi: version }, null)).toBe(true);
});

it.each([
'2',
'2.3',
'2.0.0',
'2.1.0',
'2.1.37',
'2.2.0',
'2.3.0',
'2.4.0',
'2.4.3',
'2.5.0',
'2.5.4',
'2.7.0',
'2.7.4',
])('does not recognize %s version', version => {
expect(aas2_6({ asyncapi: version }, null)).toBe(false);
});
});
});
5 changes: 5 additions & 0 deletions packages/formats/src/asyncapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const aas2_2Regex = /^2\.2(?:\.[0-9]*)?$/;
const aas2_3Regex = /^2\.3(?:\.[0-9]*)?$/;
const aas2_4Regex = /^2\.4(?:\.[0-9]*)?$/;
const aas2_5Regex = /^2\.5(?:\.[0-9]*)?$/;
const aas2_6Regex = /^2\.6(?:\.[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));
Expand Down Expand Up @@ -44,3 +45,7 @@ aas2_4.displayName = 'AsyncAPI 2.4.x';
export const aas2_5: Format = (document: unknown): boolean =>
isAas2(document) && aas2_5Regex.test(String((document as MaybeAAS2).asyncapi));
aas2_5.displayName = 'AsyncAPI 2.5.x';

export const aas2_6: Format = (document: unknown): boolean =>
isAas2(document) && aas2_6Regex.test(String((document as MaybeAAS2).asyncapi));
aas2_6.displayName = 'AsyncAPI 2.6.x';
2 changes: 1 addition & 1 deletion packages/rulesets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"release": "semantic-release -e semantic-release-monorepo"
},
"dependencies": {
"@asyncapi/specs": "^3.2.0",
"@asyncapi/specs": "^4.1.0",
"@stoplight/better-ajv-errors": "1.0.3",
"@stoplight/json": "^3.17.0",
"@stoplight/spectral-core": "^1.8.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,116 @@ describe('asyncApi2DocumentSchema', () => {
});
});

describe('given AsyncAPI 2.4.0 document', () => {
test('validate messageId on message', async () => {
expect(
await s.run({
asyncapi: '2.4.0',
info: {
title: 'Signup service example (internal)',
version: '0.1.0',
},
channels: {
'/user/signedup': {
subscribe: {
message: {
messageId: 'messageId',
payload: {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
},
},
},
},
},
},
},
}),
).toEqual([]);
});
});

describe('given AsyncAPI 2.5.0 document', () => {
test('validate tags on server', async () => {
expect(
await s.run({
asyncapi: '2.5.0',
info: {
title: 'Signup service example (internal)',
version: '0.1.0',
},
servers: {
development: {
url: 'https://some-server.com/example',
protocol: 'kafka',
tags: [
{
name: 'env:production',
},
{
name: 'e-commerce',
},
],
},
},
channels: {
'/user/signedup': {
subscribe: {
message: {
messageId: 'messageId',
payload: {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
},
},
},
},
},
},
},
}),
).toEqual([]);
});
});

describe('given AsyncAPI 2.6.0 document', () => {
test('validate valid spec', async () => {
expect(
await s.run({
asyncapi: '2.6.0',
info: {
title: 'Signup service example (internal)',
version: '0.1.0',
},
channels: {
'/user/signedup': {
subscribe: {
message: {
messageId: 'messageId',
payload: {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
},
},
},
},
},
},
},
}),
).toEqual([]);
});
});

describe('prepareResults', () => {
test('given oneOf error one of which is required $ref property missing, picks only one error', () => {
const errors: ErrorObject[] = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createRulesetFunction } from '@stoplight/spectral-core';
import { schema as schemaFn } from '@stoplight/spectral-functions';
import { aas2_0, aas2_1, aas2_2, aas2_3, aas2_4, aas2_5 } from '@stoplight/spectral-formats';
import { aas2_0, aas2_1, aas2_2, aas2_3, aas2_4, aas2_5, aas2_6 } from '@stoplight/spectral-formats';

import { getCopyOfSchema } from './utils/specs';

Expand Down Expand Up @@ -92,6 +92,8 @@ function getSerializedSchema(version: AsyncAPISpecVersion): Record<string, unkno

function getSchema(formats: Set<Format>): Record<string, any> | void {
switch (true) {
case formats.has(aas2_6):
return getSerializedSchema('2.6.0');
case formats.has(aas2_5):
return getSerializedSchema('2.5.0');
case formats.has(aas2_4):
Expand Down
17 changes: 1 addition & 16 deletions packages/rulesets/src/asyncapi/functions/utils/specs.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
// import only 2.X.X AsyncAPI JSON Schemas for better treeshaking
import * as asyncAPI2_0_0Schema from '@asyncapi/specs/schemas/2.0.0.json';
import * as asyncAPI2_1_0Schema from '@asyncapi/specs/schemas/2.1.0.json';
import * as asyncAPI2_2_0Schema from '@asyncapi/specs/schemas/2.2.0.json';
import * as asyncAPI2_3_0Schema from '@asyncapi/specs/schemas/2.3.0.json';
import * as asyncAPI2_4_0Schema from '@asyncapi/specs/schemas/2.4.0.json';
import * as asyncAPI2_5_0Schema from '@asyncapi/specs/schemas/2.5.0.json';
import specs from '@asyncapi/specs';

export type AsyncAPISpecVersion = keyof typeof specs;

export const specs = {
'2.0.0': asyncAPI2_0_0Schema,
'2.1.0': asyncAPI2_1_0Schema,
'2.2.0': asyncAPI2_2_0Schema,
'2.3.0': asyncAPI2_3_0Schema,
'2.4.0': asyncAPI2_4_0Schema,
'2.5.0': asyncAPI2_5_0Schema,
};

const versions = Object.keys(specs);
export const latestVersion = versions[versions.length - 1];

Expand Down
4 changes: 2 additions & 2 deletions packages/rulesets/src/asyncapi/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { aas2_0, aas2_1, aas2_2, aas2_3, aas2_4, aas2_5 } from '@stoplight/spectral-formats';
import { aas2_0, aas2_1, aas2_2, aas2_3, aas2_4, aas2_5, aas2_6 } from '@stoplight/spectral-formats';
import {
truthy,
pattern,
Expand All @@ -23,7 +23,7 @@ import { latestVersion } from './functions/utils/specs';

export default {
documentationUrl: 'https://meta.stoplight.io/docs/spectral/docs/reference/asyncapi-rules.md',
formats: [aas2_0, aas2_1, aas2_2, aas2_3, aas2_4, aas2_5],
formats: [aas2_0, aas2_1, aas2_2, aas2_3, aas2_4, aas2_5, aas2_6],
rules: {
'asyncapi-channel-no-empty-parameter': {
description: 'Channel path must not have empty parameter substitution pattern.',
Expand Down
2 changes: 1 addition & 1 deletion test-harness/scenarios/asyncapi2-streetlights.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ module.exports = asyncapi;
====stdout====
{document}
1:1 warning asyncapi-tags AsyncAPI object must have non-empty "tags" array.
1:11 information asyncapi-latest-version The latest version is not used. You should update to the "2.5.0" version. asyncapi
1:11 information asyncapi-latest-version The latest version is not used. You should update to the "2.6.0" version. asyncapi
2:6 warning asyncapi-info-contact Info object must have "contact" object. info
45:13 warning asyncapi-operation-description Operation "description" must be present and non-empty string. channels.smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured.publish
57:15 warning asyncapi-operation-description Operation "description" must be present and non-empty string. channels.smartylighting/streetlights/1/0/action/{streetlightId}/turn/on.subscribe
Expand Down
12 changes: 7 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ __metadata:
languageName: node
linkType: hard

"@asyncapi/specs@npm:^3.2.0":
version: 3.2.0
resolution: "@asyncapi/specs@npm:3.2.0"
checksum: 09971262aefc8844ab3e7c0c3652711862ac562dd5d614f23b496185690430a81df8e50eddba657f4141e0fd9548ef622fe6c20f4e3dec8054be23f774798335
"@asyncapi/specs@npm:^4.1.0":
version: 4.1.0
resolution: "@asyncapi/specs@npm:4.1.0"
dependencies:
"@types/json-schema": ^7.0.11
checksum: 6e95f3c1ef7267480cdfc69f5a015f63b9101874289e31843a629346a3ea07490e3043b296a089bf3458e877c664d83d4b4738dcb53d37d7e13b75c7bc08c879
languageName: node
linkType: hard

Expand Down Expand Up @@ -2677,7 +2679,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@stoplight/spectral-rulesets@workspace:packages/rulesets"
dependencies:
"@asyncapi/specs": ^3.2.0
"@asyncapi/specs": ^4.1.0
"@stoplight/better-ajv-errors": 1.0.3
"@stoplight/json": ^3.17.0
"@stoplight/path": ^1.3.2
Expand Down

0 comments on commit 84afbef

Please sign in to comment.