From 7661dd94d6940f2023f0cad8a8af6f9e1dfbae94 Mon Sep 17 00:00:00 2001 From: Sergio Moya <1083296+smoya@users.noreply.github.com> Date: Mon, 18 Jul 2022 13:54:22 +0200 Subject: [PATCH 1/7] refactor: add AsyncAPI Schema Parser --- package-lock.json | 246 ++++-------------- package.json | 2 + src/models/schema.ts | 2 +- src/schema-parser/asyncapi-schema-parser.ts | 58 ++++- src/schema-parser/index.ts | 8 +- src/types.ts | 4 +- .../asyncapi-schema-parser.spec.ts | 89 +++++++ 7 files changed, 208 insertions(+), 201 deletions(-) create mode 100644 test/schema-parser/asyncapi-schema-parser.spec.ts diff --git a/package-lock.json b/package-lock.json index 5d8c96516..545cb2601 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,8 @@ "@stoplight/spectral-functions": "^1.5.1", "@stoplight/spectral-parsers": "^1.0.1", "@stoplight/spectral-rulesets": "^1.4.3", + "@types/json-schema": "^7.0.11", + "ajv": "^8.11.0", "jsonpath-plus": "^6.0.1", "lodash": "^4.17.21" }, @@ -2072,21 +2074,6 @@ "ajv": ">=8" } }, - "node_modules/@stoplight/spectral-core/node_modules/ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/@stoplight/spectral-core/node_modules/ajv-errors": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", @@ -2095,11 +2082,6 @@ "ajv": "^8.0.1" } }, - "node_modules/@stoplight/spectral-core/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/@stoplight/spectral-formats": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@stoplight/spectral-formats/-/spectral-formats-1.1.0.tgz", @@ -2151,21 +2133,6 @@ "ajv": ">=8" } }, - "node_modules/@stoplight/spectral-functions/node_modules/ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/@stoplight/spectral-functions/node_modules/ajv-draft-04": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", @@ -2187,11 +2154,6 @@ "ajv": "^8.0.1" } }, - "node_modules/@stoplight/spectral-functions/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/@stoplight/spectral-parsers": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stoplight/spectral-parsers/-/spectral-parsers-1.0.1.tgz", @@ -2275,21 +2237,6 @@ "ajv": ">=8" } }, - "node_modules/@stoplight/spectral-rulesets/node_modules/ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/@stoplight/spectral-rulesets/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2467,9 +2414,9 @@ "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "node_modules/@types/lodash": { "version": "4.14.179", @@ -2637,14 +2584,13 @@ } }, "node_modules/ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", - "dev": true, + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dependencies": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { @@ -2668,22 +2614,7 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "node_modules/ajv/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" @@ -4231,6 +4162,22 @@ "@babel/highlight": "^7.10.4" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", @@ -14406,28 +14353,6 @@ "node": ">=10.0.0" } }, - "node_modules/table/node_modules/ajv": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz", - "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -16755,27 +16680,11 @@ "leven": "^3.1.0" } }, - "ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-errors": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", "requires": {} - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" } } }, @@ -16818,17 +16727,6 @@ "leven": "^3.1.0" } }, - "ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-draft-04": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", @@ -16840,11 +16738,6 @@ "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", "requires": {} - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" } } }, @@ -16915,17 +16808,6 @@ "leven": "^3.1.0" } }, - "ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -17093,9 +16975,9 @@ "dev": true }, "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "@types/lodash": { "version": "4.14.179", @@ -17238,15 +17120,21 @@ } }, "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", - "dev": true, + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "requires": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" + }, + "dependencies": { + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } } }, "ajv-formats": { @@ -17255,24 +17143,6 @@ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "requires": { "ajv": "^8.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - } } }, "ansi-escapes": { @@ -18416,6 +18286,18 @@ "@babel/highlight": "^7.10.4" } }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", @@ -26218,26 +26100,6 @@ "slice-ansi": "^4.0.0", "string-width": "^4.2.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz", - "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } } }, "temp-dir": { diff --git a/package.json b/package.json index 4764f54be..c6a4f0d24 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,8 @@ "@stoplight/spectral-functions": "^1.5.1", "@stoplight/spectral-parsers": "^1.0.1", "@stoplight/spectral-rulesets": "^1.4.3", + "@types/json-schema": "^7.0.11", + "ajv": "^8.11.0", "jsonpath-plus": "^6.0.1", "lodash": "^4.17.21" }, diff --git a/src/models/schema.ts b/src/models/schema.ts index ec48a7788..81ec0008a 100644 --- a/src/models/schema.ts +++ b/src/models/schema.ts @@ -53,4 +53,4 @@ export interface SchemaInterface extends BaseModel, ExtensionsMixinInterface, Ex type(): string | Array | undefined; uniqueItems(): boolean | undefined; writeOnly(): boolean | undefined; -} +} \ No newline at end of file diff --git a/src/schema-parser/asyncapi-schema-parser.ts b/src/schema-parser/asyncapi-schema-parser.ts index a75ef58a5..f40dc7c21 100644 --- a/src/schema-parser/asyncapi-schema-parser.ts +++ b/src/schema-parser/asyncapi-schema-parser.ts @@ -1,4 +1,11 @@ -import { SchemaParser } from "../schema-parser"; +import { SchemaParser, ParseSchemaInput, ValidateSchemaInput } from "../schema-parser"; +import Ajv from "ajv"; +import { JSONSchema7 } from "json-schema" +import type { AsyncAPISchema, SchemaValidateError } from '../types'; + +const ajv = new Ajv({ + allErrors: true, +}) export function AsyncAPISchemaParser(): SchemaParser { return { @@ -8,12 +15,57 @@ export function AsyncAPISchemaParser(): SchemaParser { } } -function validate() { +async function validate(input: ValidateSchemaInput): Promise { + const schema = input.data as JSONSchema7; + let errors: SchemaValidateError[] = []; + + try { + ajv.compile(schema); + } catch (error: any) { + if (error! instanceof Error) { + errors = AjvToSpectralErrors(error); + } else { + // Unknown and unexpected error + throw error; + } + } + return errors; } -function parse() { +function AjvToSpectralErrors(error: Error): SchemaValidateError[] { + let errors: SchemaValidateError[] = []; + let errorMessage = error.message; + + // Validation errors + const validationErrorPrefix = "schema is invalid: "; + if (error.message.startsWith(validationErrorPrefix)) { + // remove prefix + errorMessage = errorMessage.substring(validationErrorPrefix.length); + + // message can contain multiple validation errors separated by ',' (comma) + errorMessage.split(", ").forEach((message: string) => { + const path = message.slice(0, message.indexOf(" ")); + const error = message.slice(message.indexOf(" ") + 1); + const resultErr: SchemaValidateError = { + message: error, + path: path.split("/") + }; + errors.push(resultErr); + }); + } else { + // Not a validation error + const resultErr: SchemaValidateError = { + message: error.message, + }; + errors.push(resultErr); + } + + return errors; +} +async function parse(input: ParseSchemaInput): Promise { + return input.data as JSONSchema7; } function getMimeTypes() { diff --git a/src/schema-parser/index.ts b/src/schema-parser/index.ts index 3966782fe..472542a9a 100644 --- a/src/schema-parser/index.ts +++ b/src/schema-parser/index.ts @@ -1,5 +1,5 @@ import type { Parser } from '../parser'; -import type { DetailedAsyncAPI, SchemaValidateResult } from '../types'; +import type { AsyncAPISchema, DetailedAsyncAPI, SchemaValidateError } from '../types'; export interface ValidateSchemaInput { readonly asyncapi: DetailedAsyncAPI; @@ -20,12 +20,12 @@ export interface ParseSchemaInput { } export interface SchemaParser { - validate: (input: ValidateSchemaInput) => void | SchemaValidateResult[] | Promise; - parse: (input: ParseSchemaInput) => unknown | Promise; + validate: (input: ValidateSchemaInput) => void | SchemaValidateError[] | Promise; + parse: (input: ParseSchemaInput) => AsyncAPISchema | Promise; getMimeTypes: () => Array; } -export async function validateSchema(parser: Parser, input: ParseSchemaInput) { +export async function validateSchema(parser: Parser, input: ValidateSchemaInput) { const schemaParser = parser.parserRegistry.get(input.schemaFormat); if (schemaParser === undefined) { // throw appropriate error diff --git a/src/types.ts b/src/types.ts index 94f03e353..abeb61647 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import type { ISpectralDiagnostic, IFunctionResult } from '@stoplight/spectral-core'; +import { JSONSchema7 }from "json-schema" export type MaybeAsyncAPI = { asyncapi: string } & Record; export interface AsyncAPISemver { @@ -16,4 +17,5 @@ export interface DetailedAsyncAPI { } export type Diagnostic = ISpectralDiagnostic; -export type SchemaValidateResult = IFunctionResult; +export type SchemaValidateError = IFunctionResult; +export type AsyncAPISchema = JSONSchema7; diff --git a/test/schema-parser/asyncapi-schema-parser.spec.ts b/test/schema-parser/asyncapi-schema-parser.spec.ts new file mode 100644 index 000000000..fa7cb54c7 --- /dev/null +++ b/test/schema-parser/asyncapi-schema-parser.spec.ts @@ -0,0 +1,89 @@ +import { ParseSchemaInput, ValidateSchemaInput } from '../../src/schema-parser/index'; +import { AsyncAPISchemaParser } from '../../src/schema-parser/asyncapi-schema-parser'; +import { SchemaValidateError } from '../../src/types'; + +describe('AsyncAPISchemaParser', function () { + + const validSchema = { + asyncapi: { + semver: { + major: 2 + } + }, + data: { + type: "object", + required: [ + "name" + ], + properties: { + name: { + type: "string" + }, + address: { + type: "string" + }, + } + }, + }; + + const parser = AsyncAPISchemaParser(); + + it('should parse valid AsyncAPI Schema', async function () { + const schema = >validSchema; + const parsed = await parser.parse(schema); + expect(parsed).toEqual(schema.data); + }); + + it('should validate valid AsyncAPI Schema', async function () { + const schema = >validSchema; + const result = await parser.validate(schema); + + expect(result).toHaveLength(0); + }); + + it('should validate invalid AsyncAPI Schema with invalid schema', async function () { + const schema = >{ + asyncapi: { + semver: { + major: 2 + } + }, + data: { + oneOf: "this should be an array", + properties: { + name: { + if: "this should be an if" + } + } + } + }; + + const result = await parser.validate(schema); + const expectedResult: SchemaValidateError[] = [ + { "message": "must be object,boolean", "path": ["data", "properties", "name", "if"] }, + { "message": "must be array", "path": ["data", "oneOf"] } + ]; + + expect(result).toEqual(expectedResult); + }); + + it('should validate invalid AsyncAPI Schema with invalid meta schema', async function () { + const schema = >{ + asyncapi: { + semver: { + major: 2 + } + }, + data: { + $schema: "non-existent-meta-schema", + } + }; + + const result = await parser.validate(schema); + const expectedResult: SchemaValidateError[] = [ + { "message": "no schema with key or ref \"non-existent-meta-schema\"" }, + ]; + + expect(result).toEqual(expectedResult); + }); +}); From 903e8e7bffc2320f52edb452b0a2470ae04d7fd6 Mon Sep 17 00:00:00 2001 From: Sergio Moya <1083296+smoya@users.noreply.github.com> Date: Fri, 22 Jul 2022 12:00:58 +0200 Subject: [PATCH 2/7] add comments --- src/schema-parser/asyncapi-schema-parser.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/schema-parser/asyncapi-schema-parser.ts b/src/schema-parser/asyncapi-schema-parser.ts index f40dc7c21..113c27332 100644 --- a/src/schema-parser/asyncapi-schema-parser.ts +++ b/src/schema-parser/asyncapi-schema-parser.ts @@ -23,7 +23,7 @@ async function validate(input: ValidateSchemaInput): Promise): Promise { - const path = message.slice(0, message.indexOf(" ")); - const error = message.slice(message.indexOf(" ") + 1); + const splitIndex = message.indexOf(" "); + const path = message.slice(0, splitIndex); + const error = message.slice(splitIndex + 1); + const resultErr: SchemaValidateError = { message: error, path: path.split("/") }; + errors.push(resultErr); }); } else { @@ -58,6 +63,7 @@ function AjvToSpectralErrors(error: Error): SchemaValidateError[] { const resultErr: SchemaValidateError = { message: error.message, }; + errors.push(resultErr); } From dc7084f528fc618be160757402ba9dd4d61e866c Mon Sep 17 00:00:00 2001 From: Sergio Moya <1083296+smoya@users.noreply.github.com> Date: Mon, 25 Jul 2022 12:25:32 +0200 Subject: [PATCH 3/7] rollback renaming for SchemaValidateResult --- src/schema-parser/asyncapi-schema-parser.ts | 14 +++++++------- src/schema-parser/index.ts | 4 ++-- src/types.ts | 2 +- test/schema-parser/asyncapi-schema-parser.spec.ts | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/schema-parser/asyncapi-schema-parser.ts b/src/schema-parser/asyncapi-schema-parser.ts index 113c27332..2d38f32b8 100644 --- a/src/schema-parser/asyncapi-schema-parser.ts +++ b/src/schema-parser/asyncapi-schema-parser.ts @@ -1,7 +1,7 @@ import { SchemaParser, ParseSchemaInput, ValidateSchemaInput } from "../schema-parser"; import Ajv from "ajv"; import { JSONSchema7 } from "json-schema" -import type { AsyncAPISchema, SchemaValidateError } from '../types'; +import type { AsyncAPISchema, SchemaValidateResult } from '../types'; const ajv = new Ajv({ allErrors: true, @@ -15,9 +15,9 @@ export function AsyncAPISchemaParser(): SchemaParser { } } -async function validate(input: ValidateSchemaInput): Promise { +async function validate(input: ValidateSchemaInput): Promise { const schema = input.data as JSONSchema7; - let errors: SchemaValidateError[] = []; + let errors: SchemaValidateResult[] = []; try { ajv.compile(schema); @@ -33,8 +33,8 @@ async function validate(input: ValidateSchemaInput): Promise { readonly asyncapi: DetailedAsyncAPI; @@ -20,7 +20,7 @@ export interface ParseSchemaInput { } export interface SchemaParser { - validate: (input: ValidateSchemaInput) => void | SchemaValidateError[] | Promise; + validate: (input: ValidateSchemaInput) => void | SchemaValidateResult[] | Promise; parse: (input: ParseSchemaInput) => AsyncAPISchema | Promise; getMimeTypes: () => Array; } diff --git a/src/types.ts b/src/types.ts index abeb61647..9f1074946 100644 --- a/src/types.ts +++ b/src/types.ts @@ -17,5 +17,5 @@ export interface DetailedAsyncAPI { } export type Diagnostic = ISpectralDiagnostic; -export type SchemaValidateError = IFunctionResult; +export type SchemaValidateResult = IFunctionResult; export type AsyncAPISchema = JSONSchema7; diff --git a/test/schema-parser/asyncapi-schema-parser.spec.ts b/test/schema-parser/asyncapi-schema-parser.spec.ts index fa7cb54c7..65746f048 100644 --- a/test/schema-parser/asyncapi-schema-parser.spec.ts +++ b/test/schema-parser/asyncapi-schema-parser.spec.ts @@ -1,6 +1,6 @@ import { ParseSchemaInput, ValidateSchemaInput } from '../../src/schema-parser/index'; import { AsyncAPISchemaParser } from '../../src/schema-parser/asyncapi-schema-parser'; -import { SchemaValidateError } from '../../src/types'; +import { SchemaValidateResult } from '../../src/types'; describe('AsyncAPISchemaParser', function () { @@ -59,7 +59,7 @@ describe('AsyncAPISchemaParser', function () { }; const result = await parser.validate(schema); - const expectedResult: SchemaValidateError[] = [ + const expectedResult: SchemaValidateResult[] = [ { "message": "must be object,boolean", "path": ["data", "properties", "name", "if"] }, { "message": "must be array", "path": ["data", "oneOf"] } ]; @@ -80,7 +80,7 @@ describe('AsyncAPISchemaParser', function () { }; const result = await parser.validate(schema); - const expectedResult: SchemaValidateError[] = [ + const expectedResult: SchemaValidateResult[] = [ { "message": "no schema with key or ref \"non-existent-meta-schema\"" }, ]; From 712db4ea8ed71565b7ca62e9d22641e54adabc0c Mon Sep 17 00:00:00 2001 From: Sergio Moya <1083296+smoya@users.noreply.github.com> Date: Mon, 25 Jul 2022 12:29:28 +0200 Subject: [PATCH 4/7] install @types/json-schema as dev dependency --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 545cb2601..c0854cec1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "@stoplight/spectral-functions": "^1.5.1", "@stoplight/spectral-parsers": "^1.0.1", "@stoplight/spectral-rulesets": "^1.4.3", - "@types/json-schema": "^7.0.11", "ajv": "^8.11.0", "jsonpath-plus": "^6.0.1", "lodash": "^4.17.21" @@ -26,6 +25,7 @@ "@semantic-release/release-notes-generator": "^9.0.1", "@types/jest": "^27.4.1", "@types/js-yaml": "^4.0.5", + "@types/json-schema": "^7.0.11", "@types/lodash": "^4.14.179", "conventional-changelog-conventionalcommits": "^4.2.3", "cross-env": "^7.0.3", diff --git a/package.json b/package.json index c6a4f0d24..b017fb76c 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@semantic-release/release-notes-generator": "^9.0.1", "@types/jest": "^27.4.1", "@types/js-yaml": "^4.0.5", + "@types/json-schema": "^7.0.11", "@types/lodash": "^4.14.179", "conventional-changelog-conventionalcommits": "^4.2.3", "cross-env": "^7.0.3", @@ -52,7 +53,6 @@ "@stoplight/spectral-functions": "^1.5.1", "@stoplight/spectral-parsers": "^1.0.1", "@stoplight/spectral-rulesets": "^1.4.3", - "@types/json-schema": "^7.0.11", "ajv": "^8.11.0", "jsonpath-plus": "^6.0.1", "lodash": "^4.17.21" From 30c0cb3d073a9080c512f64424120c13c0917b49 Mon Sep 17 00:00:00 2001 From: Sergio Moya <1083296+smoya@users.noreply.github.com> Date: Wed, 27 Jul 2022 12:50:36 +0200 Subject: [PATCH 5/7] rework --- package-lock.json | 23 +++- package.json | 1 + src/schema-parser/asyncapi-schema-parser.ts | 113 ++++++++++-------- .../asyncapi-schema-parser.spec.ts | 33 ++--- 4 files changed, 91 insertions(+), 79 deletions(-) diff --git a/package-lock.json b/package-lock.json index c0854cec1..191bf1b26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "2.0.0", "license": "Apache-2.0", "dependencies": { + "@asyncapi/specs": "^3.1.0", "@stoplight/spectral-core": "^1.10.1", "@stoplight/spectral-functions": "^1.5.1", "@stoplight/spectral-parsers": "^1.0.1", @@ -54,9 +55,9 @@ } }, "node_modules/@asyncapi/specs": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-2.13.1.tgz", - "integrity": "sha512-Hl44ml5/yDtBnOlk0A7RWl+Xy8JcWRni/2QVT1tkmQmwg6ylW+nfIXn2Zzi9Hww+oCkgyrqMXe45rlcJVhKlDQ==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-3.1.0.tgz", + "integrity": "sha512-6xFvzDd54+M9g6EM259Y4a4iiFb2VzPr6eoxA/ttwTu7NRxaGScocXskXtuz53ZWx9BWZWuzwDYKfM3KBkDfiQ==" }, "node_modules/@babel/code-frame": { "version": "7.16.7", @@ -2222,6 +2223,11 @@ "node": ">=12" } }, + "node_modules/@stoplight/spectral-rulesets/node_modules/@asyncapi/specs": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-2.14.0.tgz", + "integrity": "sha512-hHsYF6XsYNIKb1P2rXaooF4H+uKKQ4b/Ljxrk3rZ3riEDiSxMshMEfb1fUlw9Yj4V4OmJhjXwkNvw8W59AXv1A==" + }, "node_modules/@stoplight/spectral-rulesets/node_modules/@stoplight/better-ajv-errors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stoplight/better-ajv-errors/-/better-ajv-errors-1.0.1.tgz", @@ -15126,9 +15132,9 @@ } }, "@asyncapi/specs": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-2.13.1.tgz", - "integrity": "sha512-Hl44ml5/yDtBnOlk0A7RWl+Xy8JcWRni/2QVT1tkmQmwg6ylW+nfIXn2Zzi9Hww+oCkgyrqMXe45rlcJVhKlDQ==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-3.1.0.tgz", + "integrity": "sha512-6xFvzDd54+M9g6EM259Y4a4iiFb2VzPr6eoxA/ttwTu7NRxaGScocXskXtuz53ZWx9BWZWuzwDYKfM3KBkDfiQ==" }, "@babel/code-frame": { "version": "7.16.7", @@ -16799,6 +16805,11 @@ "tslib": "^2.3.0" }, "dependencies": { + "@asyncapi/specs": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-2.14.0.tgz", + "integrity": "sha512-hHsYF6XsYNIKb1P2rXaooF4H+uKKQ4b/Ljxrk3rZ3riEDiSxMshMEfb1fUlw9Yj4V4OmJhjXwkNvw8W59AXv1A==" + }, "@stoplight/better-ajv-errors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stoplight/better-ajv-errors/-/better-ajv-errors-1.0.1.tgz", diff --git a/package.json b/package.json index b017fb76c..797baf8f1 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "typescript": "^4.6.2" }, "dependencies": { + "@asyncapi/specs": "^3.1.0", "@stoplight/spectral-core": "^1.10.1", "@stoplight/spectral-functions": "^1.5.1", "@stoplight/spectral-parsers": "^1.0.1", diff --git a/src/schema-parser/asyncapi-schema-parser.ts b/src/schema-parser/asyncapi-schema-parser.ts index 2d38f32b8..17b6c75f9 100644 --- a/src/schema-parser/asyncapi-schema-parser.ts +++ b/src/schema-parser/asyncapi-schema-parser.ts @@ -1,11 +1,15 @@ import { SchemaParser, ParseSchemaInput, ValidateSchemaInput } from "../schema-parser"; -import Ajv from "ajv"; -import { JSONSchema7 } from "json-schema" +import Ajv, { ErrorObject, ValidateFunction } from "ajv"; import type { AsyncAPISchema, SchemaValidateResult } from '../types'; +// @ts-ignore +import specs from '@asyncapi/specs'; const ajv = new Ajv({ allErrors: true, -}) + strict: false, +}); + +const specVersions = Object.keys(specs).filter((version: string) => !['1.0.0', '1.1.0', '1.2.0', '2.0.0-rc1', '2.0.0-rc2'].includes(version)); export function AsyncAPISchemaParser(): SchemaParser { return { @@ -16,62 +20,72 @@ export function AsyncAPISchemaParser(): SchemaParser { } async function validate(input: ValidateSchemaInput): Promise { - const schema = input.data as JSONSchema7; - let errors: SchemaValidateResult[] = []; - - try { - ajv.compile(schema); - } catch (error: any) { - if (error! instanceof Error) { - errors = ajvToSpectralErrors(error); - } else { - // Unknown and unexpected error - throw error; - } + const version = input.asyncapi.semver.version + const validator = findSchemaValidator(version); + + let result: SchemaValidateResult[] = [] + const valid = validator(input.data); + if (!valid && validator.errors) { + result = ajvToSpectralResult(validator.errors, input.path); } - return errors; + return result; } -function ajvToSpectralErrors(error: Error): SchemaValidateResult[] { - let errors: SchemaValidateResult[] = []; - let errorMessage = error.message; - - // Validation errors. - // See related AJV function where the error message is generated: - // https://github.com/ajv-validator/ajv/blob/99e884dc4bbb828cf47771b7bbdb14f23193b0b1/lib/core.ts#L501-L522 - const validationErrorPrefix = "schema is invalid: "; - if (error.message.startsWith(validationErrorPrefix)) { - // remove prefix - errorMessage = errorMessage.substring(validationErrorPrefix.length); - - // message can contain multiple validation errors separated by ',' (comma) - errorMessage.split(", ").forEach((message: string) => { - const splitIndex = message.indexOf(" "); - const path = message.slice(0, splitIndex); - const error = message.slice(splitIndex + 1); - - const resultErr: SchemaValidateResult = { - message: error, - path: path.split("/") - }; - - errors.push(resultErr); - }); - } else { - // Not a validation error - const resultErr: SchemaValidateResult = { +function ajvToSpectralResult(errors: ErrorObject[], parentPath: Array): SchemaValidateResult[] { + if (parentPath === undefined) { + parentPath = []; + } + + return errors.map(error => { + const errorPath = error.instancePath.replace(/^\//, '').split('/'); // TODO: Instance Path or Schema Path? + + return { message: error.message, - }; + path: parentPath.concat(errorPath), + } as SchemaValidateResult; + }); +} - errors.push(resultErr); +function findSchemaValidator(version: string): ValidateFunction { + let validator = ajv.getSchema(version); + if (!validator) { + const schema = preparePayloadSchema2(specs[version], version); + + ajv.addSchema(schema, version); + validator = ajv.getSchema(version); } - return errors; + return validator as ValidateFunction; } async function parse(input: ParseSchemaInput): Promise { - return input.data as JSONSchema7; + return input.data as AsyncAPISchema; +} + +/** + * To validate schema of the payload we just need a small portion of official AsyncAPI spec JSON Schema, the definition of the schema must be + * a main part of the JSON Schema + * + * @private + * @param {Object} asyncapiSchema AsyncAPI specification JSON Schema + * @param {Object} version AsyncAPI version. + * @returns {Object} valid JSON Schema document describing format of AsyncAPI-valid schema for message payload + */ +function preparePayloadSchema2(asyncapiSchema: AsyncAPISchema, version: string) { + const payloadSchema = `http://asyncapi.com/definitions/${version}/schema.json`; + const definitions = asyncapiSchema.definitions; + if (definitions === undefined) { + throw new Error("AsyncAPI schema must contain definitions"); + } + + // Remove the meta schemas because it is already present within Ajv, and it's not possible to add duplicate schemas. + delete definitions['http://json-schema.org/draft-07/schema']; + delete definitions['http://json-schema.org/draft-04/schema']; + return { + $ref: payloadSchema, + definitions + }; } function getMimeTypes() { @@ -80,7 +94,8 @@ function getMimeTypes() { 'application/schema+json;version=draft-07', 'application/schema+yaml;version=draft-07', ]; - ['2.0.0', '2.1.0', '2.2.0', '2.3.0'].forEach(version => { + + specVersions.forEach((version: string) => { mimeTypes.push( `application/vnd.aai.asyncapi;version=${version}`, `application/vnd.aai.asyncapi+json;version=${version}`, diff --git a/test/schema-parser/asyncapi-schema-parser.spec.ts b/test/schema-parser/asyncapi-schema-parser.spec.ts index 65746f048..e4ad16dcd 100644 --- a/test/schema-parser/asyncapi-schema-parser.spec.ts +++ b/test/schema-parser/asyncapi-schema-parser.spec.ts @@ -7,7 +7,7 @@ describe('AsyncAPISchemaParser', function () { const validSchema = { asyncapi: { semver: { - major: 2 + version: "2.4.0", } }, data: { @@ -28,6 +28,10 @@ describe('AsyncAPISchemaParser', function () { const parser = AsyncAPISchemaParser(); + it('should return Mime Types', async function () { + expect(parser.getMimeTypes()).not.toEqual([]); + }); + it('should parse valid AsyncAPI Schema', async function () { const schema = >validSchema; const parsed = await parser.parse(schema); @@ -45,9 +49,10 @@ describe('AsyncAPISchemaParser', function () { const schema = >{ asyncapi: { semver: { - major: 2 + version: "2.4.0", } }, + path: ["components", "schemas", "schema1", "payload"], data: { oneOf: "this should be an array", properties: { @@ -60,28 +65,8 @@ describe('AsyncAPISchemaParser', function () { const result = await parser.validate(schema); const expectedResult: SchemaValidateResult[] = [ - { "message": "must be object,boolean", "path": ["data", "properties", "name", "if"] }, - { "message": "must be array", "path": ["data", "oneOf"] } - ]; - - expect(result).toEqual(expectedResult); - }); - - it('should validate invalid AsyncAPI Schema with invalid meta schema', async function () { - const schema = >{ - asyncapi: { - semver: { - major: 2 - } - }, - data: { - $schema: "non-existent-meta-schema", - } - }; - - const result = await parser.validate(schema); - const expectedResult: SchemaValidateResult[] = [ - { "message": "no schema with key or ref \"non-existent-meta-schema\"" }, + { "message": "must be object,boolean", "path": ["components", "schemas", "schema1", "payload", "properties", "name", "if"] }, + { "message": "must be array", "path": ["components", "schemas", "schema1", "payload", "oneOf"] } ]; expect(result).toEqual(expectedResult); From 3ae666fb8409cdd5db4b2b23aa1849e5d526c4da Mon Sep 17 00:00:00 2001 From: Sergio Moya <1083296+smoya@users.noreply.github.com> Date: Wed, 27 Jul 2022 15:25:12 +0200 Subject: [PATCH 6/7] fixes --- src/schema-parser/asyncapi-schema-parser.ts | 68 +++++++++---------- .../asyncapi-schema-parser.spec.ts | 18 ++++- 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/src/schema-parser/asyncapi-schema-parser.ts b/src/schema-parser/asyncapi-schema-parser.ts index 17b6c75f9..5b6f64fa3 100644 --- a/src/schema-parser/asyncapi-schema-parser.ts +++ b/src/schema-parser/asyncapi-schema-parser.ts @@ -7,8 +7,10 @@ import specs from '@asyncapi/specs'; const ajv = new Ajv({ allErrors: true, strict: false, + logger: false, }); +// Only versions compatible with JSON Schema Draf-07 are supported. const specVersions = Object.keys(specs).filter((version: string) => !['1.0.0', '1.1.0', '1.2.0', '2.0.0-rc1', '2.0.0-rc2'].includes(version)); export function AsyncAPISchemaParser(): SchemaParser { @@ -21,24 +23,45 @@ export function AsyncAPISchemaParser(): SchemaParser { async function validate(input: ValidateSchemaInput): Promise { const version = input.asyncapi.semver.version - const validator = findSchemaValidator(version); + const validator = getSchemaValidator(version); let result: SchemaValidateResult[] = [] const valid = validator(input.data); if (!valid && validator.errors) { - result = ajvToSpectralResult(validator.errors, input.path); + result = ajvToSpectralResult([...validator.errors], input.path); } return result; } +async function parse(input: ParseSchemaInput): Promise { + return input.data as AsyncAPISchema; +} + +function getMimeTypes() { + const mimeTypes = [ + 'application/schema;version=draft-07', + 'application/schema+json;version=draft-07', + 'application/schema+yaml;version=draft-07', + ]; + + specVersions.forEach((version: string) => { + mimeTypes.push( + `application/vnd.aai.asyncapi;version=${version}`, + `application/vnd.aai.asyncapi+json;version=${version}`, + `application/vnd.aai.asyncapi+yaml;version=${version}`, + ); + }); + return mimeTypes; +} + function ajvToSpectralResult(errors: ErrorObject[], parentPath: Array): SchemaValidateResult[] { if (parentPath === undefined) { parentPath = []; } return errors.map(error => { - const errorPath = error.instancePath.replace(/^\//, '').split('/'); // TODO: Instance Path or Schema Path? + const errorPath = error.instancePath.replace(/^\//, '').split('/'); return { message: error.message, @@ -47,10 +70,10 @@ function ajvToSpectralResult(errors: ErrorObject[], parentPath: Array): Promise { - return input.data as AsyncAPISchema; -} - /** - * To validate schema of the payload we just need a small portion of official AsyncAPI spec JSON Schema, the definition of the schema must be - * a main part of the JSON Schema - * - * @private - * @param {Object} asyncapiSchema AsyncAPI specification JSON Schema - * @param {Object} version AsyncAPI version. - * @returns {Object} valid JSON Schema document describing format of AsyncAPI-valid schema for message payload + * To validate the schema of the payload we just need a small portion of official AsyncAPI spec JSON Schema, the Schema Object in particular. The definition of Schema Object must be + * included in the returned JSON Schema. */ -function preparePayloadSchema2(asyncapiSchema: AsyncAPISchema, version: string) { +function preparePayloadSchema(asyncapiSchema: AsyncAPISchema, version: string): AsyncAPISchema { const payloadSchema = `http://asyncapi.com/definitions/${version}/schema.json`; const definitions = asyncapiSchema.definitions; if (definitions === undefined) { throw new Error("AsyncAPI schema must contain definitions"); } - // Remove the meta schemas because it is already present within Ajv, and it's not possible to add duplicate schemas. + // Remove the meta schemas because they are already present within Ajv, and it's not possible to add duplicated schemas. delete definitions['http://json-schema.org/draft-07/schema']; delete definitions['http://json-schema.org/draft-04/schema']; + return { $ref: payloadSchema, definitions }; } - -function getMimeTypes() { - const mimeTypes = [ - 'application/schema;version=draft-07', - 'application/schema+json;version=draft-07', - 'application/schema+yaml;version=draft-07', - ]; - - specVersions.forEach((version: string) => { - mimeTypes.push( - `application/vnd.aai.asyncapi;version=${version}`, - `application/vnd.aai.asyncapi+json;version=${version}`, - `application/vnd.aai.asyncapi+yaml;version=${version}`, - ); - }); - return mimeTypes; -} diff --git a/test/schema-parser/asyncapi-schema-parser.spec.ts b/test/schema-parser/asyncapi-schema-parser.spec.ts index e4ad16dcd..51af7b933 100644 --- a/test/schema-parser/asyncapi-schema-parser.spec.ts +++ b/test/schema-parser/asyncapi-schema-parser.spec.ts @@ -65,8 +65,22 @@ describe('AsyncAPISchemaParser', function () { const result = await parser.validate(schema); const expectedResult: SchemaValidateResult[] = [ - { "message": "must be object,boolean", "path": ["components", "schemas", "schema1", "payload", "properties", "name", "if"] }, - { "message": "must be array", "path": ["components", "schemas", "schema1", "payload", "oneOf"] } + { + message: 'must be object,boolean', + path: ['components', 'schemas', 'schema1', 'payload', 'properties', 'name', 'if'] + }, + { + message: 'must be array', + path: ['components', 'schemas', 'schema1', 'payload', 'oneOf'] + }, + { + message: 'must be array', + path: ['components', 'schemas', 'schema1', 'payload', 'oneOf'] + }, + { + message: 'must be object,boolean', + path: ['components', 'schemas', 'schema1', 'payload', 'properties', 'name', 'if'] + } ]; expect(result).toEqual(expectedResult); From df774dd011a2d87760077eac5199b17c557b5ed8 Mon Sep 17 00:00:00 2001 From: Sergio Moya <1083296+smoya@users.noreply.github.com> Date: Wed, 27 Jul 2022 16:10:24 +0200 Subject: [PATCH 7/7] fix --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 9f1074946..954b4b5ad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,5 @@ import type { ISpectralDiagnostic, IFunctionResult } from '@stoplight/spectral-core'; -import { JSONSchema7 }from "json-schema" +import type { JSONSchema7 } from "json-schema" export type MaybeAsyncAPI = { asyncapi: string } & Record; export interface AsyncAPISemver {