From d65e4840b651345d4ebb75b9acb4d4108eabb65a Mon Sep 17 00:00:00 2001 From: Sergio Moya <1083296+smoya@users.noreply.github.com> Date: Wed, 27 Jul 2022 16:15:35 +0200 Subject: [PATCH] refactor: add AsyncAPI Schema Parser (#573) --- package-lock.json | 269 +++++------------- package.json | 3 + src/models/schema.ts | 2 +- src/schema-parser/asyncapi-schema-parser.ts | 81 +++++- src/schema-parser/index.ts | 6 +- src/types.ts | 2 + .../asyncapi-schema-parser.spec.ts | 88 ++++++ 7 files changed, 244 insertions(+), 207 deletions(-) create mode 100644 test/schema-parser/asyncapi-schema-parser.spec.ts diff --git a/package-lock.json b/package-lock.json index 5d8c96516..191bf1b26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,12 @@ "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", "@stoplight/spectral-rulesets": "^1.4.3", + "ajv": "^8.11.0", "jsonpath-plus": "^6.0.1", "lodash": "^4.17.21" }, @@ -24,6 +26,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,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", @@ -2072,21 +2075,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 +2083,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 +2134,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 +2155,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", @@ -2260,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", @@ -2275,21 +2243,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 +2420,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 +2590,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 +2620,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 +4168,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 +14359,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", @@ -15201,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", @@ -16755,27 +16686,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 +16733,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 +16744,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==" } } }, @@ -16906,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", @@ -16915,17 +16819,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 +16986,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 +17131,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 +17154,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 +18297,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 +26111,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..797baf8f1 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", @@ -48,10 +49,12 @@ "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", "@stoplight/spectral-rulesets": "^1.4.3", + "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..5b6f64fa3 100644 --- a/src/schema-parser/asyncapi-schema-parser.ts +++ b/src/schema-parser/asyncapi-schema-parser.ts @@ -1,4 +1,17 @@ -import { SchemaParser } from "../schema-parser"; +import { SchemaParser, ParseSchemaInput, ValidateSchemaInput } from "../schema-parser"; +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, + 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 { return { @@ -8,12 +21,21 @@ export function AsyncAPISchemaParser(): SchemaParser { } } -function validate() { +async function validate(input: ValidateSchemaInput): Promise { + const version = input.asyncapi.semver.version + const validator = getSchemaValidator(version); -} + let result: SchemaValidateResult[] = [] + const valid = validator(input.data); + if (!valid && validator.errors) { + result = ajvToSpectralResult([...validator.errors], input.path); + } -function parse() { + return result; +} +async function parse(input: ParseSchemaInput): Promise { + return input.data as AsyncAPISchema; } function getMimeTypes() { @@ -22,7 +44,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}`, @@ -31,3 +54,51 @@ function getMimeTypes() { }); return mimeTypes; } + +function ajvToSpectralResult(errors: ErrorObject[], parentPath: Array): SchemaValidateResult[] { + if (parentPath === undefined) { + parentPath = []; + } + + return errors.map(error => { + const errorPath = error.instancePath.replace(/^\//, '').split('/'); + + return { + message: error.message, + path: parentPath.concat(errorPath), + } as SchemaValidateResult; + }); +} + +function getSchemaValidator(version: string): ValidateFunction { + let validator = ajv.getSchema(version); + if (!validator) { + const schema = preparePayloadSchema(specs[version], version); + + ajv.addSchema(schema, version); + validator = ajv.getSchema(version); + } + + return validator as ValidateFunction; +} + +/** + * 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 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 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 + }; +} diff --git a/src/schema-parser/index.ts b/src/schema-parser/index.ts index 3966782fe..89bf2ade6 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, SchemaValidateResult } from '../types'; export interface ValidateSchemaInput { readonly asyncapi: DetailedAsyncAPI; @@ -21,11 +21,11 @@ export interface ParseSchemaInput { export interface SchemaParser { validate: (input: ValidateSchemaInput) => void | SchemaValidateResult[] | Promise; - parse: (input: ParseSchemaInput) => unknown | 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..954b4b5ad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import type { ISpectralDiagnostic, IFunctionResult } from '@stoplight/spectral-core'; +import type { JSONSchema7 } from "json-schema" export type MaybeAsyncAPI = { asyncapi: string } & Record; export interface AsyncAPISemver { @@ -17,3 +18,4 @@ export interface DetailedAsyncAPI { export type Diagnostic = ISpectralDiagnostic; 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 new file mode 100644 index 000000000..51af7b933 --- /dev/null +++ b/test/schema-parser/asyncapi-schema-parser.spec.ts @@ -0,0 +1,88 @@ +import { ParseSchemaInput, ValidateSchemaInput } from '../../src/schema-parser/index'; +import { AsyncAPISchemaParser } from '../../src/schema-parser/asyncapi-schema-parser'; +import { SchemaValidateResult } from '../../src/types'; + +describe('AsyncAPISchemaParser', function () { + + const validSchema = { + asyncapi: { + semver: { + version: "2.4.0", + } + }, + data: { + type: "object", + required: [ + "name" + ], + properties: { + name: { + type: "string" + }, + address: { + type: "string" + }, + } + }, + }; + + 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); + 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: { + version: "2.4.0", + } + }, + path: ["components", "schemas", "schema1", "payload"], + data: { + oneOf: "this should be an array", + properties: { + name: { + if: "this should be an if" + } + } + } + }; + + 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 array', + path: ['components', 'schemas', 'schema1', 'payload', 'oneOf'] + }, + { + message: 'must be object,boolean', + path: ['components', 'schemas', 'schema1', 'payload', 'properties', 'name', 'if'] + } + ]; + + expect(result).toEqual(expectedResult); + }); +});