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 AsyncAPI 2.6.0 #2391

Merged
merged 1 commit into from
Feb 3, 2023
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
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