From 15a667a9b7b26e944a069f5f0dc5caae05966a71 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Sat, 19 Feb 2022 15:06:24 +0100 Subject: [PATCH 1/2] feat(rulesets): support 2.1.0, 2.2.0, 2.3.0 AsyncAPI versions --- .../formats/src/__tests__/asyncapi.test.ts | 61 +- packages/formats/src/asyncapi.ts | 40 +- packages/rulesets/package.json | 1 + .../asyncapi/__tests__/__helpers__/tester.ts | 4 +- .../__tests__/asyncapi-schema.test.ts | 5 +- .../__tests__/asyncApi2DocumentSchema.test.ts | 349 ++++ .../functions/asyncApi2DocumentSchema.ts | 109 ++ .../functions/asyncApi2PayloadValidation.ts | 9 +- packages/rulesets/src/asyncapi/index.ts | 18 +- .../asyncapi/schemas/schema.asyncapi2.json | 1486 ----------------- yarn.lock | 8 + 11 files changed, 562 insertions(+), 1528 deletions(-) create mode 100644 packages/rulesets/src/asyncapi/functions/__tests__/asyncApi2DocumentSchema.test.ts create mode 100644 packages/rulesets/src/asyncapi/functions/asyncApi2DocumentSchema.ts delete mode 100644 packages/rulesets/src/asyncapi/schemas/schema.asyncapi2.json 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'; diff --git a/packages/rulesets/package.json b/packages/rulesets/package.json index b0225574f..1f5eb63df 100644 --- a/packages/rulesets/package.json +++ b/packages/rulesets/package.json @@ -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", diff --git a/packages/rulesets/src/asyncapi/__tests__/__helpers__/tester.ts b/packages/rulesets/src/asyncapi/__tests__/__helpers__/tester.ts index e23221019..5eaf939aa 100644 --- a/packages/rulesets/src/asyncapi/__tests__/__helpers__/tester.ts +++ b/packages/rulesets/src/asyncapi/__tests__/__helpers__/tester.ts @@ -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 }; diff --git a/packages/rulesets/src/asyncapi/__tests__/asyncapi-schema.test.ts b/packages/rulesets/src/asyncapi/__tests__/asyncapi-schema.test.ts index 02b194ed1..bd67bbc09 100644 --- a/packages/rulesets/src/asyncapi/__tests__/asyncapi-schema.test.ts +++ b/packages/rulesets/src/asyncapi/__tests__/asyncapi-schema.test.ts @@ -14,7 +14,6 @@ testRule('asyncapi-schema', [ }, errors: [], }, - { name: 'channels property is missing', document: { @@ -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 }], }, ]); diff --git a/packages/rulesets/src/asyncapi/functions/__tests__/asyncApi2DocumentSchema.test.ts b/packages/rulesets/src/asyncapi/functions/__tests__/asyncApi2DocumentSchema.test.ts new file mode 100644 index 000000000..b7cd12702 --- /dev/null +++ b/packages/rulesets/src/asyncapi/functions/__tests__/asyncApi2DocumentSchema.test.ts @@ -0,0 +1,349 @@ +import { DiagnosticSeverity } from '@stoplight/types'; +import { Spectral } from '@stoplight/spectral-core'; +import { prepareResults } from '../asyncApi2DocumentSchema'; + +import { ErrorObject } from 'ajv'; +import { createWithRules } from '../../__tests__/__helpers__/tester'; + +describe('asyncApi2DocumentSchema', () => { + let s: Spectral; + + beforeEach(async () => { + s = createWithRules(['asyncapi-schema']); + }); + + describe('given AsyncAPI 2.0.0 document', () => { + test('validate invalid info object', async () => { + expect( + await s.run({ + asyncapi: '2.0.0', + info: { + version: '1.0.1', + description: 'This is a sample server.', + termsOfService: 'http://asyncapi.org/terms/', + }, + channels: { + '/user/signedup': { + subscribe: { + message: { + payload: { + type: 'object', + properties: { + email: { + type: 'string', + format: 'email', + }, + }, + }, + }, + }, + }, + }, + }), + ).toEqual([ + { + code: 'asyncapi-schema', + message: '"info" property must have required property "title".', + path: ['info'], + severity: DiagnosticSeverity.Error, + range: expect.any(Object), + }, + ]); + }); + }); + + describe('given AsyncAPI 2.1.0 document', () => { + test('validate with message examples', async () => { + expect( + await s.run({ + asyncapi: '2.1.0', + info: { + title: 'Signup service example (internal)', + version: '0.1.0', + }, + channels: { + '/user/signedup': { + subscribe: { + message: { + payload: { + type: 'object', + properties: { + email: { + type: 'string', + format: 'email', + }, + }, + }, + examples: [ + { + name: 'Example 1', + summary: 'Example summary for example 1', + payload: { + email: 'bye@foo.bar', + }, + }, + ], + }, + }, + }, + }, + }), + ).toEqual([]); + }); + }); + + describe('given AsyncAPI 2.2.0 document', () => { + test('validate channel with connected server', async () => { + expect( + await s.run({ + asyncapi: '2.2.0', + info: { + title: 'Signup service example (internal)', + version: '0.1.0', + }, + servers: { + kafka: { + url: 'development.gigantic-server.com', + description: 'Development server', + protocol: 'kafka', + protocolVersion: '1.0.0', + }, + }, + channels: { + '/user/signedup': { + servers: [1, 'foobar', 3], + subscribe: { + message: { + payload: { + type: 'object', + properties: { + email: { + type: 'string', + format: 'email', + }, + }, + }, + }, + }, + }, + }, + }), + ).toEqual([ + { + code: 'asyncapi-schema', + message: '"0" property type must be string.', + path: ['channels', '/user/signedup', 'servers', '0'], + severity: DiagnosticSeverity.Error, + range: expect.any(Object), + }, + { + code: 'asyncapi-schema', + message: '"2" property type must be string.', + path: ['channels', '/user/signedup', 'servers', '2'], + severity: DiagnosticSeverity.Error, + range: expect.any(Object), + }, + ]); + }); + }); + + describe('given AsyncAPI 2.3.0 document', () => { + test('validate reusable server', async () => { + expect( + await s.run({ + asyncapi: '2.3.0', + info: { + title: 'Signup service example (internal)', + version: '0.1.0', + }, + channels: { + '/user/signedup': { + subscribe: { + message: { + payload: { + type: 'object', + properties: { + email: { + type: 'string', + format: 'email', + }, + }, + }, + }, + }, + }, + }, + components: { + servers: { + kafka: { + description: 'Development server', + }, + }, + }, + }), + ).toEqual([ + { + code: 'asyncapi-schema', + message: '"kafka" property must have required property "url".', + path: ['components', 'servers', 'kafka'], + severity: DiagnosticSeverity.Error, + range: expect.any(Object), + }, + ]); + }); + }); + + describe('prepareResults', () => { + test('given oneOf error one of which is required $ref property missing, picks only one error', () => { + const errors: ErrorObject[] = [ + { + keyword: 'type', + instancePath: '/paths/test/post/parameters/0/schema/type', + schemaPath: '#/properties/type/type', + params: { type: 'string' }, + message: 'must be string', + }, + { + keyword: 'required', + instancePath: '/paths/test/post/parameters/0/schema', + schemaPath: '#/definitions/Reference/required', + params: { missingProperty: '$ref' }, + message: "must have required property '$ref'", + }, + { + keyword: 'oneOf', + instancePath: '/paths/test/post/parameters/0/schema', + schemaPath: '#/properties/schema/oneOf', + params: { passingSchemas: null }, + message: 'must match exactly one schema in oneOf', + }, + ]; + + prepareResults(errors); + + expect(errors).toStrictEqual([ + { + keyword: 'type', + instancePath: '/paths/test/post/parameters/0/schema/type', + schemaPath: '#/properties/type/type', + params: { type: 'string' }, + message: 'must be string', + }, + ]); + }); + + test('given oneOf error one without any $ref property missing, picks all errors', () => { + const errors: ErrorObject[] = [ + { + keyword: 'type', + instancePath: '/paths/test/post/parameters/0/schema/type', + schemaPath: '#/properties/type/type', + params: { type: 'string' }, + message: 'must be string', + }, + { + keyword: 'type', + instancePath: '/paths/test/post/parameters/1/schema/type', + schemaPath: '#/properties/type/type', + params: { type: 'string' }, + message: 'must be string', + }, + { + keyword: 'oneOf', + instancePath: '/paths/test/post/parameters/0/schema', + schemaPath: '#/properties/schema/oneOf', + params: { passingSchemas: null }, + message: 'must match exactly one schema in oneOf', + }, + ]; + + prepareResults(errors); + + expect(errors).toStrictEqual([ + { + keyword: 'type', + instancePath: '/paths/test/post/parameters/0/schema/type', + schemaPath: '#/properties/type/type', + params: { type: 'string' }, + message: 'must be string', + }, + { + instancePath: '/paths/test/post/parameters/1/schema/type', + keyword: 'type', + message: 'must be string', + params: { + type: 'string', + }, + schemaPath: '#/properties/type/type', + }, + { + instancePath: '/paths/test/post/parameters/0/schema', + keyword: 'oneOf', + message: 'must match exactly one schema in oneOf', + params: { + passingSchemas: null, + }, + schemaPath: '#/properties/schema/oneOf', + }, + ]); + }); + + test('given errors with different data paths, picks all errors', () => { + const errors: ErrorObject[] = [ + { + keyword: 'type', + instancePath: '/paths/test/post/parameters/0/schema/type', + schemaPath: '#/properties/type/type', + params: { type: 'string' }, + message: 'must be string', + }, + { + keyword: 'required', + instancePath: '/paths/foo/post/parameters/0/schema', + schemaPath: '#/definitions/Reference/required', + params: { missingProperty: '$ref' }, + message: "must have required property '$ref'", + }, + { + keyword: 'oneOf', + instancePath: '/paths/baz/post/parameters/0/schema', + schemaPath: '#/properties/schema/oneOf', + params: { passingSchemas: null }, + message: 'must match exactly one schema in oneOf', + }, + ]; + + prepareResults(errors); + + expect(errors).toStrictEqual([ + { + instancePath: '/paths/test/post/parameters/0/schema/type', + keyword: 'type', + message: 'must be string', + params: { + type: 'string', + }, + schemaPath: '#/properties/type/type', + }, + { + instancePath: '/paths/foo/post/parameters/0/schema', + keyword: 'required', + message: "must have required property '$ref'", + params: { + missingProperty: '$ref', + }, + schemaPath: '#/definitions/Reference/required', + }, + { + instancePath: '/paths/baz/post/parameters/0/schema', + keyword: 'oneOf', + message: 'must match exactly one schema in oneOf', + params: { + passingSchemas: null, + }, + schemaPath: '#/properties/schema/oneOf', + }, + ]); + }); + }); +}); diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2DocumentSchema.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2DocumentSchema.ts new file mode 100644 index 000000000..dceae2ed7 --- /dev/null +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2DocumentSchema.ts @@ -0,0 +1,109 @@ +import { createRulesetFunction } from '@stoplight/spectral-core'; +import { schema as schemaFn } from '@stoplight/spectral-functions'; +import { aas2_0, aas2_1, aas2_2, aas2_3 } from '@stoplight/spectral-formats'; + +import type { ErrorObject } from 'ajv'; +import type { IFunctionResult, Format } from '@stoplight/spectral-core'; + +// 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'; + +function shouldIgnoreError(error: ErrorObject): boolean { + return ( + // oneOf is a fairly error as we have 2 options to choose from for most of the time. + error.keyword === 'oneOf' || + // the required $ref is entirely useless, since aas-schema rules operate on resolved content, so there won't be any $refs in the document + (error.keyword === 'required' && error.params.missingProperty === '$ref') + ); +} + +// this is supposed to cover edge cases we need to cover manually, when it's impossible to detect the most appropriate error, i.e. oneOf consisting of more than 3 members, etc. +// note, more errors can be included if certain messages reported by AJV are not quite meaningful +const ERROR_MAP = [ + { + path: /^components\/securitySchemes\/[^/]+$/, + message: 'Invalid security scheme', + }, +]; + +// The function removes irrelevant (aka misleading, confusing, useless, whatever you call it) errors. +// There are a few exceptions, i.e. security components I covered manually, +// yet apart from them we usually deal with a relatively simple scenario that can be literally expressed as: "either proper value of $ref property". +// The $ref part is never going to be interesting for us, because both aas-schema rules operate on resolved content, so we won't have any $refs left. +// As you can see, what we deal here wit is actually not really oneOf anymore - it's always the first member of oneOf we match against. +// That being said, we always strip both oneOf and $ref, since we are always interested in the first error. +export function prepareResults(errors: ErrorObject[]): void { + // Update additionalProperties errors to make them more precise and prevent them from being treated as duplicates + for (const error of errors) { + if (error.keyword === 'additionalProperties') { + error.instancePath = `${error.instancePath}/${String(error.params['additionalProperty'])}`; + } + } + + for (let i = 0; i < errors.length; i++) { + const error = errors[i]; + + if (i + 1 < errors.length && errors[i + 1].instancePath === error.instancePath) { + errors.splice(i + 1, 1); + i--; + } else if (i > 0 && shouldIgnoreError(error) && errors[i - 1].instancePath.startsWith(error.instancePath)) { + errors.splice(i, 1); + i--; + } + } +} + +function applyManualReplacements(errors: IFunctionResult[]): void { + for (const error of errors) { + if (error.path === void 0) continue; + + const joinedPath = error.path.join('/'); + + for (const mappedError of ERROR_MAP) { + if (mappedError.path.test(joinedPath)) { + error.message = mappedError.message; + break; + } + } + } +} + +function getSchema(formats: Set): Record | void { + switch (true) { + case formats.has(aas2_0): + return asyncAPI2_0_0Schema; + case formats.has(aas2_1): + return asyncAPI2_1_0Schema; + case formats.has(aas2_2): + return asyncAPI2_2_0Schema; + case formats.has(aas2_3): + return asyncAPI2_3_0Schema; + default: + return; + } +} + +export default createRulesetFunction( + { + input: null, + options: null, + }, + function oasDocumentSchema(targetVal, _, context) { + const formats = context.document.formats; + if (formats === null || formats === void 0) return; + + const schema = getSchema(formats); + if (schema === void 0) return; + + const errors = schemaFn(targetVal, { allErrors: true, schema, prepareResults }, context); + + if (Array.isArray(errors)) { + applyManualReplacements(errors); + } + + return errors; + }, +); diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2PayloadValidation.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2PayloadValidation.ts index fec61d070..501ccc79f 100644 --- a/packages/rulesets/src/asyncapi/functions/asyncApi2PayloadValidation.ts +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2PayloadValidation.ts @@ -2,10 +2,11 @@ import Ajv from 'ajv'; import addFormats from 'ajv-formats'; import { createRulesetFunction } from '@stoplight/spectral-core'; import betterAjvErrors from '@stoplight/better-ajv-errors'; -import * as asyncApi2Schema from '../schemas/schema.asyncapi2.json'; -const fakeSchemaObjectId = 'asyncapi2#/definitions/schema'; -const asyncApi2SchemaObject = { $ref: fakeSchemaObjectId }; +// use latest AsyncAPI JSON Schema because there are no differences of Schema Object definitions between the 2.X.X. +import * as asyncApi2Schema from '@asyncapi/specs/schemas/2.3.0.json'; + +const asyncApi2SchemaObject = { $ref: 'asyncapi2#/definitions/schema' }; const ajv = new Ajv({ allErrors: true, @@ -14,7 +15,7 @@ const ajv = new Ajv({ addFormats(ajv); -ajv.addSchema(asyncApi2Schema, asyncApi2Schema.$id); +ajv.addSchema(asyncApi2Schema, 'asyncapi2'); const ajvValidationFn = ajv.compile(asyncApi2SchemaObject); diff --git a/packages/rulesets/src/asyncapi/index.ts b/packages/rulesets/src/asyncapi/index.ts index 15c751c03..31ac89905 100644 --- a/packages/rulesets/src/asyncapi/index.ts +++ b/packages/rulesets/src/asyncapi/index.ts @@ -1,4 +1,4 @@ -import { asyncApi2 } from '@stoplight/spectral-formats'; +import { aas2_0, aas2_1, aas2_2, aas2_3 } from '@stoplight/spectral-formats'; import { truthy, pattern, @@ -8,13 +8,13 @@ import { alphabetical, } from '@stoplight/spectral-functions'; +import asyncApi2DocumentSchema from './functions/asyncApi2DocumentSchema'; import asyncApi2SchemaValidation from './functions/asyncApi2SchemaValidation'; import asyncApi2PayloadValidation from './functions/asyncApi2PayloadValidation'; -import * as asyncApi2Schema from './schemas/schema.asyncapi2.json'; export default { documentationUrl: 'https://meta.stoplight.io/docs/spectral/docs/reference/asyncapi-rules.md', - formats: [asyncApi2], + formats: [aas2_0, aas2_1, aas2_2, aas2_3], rules: { 'asyncapi-channel-no-empty-parameter': { description: 'Channel path must not have empty parameter substitution pattern.', @@ -272,18 +272,14 @@ export default { }, }, 'asyncapi-schema': { - description: 'Validate structure of AsyncAPI v2.0.0 Specification.', - message: '{{error}}', - severity: 'error', + description: 'Validate structure of AsyncAPI v2 specification.', + message: '{{error}}.', + severity: 0, recommended: true, type: 'validation', given: '$', then: { - function: schema, - functionOptions: { - allErrors: true, - schema: asyncApi2Schema, - }, + function: asyncApi2DocumentSchema, }, }, 'asyncapi-server-no-empty-variable': { diff --git a/packages/rulesets/src/asyncapi/schemas/schema.asyncapi2.json b/packages/rulesets/src/asyncapi/schemas/schema.asyncapi2.json deleted file mode 100644 index e07443707..000000000 --- a/packages/rulesets/src/asyncapi/schemas/schema.asyncapi2.json +++ /dev/null @@ -1,1486 +0,0 @@ -{ - "title": "AsyncAPI 2.0.0 schema.", - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "asyncapi2", - "type": "object", - "required": ["asyncapi", "info", "channels"], - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "properties": { - "asyncapi": { - "type": "string", - "enum": ["2.0.0"], - "description": "The AsyncAPI specification version of this document." - }, - "id": { - "type": "string", - "description": "A unique id representing the application.", - "format": "uri" - }, - "info": { - "$ref": "#/definitions/info" - }, - "servers": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/server" - } - }, - "defaultContentType": { - "type": "string" - }, - "channels": { - "$ref": "#/definitions/channels" - }, - "components": { - "$ref": "#/definitions/components" - }, - "tags": { - "type": "array", - "items": { - "$ref": "#/definitions/tag" - }, - "uniqueItems": true - }, - "externalDocs": { - "$ref": "#/definitions/externalDocs" - } - }, - "definitions": { - "Reference": { - "type": "object", - "required": ["$ref"], - "properties": { - "$ref": { - "$ref": "#/definitions/ReferenceObject" - } - } - }, - "ReferenceObject": { - "type": "string", - "format": "uri-reference" - }, - "info": { - "type": "object", - "description": "General information about the API.", - "required": ["version", "title"], - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "properties": { - "title": { - "type": "string", - "description": "A unique and precise title of the API." - }, - "version": { - "type": "string", - "description": "A semantic version number of the API." - }, - "description": { - "type": "string", - "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." - }, - "termsOfService": { - "type": "string", - "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", - "format": "uri" - }, - "contact": { - "$ref": "#/definitions/contact" - }, - "license": { - "$ref": "#/definitions/license" - } - } - }, - "contact": { - "type": "object", - "description": "Contact information for the owners of the API.", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "description": "The identifying name of the contact person/organization." - }, - "url": { - "type": "string", - "description": "The URL pointing to the contact information.", - "format": "uri" - }, - "email": { - "type": "string", - "description": "The email address of the contact person/organization.", - "format": "email" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - } - }, - "license": { - "type": "object", - "required": ["name"], - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "description": "The name of the license type. It's encouraged to use an OSI compatible license." - }, - "url": { - "type": "string", - "description": "The URL pointing to the license.", - "format": "uri" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - } - }, - "server": { - "type": "object", - "description": "An object representing a Server.", - "required": ["url", "protocol"], - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "properties": { - "url": { - "type": "string" - }, - "description": { - "type": "string" - }, - "protocol": { - "type": "string", - "description": "The transfer protocol." - }, - "protocolVersion": { - "type": "string" - }, - "variables": { - "$ref": "#/definitions/serverVariables" - }, - "security": { - "type": "array", - "items": { - "$ref": "#/definitions/SecurityRequirement" - } - }, - "bindings": { - "$ref": "#/definitions/bindingsObject" - } - } - }, - "serverVariables": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/serverVariable" - } - }, - "serverVariable": { - "type": "object", - "description": "An object representing a Server Variable for server URL template substitution.", - "minProperties": 1, - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "properties": { - "enum": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "default": { - "type": "string" - }, - "description": { - "type": "string" - }, - "examples": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "channels": { - "type": "object", - "propertyNames": { - "type": "string", - "format": "uri-template", - "minLength": 1 - }, - "additionalProperties": { - "$ref": "#/definitions/channelItem" - } - }, - "components": { - "type": "object", - "description": "An object to hold a set of reusable objects for different aspects of the AsyncAPI Specification.", - "additionalProperties": false, - "properties": { - "schemas": { - "$ref": "#/definitions/schemas" - }, - "messages": { - "$ref": "#/definitions/messages" - }, - "securitySchemes": { - "type": "object", - "patternProperties": { - "^[\\w\\d.\\-_]+$": { - "oneOf": [ - { - "$ref": "#/definitions/Reference" - }, - { - "$ref": "#/definitions/SecurityScheme" - } - ] - } - } - }, - "parameters": { - "$ref": "#/definitions/parameters" - }, - "correlationIds": { - "type": "object", - "patternProperties": { - "^[\\w\\d.\\-_]+$": { - "oneOf": [ - { - "$ref": "#/definitions/Reference" - }, - { - "$ref": "#/definitions/correlationId" - } - ] - } - } - }, - "operationTraits": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/operationTrait" - } - }, - "messageTraits": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/messageTrait" - } - }, - "serverBindings": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/bindingsObject" - } - }, - "channelBindings": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/bindingsObject" - } - }, - "operationBindings": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/bindingsObject" - } - }, - "messageBindings": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/bindingsObject" - } - } - } - }, - "schemas": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/schema" - }, - "description": "JSON objects describing schemas the API uses." - }, - "messages": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/message" - }, - "description": "JSON objects describing the messages being consumed and produced by the API." - }, - "parameters": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/parameter" - }, - "description": "JSON objects describing re-usable channel parameters." - }, - "schema": { - "allOf": [ - { - "definitions": { - "schemaArray": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/definitions/schema/allOf/0" - } - }, - "nonNegativeInteger": { - "type": "integer", - "minimum": 0 - }, - "nonNegativeIntegerDefault0": { - "allOf": [ - { - "$ref": "#/definitions/schema/allOf/0/definitions/nonNegativeInteger" - }, - { - "default": 0 - } - ] - }, - "simpleTypes": { - "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] - }, - "stringArray": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true, - "default": [] - } - }, - "type": ["object", "boolean"], - "properties": { - "$id": { - "type": "string", - "format": "uri-reference" - }, - "$schema": { - "type": "string", - "format": "uri" - }, - "$ref": { - "type": "string", - "format": "uri-reference" - }, - "$comment": { - "type": "string" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "default": true, - "readOnly": { - "type": "boolean", - "default": false - }, - "writeOnly": { - "type": "boolean", - "default": false - }, - "examples": { - "type": "array", - "items": true - }, - "multipleOf": { - "type": "number", - "exclusiveMinimum": 0 - }, - "maximum": { - "type": "number" - }, - "exclusiveMaximum": { - "type": "number" - }, - "minimum": { - "type": "number" - }, - "exclusiveMinimum": { - "type": "number" - }, - "maxLength": { - "$ref": "#/definitions/schema/allOf/0/definitions/nonNegativeInteger" - }, - "minLength": { - "$ref": "#/definitions/schema/allOf/0/definitions/nonNegativeIntegerDefault0" - }, - "pattern": { - "type": "string", - "format": "regex" - }, - "additionalItems": { - "$ref": "#/definitions/schema/allOf/0" - }, - "items": { - "anyOf": [ - { - "$ref": "#/definitions/schema/allOf/0" - }, - { - "$ref": "#/definitions/schema/allOf/0/definitions/schemaArray" - } - ], - "default": true - }, - "maxItems": { - "$ref": "#/definitions/schema/allOf/0/definitions/nonNegativeInteger" - }, - "minItems": { - "$ref": "#/definitions/schema/allOf/0/definitions/nonNegativeIntegerDefault0" - }, - "uniqueItems": { - "type": "boolean", - "default": false - }, - "contains": { - "$ref": "#/definitions/schema/allOf/0" - }, - "maxProperties": { - "$ref": "#/definitions/schema/allOf/0/definitions/nonNegativeInteger" - }, - "minProperties": { - "$ref": "#/definitions/schema/allOf/0/definitions/nonNegativeIntegerDefault0" - }, - "required": { - "$ref": "#/definitions/schema/allOf/0/definitions/stringArray" - }, - "additionalProperties": { - "$ref": "#/definitions/schema/allOf/0" - }, - "definitions": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/schema/allOf/0" - }, - "default": {} - }, - "properties": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/schema/allOf/0" - }, - "default": {} - }, - "patternProperties": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/schema/allOf/0" - }, - "propertyNames": { - "format": "regex" - }, - "default": {} - }, - "dependencies": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "$ref": "#/definitions/schema/allOf/0" - }, - { - "$ref": "#/definitions/schema/allOf/0/definitions/stringArray" - } - ] - } - }, - "propertyNames": { - "$ref": "#/definitions/schema/allOf/0" - }, - "const": true, - "enum": { - "type": "array", - "items": true, - "minItems": 1, - "uniqueItems": true - }, - "type": { - "anyOf": [ - { - "$ref": "#/definitions/schema/allOf/0/definitions/simpleTypes" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/schema/allOf/0/definitions/simpleTypes" - }, - "minItems": 1, - "uniqueItems": true - } - ] - }, - "format": { - "type": "string" - }, - "contentMediaType": { - "type": "string" - }, - "contentEncoding": { - "type": "string" - }, - "if": { - "$ref": "#/definitions/schema/allOf/0" - }, - "then": { - "$ref": "#/definitions/schema/allOf/0" - }, - "else": { - "$ref": "#/definitions/schema/allOf/0" - }, - "allOf": { - "$ref": "#/definitions/schema/allOf/0/definitions/schemaArray" - }, - "anyOf": { - "$ref": "#/definitions/schema/allOf/0/definitions/schemaArray" - }, - "oneOf": { - "$ref": "#/definitions/schema/allOf/0/definitions/schemaArray" - }, - "not": { - "$ref": "#/definitions/schema/allOf/0" - } - }, - "default": true - }, - { - "type": "object", - "patternProperties": { - "c": { - "$ref": "#/definitions/specificationExtension" - } - }, - "properties": { - "additionalProperties": { - "anyOf": [ - { - "$ref": "#/definitions/schema" - }, - { - "type": "boolean" - } - ], - "default": {} - }, - "items": { - "anyOf": [ - { - "$ref": "#/definitions/schema" - }, - { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/definitions/schema" - } - } - ], - "default": {} - }, - "allOf": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/definitions/schema" - } - }, - "oneOf": { - "type": "array", - "minItems": 2, - "items": { - "$ref": "#/definitions/schema" - } - }, - "anyOf": { - "type": "array", - "minItems": 2, - "items": { - "$ref": "#/definitions/schema" - } - }, - "not": { - "$ref": "#/definitions/schema" - }, - "properties": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/schema" - }, - "default": {} - }, - "patternProperties": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/schema" - }, - "default": {} - }, - "propertyNames": { - "$ref": "#/definitions/schema" - }, - "contains": { - "$ref": "#/definitions/schema" - }, - "discriminator": { - "type": "string" - }, - "externalDocs": { - "$ref": "#/definitions/externalDocs" - }, - "deprecated": { - "type": "boolean", - "default": false - } - } - } - ] - }, - "externalDocs": { - "type": "object", - "additionalProperties": false, - "description": "information about external documentation", - "required": ["url"], - "properties": { - "description": { - "type": "string" - }, - "url": { - "type": "string", - "format": "uri" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - } - }, - "channelItem": { - "type": "object", - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "minProperties": 1, - "properties": { - "$ref": { - "$ref": "#/definitions/ReferenceObject" - }, - "parameters": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/parameter" - } - }, - "description": { - "type": "string", - "description": "A description of the channel." - }, - "publish": { - "$ref": "#/definitions/operation" - }, - "subscribe": { - "$ref": "#/definitions/operation" - }, - "deprecated": { - "type": "boolean", - "default": false - }, - "bindings": { - "$ref": "#/definitions/bindingsObject" - } - } - }, - "parameter": { - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "properties": { - "description": { - "type": "string", - "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." - }, - "schema": { - "$ref": "#/definitions/schema" - }, - "location": { - "type": "string", - "description": "A runtime expression that specifies the location of the parameter value", - "pattern": "^\\$message\\.(header|payload)#(/(([^/~])|(~[01]))*)*" - }, - "$ref": { - "$ref": "#/definitions/ReferenceObject" - } - } - }, - "operation": { - "type": "object", - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "properties": { - "traits": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/definitions/Reference" - }, - { - "$ref": "#/definitions/operationTrait" - }, - { - "type": "array", - "items": [ - { - "oneOf": [ - { - "$ref": "#/definitions/Reference" - }, - { - "$ref": "#/definitions/operationTrait" - } - ] - }, - { - "type": "object", - "additionalItems": true - } - ] - } - ] - } - }, - "summary": { - "type": "string" - }, - "description": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "$ref": "#/definitions/tag" - }, - "uniqueItems": true - }, - "externalDocs": { - "$ref": "#/definitions/externalDocs" - }, - "operationId": { - "type": "string" - }, - "bindings": { - "$ref": "#/definitions/bindingsObject" - }, - "message": { - "$ref": "#/definitions/message" - } - } - }, - "message": { - "oneOf": [ - { - "$ref": "#/definitions/Reference" - }, - { - "oneOf": [ - { - "type": "object", - "required": ["oneOf"], - "additionalProperties": false, - "properties": { - "oneOf": { - "type": "array", - "items": { - "$ref": "#/definitions/message" - } - } - } - }, - { - "type": "object", - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "properties": { - "schemaFormat": { - "type": "string" - }, - "contentType": { - "type": "string" - }, - "headers": { - "$ref": "#/definitions/schema" - }, - "payload": {}, - "correlationId": { - "oneOf": [ - { - "$ref": "#/definitions/Reference" - }, - { - "$ref": "#/definitions/correlationId" - } - ] - }, - "tags": { - "type": "array", - "items": { - "$ref": "#/definitions/tag" - }, - "uniqueItems": true - }, - "summary": { - "type": "string", - "description": "A brief summary of the message." - }, - "name": { - "type": "string", - "description": "Name of the message." - }, - "title": { - "type": "string", - "description": "A human-friendly title for the message." - }, - "description": { - "type": "string", - "description": "A longer description of the message. CommonMark is allowed." - }, - "externalDocs": { - "$ref": "#/definitions/externalDocs" - }, - "deprecated": { - "type": "boolean", - "default": false - }, - "examples": { - "type": "array", - "items": { - "type": "object" - } - }, - "bindings": { - "$ref": "#/definitions/bindingsObject" - }, - "traits": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/definitions/Reference" - }, - { - "$ref": "#/definitions/messageTrait" - }, - { - "type": "array", - "items": [ - { - "oneOf": [ - { - "$ref": "#/definitions/Reference" - }, - { - "$ref": "#/definitions/messageTrait" - } - ] - }, - { - "type": "object", - "additionalItems": true - } - ] - } - ] - } - } - } - } - ] - } - ] - }, - "bindingsObject": { - "type": "object", - "additionalProperties": true, - "properties": { - "http": {}, - "ws": {}, - "amqp": {}, - "amqp1": {}, - "mqtt": {}, - "mqtt5": {}, - "kafka": {}, - "nats": {}, - "jms": {}, - "sns": {}, - "sqs": {}, - "stomp": {}, - "redis": {} - } - }, - "correlationId": { - "type": "object", - "required": ["location"], - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "properties": { - "description": { - "type": "string", - "description": "A optional description of the correlation ID. GitHub Flavored Markdown is allowed." - }, - "location": { - "type": "string", - "description": "A runtime expression that specifies the location of the correlation ID", - "pattern": "^\\$message\\.(header|payload)#(/(([^/~])|(~[01]))*)*" - } - } - }, - "specificationExtension": { - "description": "Any property starting with x- is valid.", - "additionalProperties": true, - "additionalItems": true - }, - "tag": { - "type": "object", - "additionalProperties": false, - "required": ["name"], - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "externalDocs": { - "$ref": "#/definitions/externalDocs" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - } - }, - "operationTrait": { - "type": "object", - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "properties": { - "summary": { - "type": "string" - }, - "description": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "$ref": "#/definitions/tag" - }, - "uniqueItems": true - }, - "externalDocs": { - "$ref": "#/definitions/externalDocs" - }, - "operationId": { - "type": "string" - }, - "bindings": { - "$ref": "#/definitions/bindingsObject" - } - } - }, - "messageTrait": { - "type": "object", - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "properties": { - "schemaFormat": { - "type": "string" - }, - "contentType": { - "type": "string" - }, - "headers": { - "oneOf": [ - { - "$ref": "#/definitions/Reference" - }, - { - "$ref": "#/definitions/schema" - } - ] - }, - "correlationId": { - "oneOf": [ - { - "$ref": "#/definitions/Reference" - }, - { - "$ref": "#/definitions/correlationId" - } - ] - }, - "tags": { - "type": "array", - "items": { - "$ref": "#/definitions/tag" - }, - "uniqueItems": true - }, - "summary": { - "type": "string", - "description": "A brief summary of the message." - }, - "name": { - "type": "string", - "description": "Name of the message." - }, - "title": { - "type": "string", - "description": "A human-friendly title for the message." - }, - "description": { - "type": "string", - "description": "A longer description of the message. CommonMark is allowed." - }, - "externalDocs": { - "$ref": "#/definitions/externalDocs" - }, - "deprecated": { - "type": "boolean", - "default": false - }, - "examples": { - "type": "array", - "items": { - "type": "object" - } - }, - "bindings": { - "$ref": "#/definitions/bindingsObject" - } - } - }, - "SecurityScheme": { - "oneOf": [ - { - "$ref": "#/definitions/userPassword" - }, - { - "$ref": "#/definitions/apiKey" - }, - { - "$ref": "#/definitions/X509" - }, - { - "$ref": "#/definitions/symmetricEncryption" - }, - { - "$ref": "#/definitions/asymmetricEncryption" - }, - { - "$ref": "#/definitions/HTTPSecurityScheme" - }, - { - "$ref": "#/definitions/oauth2Flows" - }, - { - "$ref": "#/definitions/openIdConnect" - } - ] - }, - "userPassword": { - "type": "object", - "required": ["type"], - "properties": { - "type": { - "type": "string", - "enum": ["userPassword"] - }, - "description": { - "type": "string" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "additionalProperties": false - }, - "apiKey": { - "type": "object", - "required": ["type", "in"], - "properties": { - "type": { - "type": "string", - "enum": ["apiKey"] - }, - "in": { - "type": "string", - "enum": ["user", "password"] - }, - "description": { - "type": "string" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "additionalProperties": false - }, - "X509": { - "type": "object", - "required": ["type"], - "properties": { - "type": { - "type": "string", - "enum": ["X509"] - }, - "description": { - "type": "string" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "additionalProperties": false - }, - "symmetricEncryption": { - "type": "object", - "required": ["type"], - "properties": { - "type": { - "type": "string", - "enum": ["symmetricEncryption"] - }, - "description": { - "type": "string" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "additionalProperties": false - }, - "asymmetricEncryption": { - "type": "object", - "required": ["type"], - "properties": { - "type": { - "type": "string", - "enum": ["asymmetricEncryption"] - }, - "description": { - "type": "string" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "additionalProperties": false - }, - "HTTPSecurityScheme": { - "oneOf": [ - { - "$ref": "#/definitions/NonBearerHTTPSecurityScheme" - }, - { - "$ref": "#/definitions/BearerHTTPSecurityScheme" - }, - { - "$ref": "#/definitions/APIKeyHTTPSecurityScheme" - } - ] - }, - "NonBearerHTTPSecurityScheme": { - "not": { - "type": "object", - "properties": { - "scheme": { - "type": "string", - "enum": ["bearer"] - } - } - }, - "type": "object", - "required": ["scheme", "type"], - "properties": { - "scheme": { - "type": "string" - }, - "description": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["http"] - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "additionalProperties": false - }, - "BearerHTTPSecurityScheme": { - "type": "object", - "required": ["type", "scheme"], - "properties": { - "scheme": { - "type": "string", - "enum": ["bearer"] - }, - "bearerFormat": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["http"] - }, - "description": { - "type": "string" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "additionalProperties": false - }, - "APIKeyHTTPSecurityScheme": { - "type": "object", - "required": ["type", "name", "in"], - "properties": { - "type": { - "type": "string", - "enum": ["httpApiKey"] - }, - "name": { - "type": "string" - }, - "in": { - "type": "string", - "enum": ["header", "query", "cookie"] - }, - "description": { - "type": "string" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "additionalProperties": false - }, - "oauth2Flows": { - "type": "object", - "required": ["type", "flows"], - "properties": { - "type": { - "type": "string", - "enum": ["oauth2"] - }, - "description": { - "type": "string" - }, - "flows": { - "type": "object", - "properties": { - "implicit": { - "allOf": [ - { - "$ref": "#/definitions/oauth2Flow" - }, - { - "required": ["authorizationUrl", "scopes"] - }, - { - "not": { - "required": ["tokenUrl"] - } - } - ] - }, - "password": { - "allOf": [ - { - "$ref": "#/definitions/oauth2Flow" - }, - { - "required": ["tokenUrl", "scopes"] - }, - { - "not": { - "required": ["authorizationUrl"] - } - } - ] - }, - "clientCredentials": { - "allOf": [ - { - "$ref": "#/definitions/oauth2Flow" - }, - { - "required": ["tokenUrl", "scopes"] - }, - { - "not": { - "required": ["authorizationUrl"] - } - } - ] - }, - "authorizationCode": { - "allOf": [ - { - "$ref": "#/definitions/oauth2Flow" - }, - { - "required": ["authorizationUrl", "tokenUrl", "scopes"] - } - ] - } - }, - "additionalProperties": false, - "minProperties": 1 - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - } - }, - "oauth2Flow": { - "type": "object", - "properties": { - "authorizationUrl": { - "type": "string", - "format": "uri" - }, - "tokenUrl": { - "type": "string", - "format": "uri" - }, - "refreshUrl": { - "type": "string", - "format": "uri" - }, - "scopes": { - "$ref": "#/definitions/oauth2Scopes" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "additionalProperties": false - }, - "oauth2Scopes": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "openIdConnect": { - "type": "object", - "required": ["type", "openIdConnectUrl"], - "properties": { - "type": { - "type": "string", - "enum": ["openIdConnect"] - }, - "description": { - "type": "string" - }, - "openIdConnectUrl": { - "type": "string", - "format": "uri" - } - }, - "patternProperties": { - "^x-[\\w\\d.\\-_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "additionalProperties": false - }, - "SecurityRequirement": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - } - } - } -} diff --git a/yarn.lock b/yarn.lock index 7759bdfc7..f41852192 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,6 +17,13 @@ __metadata: languageName: node linkType: hard +"@asyncapi/specs@npm:^2.13.0": + version: 2.13.0 + resolution: "@asyncapi/specs@npm:2.13.0" + checksum: 94355c96ac2562bfd9118a3e33dd36359196d070684da952f6b0f800b588b426d012fbf96f85b7341ec74f401e3487934b92e43f48e086fa956eab29b90ab694 + languageName: node + linkType: hard + "@babel/code-frame@npm:7.12.11": version: 7.12.11 resolution: "@babel/code-frame@npm:7.12.11" @@ -2495,6 +2502,7 @@ __metadata: version: 0.0.0-use.local resolution: "@stoplight/spectral-rulesets@workspace:packages/rulesets" dependencies: + "@asyncapi/specs": ^2.13.0 "@stoplight/better-ajv-errors": 1.0.1 "@stoplight/json": ^3.17.0 "@stoplight/path": ^1.3.2 From 78885c51203939ae6c24c8d9b60a93c3b5db8dc3 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Sat, 19 Feb 2022 15:27:50 +0100 Subject: [PATCH 2/2] fix(rulesets): switch to old shape of asyncapi-schema rule --- .../src/asyncapi/__tests__/asyncapi-schema.test.ts | 2 +- .../functions/__tests__/asyncApi2DocumentSchema.test.ts | 8 ++++---- packages/rulesets/src/asyncapi/index.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/rulesets/src/asyncapi/__tests__/asyncapi-schema.test.ts b/packages/rulesets/src/asyncapi/__tests__/asyncapi-schema.test.ts index bd67bbc09..0385bfb49 100644 --- a/packages/rulesets/src/asyncapi/__tests__/asyncapi-schema.test.ts +++ b/packages/rulesets/src/asyncapi/__tests__/asyncapi-schema.test.ts @@ -23,6 +23,6 @@ testRule('asyncapi-schema', [ version: '1.0', }, }, - errors: [{ message: 'Object must have required property "channels".', severity: DiagnosticSeverity.Error }], + errors: [{ message: 'Object must have required property "channels"', severity: DiagnosticSeverity.Error }], }, ]); diff --git a/packages/rulesets/src/asyncapi/functions/__tests__/asyncApi2DocumentSchema.test.ts b/packages/rulesets/src/asyncapi/functions/__tests__/asyncApi2DocumentSchema.test.ts index b7cd12702..2d8a80c47 100644 --- a/packages/rulesets/src/asyncapi/functions/__tests__/asyncApi2DocumentSchema.test.ts +++ b/packages/rulesets/src/asyncapi/functions/__tests__/asyncApi2DocumentSchema.test.ts @@ -43,7 +43,7 @@ describe('asyncApi2DocumentSchema', () => { ).toEqual([ { code: 'asyncapi-schema', - message: '"info" property must have required property "title".', + message: '"info" property must have required property "title"', path: ['info'], severity: DiagnosticSeverity.Error, range: expect.any(Object), @@ -131,14 +131,14 @@ describe('asyncApi2DocumentSchema', () => { ).toEqual([ { code: 'asyncapi-schema', - message: '"0" property type must be string.', + message: '"0" property type must be string', path: ['channels', '/user/signedup', 'servers', '0'], severity: DiagnosticSeverity.Error, range: expect.any(Object), }, { code: 'asyncapi-schema', - message: '"2" property type must be string.', + message: '"2" property type must be string', path: ['channels', '/user/signedup', 'servers', '2'], severity: DiagnosticSeverity.Error, range: expect.any(Object), @@ -184,7 +184,7 @@ describe('asyncApi2DocumentSchema', () => { ).toEqual([ { code: 'asyncapi-schema', - message: '"kafka" property must have required property "url".', + message: '"kafka" property must have required property "url"', path: ['components', 'servers', 'kafka'], severity: DiagnosticSeverity.Error, range: expect.any(Object), diff --git a/packages/rulesets/src/asyncapi/index.ts b/packages/rulesets/src/asyncapi/index.ts index 31ac89905..f6e83d7e8 100644 --- a/packages/rulesets/src/asyncapi/index.ts +++ b/packages/rulesets/src/asyncapi/index.ts @@ -273,8 +273,8 @@ export default { }, 'asyncapi-schema': { description: 'Validate structure of AsyncAPI v2 specification.', - message: '{{error}}.', - severity: 0, + message: '{{error}}', + severity: 'error', recommended: true, type: 'validation', given: '$',