From 8315508c21ac3dc2d27cfcdf226c58ae98dbae07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Urba=C5=84czyk?= Date: Fri, 3 Feb 2023 12:35:49 +0100 Subject: [PATCH] feat(rulesets): support AsyncAPI 2.6.0 (#2391) --- packages/rulesets/package.json | 4 +- .../__tests__/asyncApi2DocumentSchema.test.ts | 110 +++++++++ .../functions/asyncApi2DocumentSchema.ts | 4 +- .../src/asyncapi/functions/utils/specs.ts | 17 +- packages/rulesets/src/asyncapi/index.ts | 4 +- .../scenarios/asyncapi2-streetlights.scenario | 228 ------------------ yarn.lock | 16 +- 7 files changed, 127 insertions(+), 256 deletions(-) diff --git a/packages/rulesets/package.json b/packages/rulesets/package.json index 5df7c91a7..cdd4223cd 100644 --- a/packages/rulesets/package.json +++ b/packages/rulesets/package.json @@ -21,11 +21,11 @@ "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", - "@stoplight/spectral-formats": "^1.4.0", + "@stoplight/spectral-formats": "^1.5.0", "@stoplight/spectral-functions": "^1.5.1", "@stoplight/spectral-runtime": "^1.1.1", "@stoplight/types": "^13.6.0", diff --git a/packages/rulesets/src/asyncapi/functions/__tests__/asyncApi2DocumentSchema.test.ts b/packages/rulesets/src/asyncapi/functions/__tests__/asyncApi2DocumentSchema.test.ts index 2f423a6e8..d530b9930 100644 --- a/packages/rulesets/src/asyncapi/functions/__tests__/asyncApi2DocumentSchema.test.ts +++ b/packages/rulesets/src/asyncapi/functions/__tests__/asyncApi2DocumentSchema.test.ts @@ -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[] = [ diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2DocumentSchema.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2DocumentSchema.ts index 7ab41fba0..810c64d57 100644 --- a/packages/rulesets/src/asyncapi/functions/asyncApi2DocumentSchema.ts +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2DocumentSchema.ts @@ -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'; @@ -92,6 +92,8 @@ function getSerializedSchema(version: AsyncAPISpecVersion): Record): Record | 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): diff --git a/packages/rulesets/src/asyncapi/functions/utils/specs.ts b/packages/rulesets/src/asyncapi/functions/utils/specs.ts index 0142f79e4..274a2481d 100644 --- a/packages/rulesets/src/asyncapi/functions/utils/specs.ts +++ b/packages/rulesets/src/asyncapi/functions/utils/specs.ts @@ -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]; diff --git a/packages/rulesets/src/asyncapi/index.ts b/packages/rulesets/src/asyncapi/index.ts index 7d8a6fc22..94221b93f 100644 --- a/packages/rulesets/src/asyncapi/index.ts +++ b/packages/rulesets/src/asyncapi/index.ts @@ -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, @@ -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.', diff --git a/test-harness/scenarios/asyncapi2-streetlights.scenario b/test-harness/scenarios/asyncapi2-streetlights.scenario index d9b93ee58..e69de29bb 100644 --- a/test-harness/scenarios/asyncapi2-streetlights.scenario +++ b/test-harness/scenarios/asyncapi2-streetlights.scenario @@ -1,228 +0,0 @@ -====test==== -Validate streetlights.yaml AsyncAPI 2.0 sample -====document==== -asyncapi: '2.0.0' -info: - title: Streetlights API - version: '1.0.0' - description: | - The Smartylighting Streetlights API allows you to remotely manage the city lights. - - ### Check out its awesome features: - - * Turn a specific streetlight on/off 🌃 - * Dim a specific streetlight 😎 - * Receive real-time information about environmental lighting conditions 📈 - license: - name: Apache 2.0 - url: https://www.apache.org/licenses/LICENSE-2.0 - -servers: - production: - url: test.mosquitto.org:{port} - protocol: mqtt - description: Test broker - variables: - port: - description: Secure connection (TLS) is available through port 8883. - default: '1883' - enum: - - '1883' - - '8883' - security: - - apiKey: [] - - supportedOauthFlows: - - streetlights:on - - streetlights:off - - streetlights:dim - - openIdConnectWellKnown: [] - -defaultContentType: application/json - -channels: - smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured: - description: The topic on which measured values may be produced and consumed. - parameters: - streetlightId: - $ref: '#/components/parameters/streetlightId' - publish: - summary: Inform about environmental lighting conditions of a particular streetlight. - operationId: receiveLightMeasurement - traits: - - $ref: '#/components/operationTraits/kafka' - message: - $ref: '#/components/messages/lightMeasured' - - smartylighting/streetlights/1/0/action/{streetlightId}/turn/on: - parameters: - streetlightId: - $ref: '#/components/parameters/streetlightId' - subscribe: - operationId: turnOn - traits: - - $ref: '#/components/operationTraits/kafka' - message: - $ref: '#/components/messages/turnOnOff' - - smartylighting/streetlights/1/0/action/{streetlightId}/turn/off: - parameters: - streetlightId: - $ref: '#/components/parameters/streetlightId' - subscribe: - operationId: turnOff - traits: - - $ref: '#/components/operationTraits/kafka' - message: - $ref: '#/components/messages/turnOnOff' - - smartylighting/streetlights/1/0/action/{streetlightId}/dim: - parameters: - streetlightId: - $ref: '#/components/parameters/streetlightId' - subscribe: - operationId: dimLight - traits: - - $ref: '#/components/operationTraits/kafka' - message: - $ref: '#/components/messages/dimLight' - -components: - messages: - lightMeasured: - name: lightMeasured - title: Light measured - summary: Inform about environmental lighting conditions of a particular streetlight. - contentType: application/json - traits: - - $ref: '#/components/messageTraits/commonHeaders' - payload: - $ref: "#/components/schemas/lightMeasuredPayload" - turnOnOff: - name: turnOnOff - title: Turn on/off - summary: Command a particular streetlight to turn the lights on or off. - traits: - - $ref: '#/components/messageTraits/commonHeaders' - payload: - $ref: "#/components/schemas/turnOnOffPayload" - dimLight: - name: dimLight - title: Dim light - summary: Command a particular streetlight to dim the lights. - traits: - - $ref: '#/components/messageTraits/commonHeaders' - payload: - $ref: "#/components/schemas/dimLightPayload" - - schemas: - lightMeasuredPayload: - type: object - properties: - lumens: - type: integer - minimum: 0 - description: Light intensity measured in lumens. - sentAt: - $ref: "#/components/schemas/sentAt" - turnOnOffPayload: - type: object - properties: - command: - type: string - enum: - - on - - off - description: Whether to turn on or off the light. - sentAt: - $ref: "#/components/schemas/sentAt" - dimLightPayload: - type: object - properties: - percentage: - type: integer - description: Percentage to which the light should be dimmed to. - minimum: 0 - maximum: 100 - sentAt: - $ref: "#/components/schemas/sentAt" - sentAt: - type: string - format: date-time - description: Date and time when the message was sent. - - securitySchemes: - apiKey: - type: apiKey - in: user - description: Provide your API key as the user and leave the password empty. - supportedOauthFlows: - type: oauth2 - description: Flows to support OAuth 2.0 - flows: - implicit: - authorizationUrl: 'https://authserver.example/auth' - scopes: - 'streetlights:on': Ability to switch lights on - 'streetlights:off': Ability to switch lights off - 'streetlights:dim': Ability to dim the lights - password: - tokenUrl: 'https://authserver.example/token' - scopes: - 'streetlights:on': Ability to switch lights on - 'streetlights:off': Ability to switch lights off - 'streetlights:dim': Ability to dim the lights - clientCredentials: - tokenUrl: 'https://authserver.example/token' - scopes: - 'streetlights:on': Ability to switch lights on - 'streetlights:off': Ability to switch lights off - 'streetlights:dim': Ability to dim the lights - authorizationCode: - authorizationUrl: 'https://authserver.example/auth' - tokenUrl: 'https://authserver.example/token' - refreshUrl: 'https://authserver.example/refresh' - scopes: - 'streetlights:on': Ability to switch lights on - 'streetlights:off': Ability to switch lights off - 'streetlights:dim': Ability to dim the lights - openIdConnectWellKnown: - type: openIdConnect - openIdConnectUrl: 'https://authserver.example/.well-known' - - parameters: - streetlightId: - description: The ID of the streetlight. - schema: - type: string - - messageTraits: - commonHeaders: - headers: - type: object - properties: - my-app-header: - type: integer - minimum: 0 - maximum: 100 - - operationTraits: - kafka: - bindings: - kafka: - clientId: my-app-id -====asset:ruleset==== -const { asyncapi } = require('@stoplight/spectral-rulesets'); -module.exports = asyncapi; -====command==== -{bin} lint {document} --ruleset "{asset:ruleset}" -====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 - 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 - 68:15 warning asyncapi-operation-description Operation "description" must be present and non-empty string. channels.smartylighting/streetlights/1/0/action/{streetlightId}/turn/off.subscribe - 79:15 warning asyncapi-operation-description Operation "description" must be present and non-empty string. channels.smartylighting/streetlights/1/0/action/{streetlightId}/dim.subscribe - -✖ 7 problems (0 errors, 6 warnings, 1 info, 0 hints) diff --git a/yarn.lock b/yarn.lock index 913c437f8..b6061fee5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27,10 +27,12 @@ __metadata: languageName: node linkType: hard -"@asyncapi/specs@npm:^3.2.0": - version: 3.2.1 - resolution: "@asyncapi/specs@npm:3.2.1" - checksum: 6392d0aaa658905f67553160c725bb0b535e187a120fb2305eb1a223460244a43eea804c00f4985ecbaddb2b04527ce9db51e72117dbe694db547b734f8082dd +"@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 @@ -2566,7 +2568,7 @@ __metadata: languageName: unknown linkType: soft -"@stoplight/spectral-formats@*, @stoplight/spectral-formats@>=1, @stoplight/spectral-formats@^1.0.0, @stoplight/spectral-formats@^1.4.0, @stoplight/spectral-formats@workspace:packages/formats": +"@stoplight/spectral-formats@*, @stoplight/spectral-formats@>=1, @stoplight/spectral-formats@^1.0.0, @stoplight/spectral-formats@^1.5.0, @stoplight/spectral-formats@workspace:packages/formats": version: 0.0.0-use.local resolution: "@stoplight/spectral-formats@workspace:packages/formats" dependencies: @@ -2677,12 +2679,12 @@ __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 "@stoplight/spectral-core": ^1.8.1 - "@stoplight/spectral-formats": ^1.4.0 + "@stoplight/spectral-formats": ^1.5.0 "@stoplight/spectral-functions": ^1.5.1 "@stoplight/spectral-parsers": "*" "@stoplight/spectral-ref-resolver": "*"