From e3772ba8aa4e25bbba196e3089a96d68b152579d Mon Sep 17 00:00:00 2001 From: Sergio Moya <1083296+smoya@users.noreply.github.com> Date: Wed, 17 Aug 2022 16:57:10 +0200 Subject: [PATCH 1/3] refactor: add RAML Schema Parser --- package-lock.json | 196 +++++++++++++++--- package.json | 5 +- src/schema-parser/raml-schema-parser.ts | 69 ++++++ test/schema-parser/raml/complex.json | 90 ++++++++ test/schema-parser/raml/invalid.json | 13 ++ .../raml/raml-schema-parser.spec.ts | 79 +++++++ test/schema-parser/raml/simple.json | 21 ++ 7 files changed, 443 insertions(+), 30 deletions(-) create mode 100644 src/schema-parser/raml-schema-parser.ts create mode 100644 test/schema-parser/raml/complex.json create mode 100644 test/schema-parser/raml/invalid.json create mode 100644 test/schema-parser/raml/raml-schema-parser.spec.ts create mode 100644 test/schema-parser/raml/simple.json diff --git a/package-lock.json b/package-lock.json index 191bf1b26..c1b1d8357 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,11 @@ "@stoplight/spectral-parsers": "^1.0.1", "@stoplight/spectral-rulesets": "^1.4.3", "ajv": "^8.11.0", + "js-yaml": "^3.14.1", "jsonpath-plus": "^6.0.1", - "lodash": "^4.17.21" + "lodash": "^4.17.21", + "ramldt2jsonschema": "^1.2.3", + "webapi-parser": "^0.5.0" }, "devDependencies": { "@jest/types": "^27.5.1", @@ -2723,7 +2726,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -3195,7 +3197,6 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true, "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -3257,6 +3258,14 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/compare-func": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", @@ -4356,7 +4365,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -4575,8 +4583,7 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -7221,10 +7228,9 @@ "dev": true }, "node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -7351,11 +7357,39 @@ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, + "node_modules/json-schema-migrate": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-0.2.0.tgz", + "integrity": "sha512-dq4/oHWmtw/+0ytnXsDqVn+VsVweTEmzm5jLgguPn9BjSzn6/q58ZiZx3BHiQyJs612f0T5Z+MrUEUUY5DHsRg==", + "dependencies": { + "ajv": "^5.0.0" + } + }, + "node_modules/json-schema-migrate/node_modules/ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==", + "dependencies": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "node_modules/json-schema-migrate/node_modules/fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha512-fueX787WZKCV0Is4/T2cyAdM4+x1S3MXXOAhavE1ys/W42SHAPacLTQhucja22QBYrfGw50M2sRiXPtTGv9Ymw==" + }, + "node_modules/json-schema-migrate/node_modules/json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha512-4JD/Ivzg7PoW8NzdrBSr3UFwC9mHgvI7Z6z3QGBsSHgKaRTUDmyZAAKJo2UbG1kUVfS9WS8bi36N49U1xw43DA==" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -13315,6 +13349,21 @@ "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==", "dev": true }, + "node_modules/ramldt2jsonschema": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/ramldt2jsonschema/-/ramldt2jsonschema-1.2.3.tgz", + "integrity": "sha512-+wLDAV2NNv9NkfEUOYStaDu/6RYgYXeC1zLtXE+dMU/jDfjpN4iJnBGycDwFTFaIQGosOQhxph7fEX6Mpwxdug==", + "dependencies": { + "commander": "^5.0.0", + "js-yaml": "^3.14.0", + "json-schema-migrate": "^0.2.0", + "webapi-parser": "^0.5.0" + }, + "bin": { + "dt2js": "bin/dt2js.js", + "js2dt": "bin/js2dt.js" + } + }, "node_modules/randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -14151,8 +14200,7 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "node_modules/stack-utils": { "version": "2.0.5", @@ -14892,6 +14940,30 @@ "makeerror": "1.0.12" } }, + "node_modules/webapi-parser": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/webapi-parser/-/webapi-parser-0.5.0.tgz", + "integrity": "sha512-fPt6XuMqLSvBz8exwX4QE1UT+pROLHa00EMDCdO0ybICduwQ1V4f7AWX4pNOpCp+x+0FjczEsOxtQU0d8L3QKw==", + "dependencies": { + "ajv": "6.5.2" + } + }, + "node_modules/webapi-parser/node_modules/ajv": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", + "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", + "dependencies": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.1" + } + }, + "node_modules/webapi-parser/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==" + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -17229,7 +17301,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -17598,8 +17669,7 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "coffee-script": { "version": "1.12.7", @@ -17643,6 +17713,11 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" + }, "compare-func": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", @@ -18488,8 +18563,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.4.0", @@ -18651,8 +18725,7 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -20646,10 +20719,9 @@ "dev": true }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -20746,11 +20818,41 @@ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, + "json-schema-migrate": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-0.2.0.tgz", + "integrity": "sha512-dq4/oHWmtw/+0ytnXsDqVn+VsVweTEmzm5jLgguPn9BjSzn6/q58ZiZx3BHiQyJs612f0T5Z+MrUEUUY5DHsRg==", + "requires": { + "ajv": "^5.0.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==", + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha512-fueX787WZKCV0Is4/T2cyAdM4+x1S3MXXOAhavE1ys/W42SHAPacLTQhucja22QBYrfGw50M2sRiXPtTGv9Ymw==" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha512-4JD/Ivzg7PoW8NzdrBSr3UFwC9mHgvI7Z6z3QGBsSHgKaRTUDmyZAAKJo2UbG1kUVfS9WS8bi36N49U1xw43DA==" + } + } + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -25318,6 +25420,17 @@ "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==", "dev": true }, + "ramldt2jsonschema": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/ramldt2jsonschema/-/ramldt2jsonschema-1.2.3.tgz", + "integrity": "sha512-+wLDAV2NNv9NkfEUOYStaDu/6RYgYXeC1zLtXE+dMU/jDfjpN4iJnBGycDwFTFaIQGosOQhxph7fEX6Mpwxdug==", + "requires": { + "commander": "^5.0.0", + "js-yaml": "^3.14.0", + "json-schema-migrate": "^0.2.0", + "webapi-parser": "^0.5.0" + } + }, "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -25952,8 +26065,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "stack-utils": { "version": "2.0.5", @@ -26506,6 +26618,32 @@ "makeerror": "1.0.12" } }, + "webapi-parser": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/webapi-parser/-/webapi-parser-0.5.0.tgz", + "integrity": "sha512-fPt6XuMqLSvBz8exwX4QE1UT+pROLHa00EMDCdO0ybICduwQ1V4f7AWX4pNOpCp+x+0FjczEsOxtQU0d8L3QKw==", + "requires": { + "ajv": "6.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", + "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.1" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==" + } + } + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 797baf8f1..1a1566b70 100644 --- a/package.json +++ b/package.json @@ -55,8 +55,11 @@ "@stoplight/spectral-parsers": "^1.0.1", "@stoplight/spectral-rulesets": "^1.4.3", "ajv": "^8.11.0", + "js-yaml": "^3.14.1", "jsonpath-plus": "^6.0.1", - "lodash": "^4.17.21" + "lodash": "^4.17.21", + "ramldt2jsonschema": "^1.2.3", + "webapi-parser": "^0.5.0" }, "release": { "branches": [ diff --git a/src/schema-parser/raml-schema-parser.ts b/src/schema-parser/raml-schema-parser.ts new file mode 100644 index 000000000..bf47ec310 --- /dev/null +++ b/src/schema-parser/raml-schema-parser.ts @@ -0,0 +1,69 @@ +import { SchemaParser, ParseSchemaInput, ValidateSchemaInput } from "../schema-parser"; +import type { AsyncAPISchema, SchemaValidateResult } from '../types'; +import yaml from 'js-yaml'; +import * as lib from "webapi-parser"; +const wap = lib.WebApiParser; +const r2j = require('ramldt2jsonschema'); + +export function RamlSchemaParser(): SchemaParser { + return { + validate, + parse, + getMimeTypes, + } + } + +async function parse(input: ParseSchemaInput): Promise { + const message = (input.meta as any).message + try { + const payload = formatPayload(input.data); + + // Draft 6 is compatible with 7. + const jsonModel = await r2j.dt2js(payload, 'tmpType', { draft: '06' }); + const convertedType = jsonModel.definitions.tmpType; + + message['x-parser-original-schema-format'] = input.schemaFormat || input.defaultSchemaFormat; + message['x-parser-original-payload'] = payload; + message.payload = convertedType; + delete message.schemaFormat; + } catch (e) { + console.error(e); + } + + return message.payload; +} + +function getMimeTypes() { + return [ + 'application/raml+yaml;version=1.0', + ]; +} + +async function validate(input: ValidateSchemaInput): Promise { + const payload = formatPayload(input.data); + const parsed = await wap.raml10.parse(payload); + const report = await wap.raml10.validate(parsed); + if (report.conforms) { + // No errors found. + return []; + } + + let validateResult: SchemaValidateResult[] = []; + report.results.forEach(result => { + validateResult.push({ + message: result.message, + path: input.path, // Info provided by the RAML parser doesn't provide a real path to the error. + } as SchemaValidateResult); + }); + + return validateResult; +} + +function formatPayload(payload: any): string { + if (typeof payload === 'object') { + payload = `#%RAML 1.0 Library\n${ + yaml.dump({ types: { tmpType: payload } })}`; + } + + return payload as string; +} \ No newline at end of file diff --git a/test/schema-parser/raml/complex.json b/test/schema-parser/raml/complex.json new file mode 100644 index 000000000..f7cac2385 --- /dev/null +++ b/test/schema-parser/raml/complex.json @@ -0,0 +1,90 @@ +{ + "schemaFormat": "application/raml+yaml;version=1.0", + "payload": + { + "type": + [ + "CatWithAddress", + "CatWithCity" + ], + "minProperties": 1, + "maxProperties": 50, + "additionalProperties": false, + "discriminator": "breed", + "discriminatorValue": "CatOne", + "properties": + { + "proscons": + { + "type": "CatPros | CatCons", + "required": true + }, + "name": + { + "type": "CatName", + "amazing": true + }, + "breed": + { + "type": "CatBreed" + }, + "age": "CatAge", + "rating": + { + "type": "integer", + "multipleOf": 5, + "example": + { + "displayName": "Cat's rating", + "description": "Rating of cat's awesomeness", + "strict": false, + "value": 50 + } + }, + "year_of_birth": "date-only", + "time_of_birth": "time-only", + "dt_of_birth": + { + "type": "datetime-only", + "required": false + }, + "addition_date": + { + "type": "datetime", + "format": "rfc2616" + }, + "removal_date": + { + "type": "datetime" + }, + "photo": + { + "type": "file", + "fileTypes": + [ + "image/jpeg", + "image/png" + ], + "minLength": 1, + "maxLength": 307200 + }, + "description": "nil", + "habits?": "string", + "character": "nil | string", + "siblings": "string[]", + "parents": "CatName[]", + "ratingHistory": "(integer | number)[]", + "additionalData": + { + "type": + { + "type": "object", + "properties": + { + "weight": "number" + } + } + } + } + } +} \ No newline at end of file diff --git a/test/schema-parser/raml/invalid.json b/test/schema-parser/raml/invalid.json new file mode 100644 index 000000000..1ed399fb7 --- /dev/null +++ b/test/schema-parser/raml/invalid.json @@ -0,0 +1,13 @@ +{ + "schemaFormat": "application/raml+yaml;version=1.0", + "payload": { + "type": "object", + "properties": { + "title": "string", + "author": { + "type": "string", + "examples": "test" + } + } + } +} \ No newline at end of file diff --git a/test/schema-parser/raml/raml-schema-parser.spec.ts b/test/schema-parser/raml/raml-schema-parser.spec.ts new file mode 100644 index 000000000..7accf3bf0 --- /dev/null +++ b/test/schema-parser/raml/raml-schema-parser.spec.ts @@ -0,0 +1,79 @@ +import { ParseSchemaInput, ValidateSchemaInput } from '../../../src/schema-parser/index'; +import { RamlSchemaParser } from '../../../src/schema-parser/raml-schema-parser'; +import { SchemaValidateResult } from '../../../src/types'; +import * as fs from 'fs'; +import * as path from 'path'; + +const inputWithSimpleRAML = toInput(fs.readFileSync(path.resolve(__dirname, './simple.json'), 'utf8')); +const outputWithSimpleRAML = '{"payload":{"type":"object","examples":[{"title":"A book","author":"An author"}],"additionalProperties":true,"required":["title","author"],"properties":{"title":{"type":"string"},"author":{"type":"string","examples":["Eva"]}}},"x-parser-original-schema-format":"application/raml+yaml;version=1.0","x-parser-original-payload":"#%RAML 1.0 Library\\ntypes:\\n tmpType:\\n type: object\\n properties:\\n title: string\\n author:\\n type: string\\n examples:\\n anExample: Eva\\n examples:\\n exampleOne:\\n title: A book\\n author: An author\\n"}'; + +const inputWithComplexRAML = toInput(fs.readFileSync(path.resolve(__dirname, './complex.json'), 'utf8')); +const outputWithComplexRAML = `{"payload":{"minProperties":1,"maxProperties":50,"additionalProperties":false,"discriminator":"breed","discriminatorValue":"CatOne","type":"object","required":["proscons","name","breed","age","rating","year_of_birth","time_of_birth","addition_date","removal_date","photo","description","character","siblings","parents","ratingHistory","additionalData"],"properties":{"proscons":{"anyOf":[true,true]},"name":true,"breed":true,"age":true,"rating":{"type":"integer","multipleOf":5,"example":{"displayName":"Cat's rating","description":"Rating of cat's awesomeness","strict":false,"value":50}},"year_of_birth":{"type":"string","format":"date"},"time_of_birth":{"type":"string","format":"time"},"dt_of_birth":{"type":"string","format":"date-time-only"},"addition_date":{"type":"string","format":"rfc2616"},"removal_date":{"type":"string","format":"date-time"},"photo":{"type":"string","minLength":1,"maxLength":307200},"description":{"type":"null"},"habits":{"type":"string"},"character":{"anyOf":[{"type":"null"},{"type":"string"}]},"siblings":{"type":"array","items":{"type":"string"}},"parents":{"type":"array","items":true},"ratingHistory":{"type":"array","items":{"anyOf":[{"type":"integer"},{"type":"number"}]}},"additionalData":{"type":"object","additionalProperties":true,"required":["weight"],"properties":{"weight":{"type":"number"}}}}},"x-parser-original-schema-format":"application/raml+yaml;version=1.0","x-parser-original-payload":"#%RAML 1.0 Library\\ntypes:\\n tmpType:\\n type:\\n - CatWithAddress\\n - CatWithCity\\n minProperties: 1\\n maxProperties: 50\\n additionalProperties: false\\n discriminator: breed\\n discriminatorValue: CatOne\\n properties:\\n proscons:\\n type: CatPros | CatCons\\n required: true\\n name:\\n type: CatName\\n amazing: true\\n breed:\\n type: CatBreed\\n age: CatAge\\n rating:\\n type: integer\\n multipleOf: 5\\n example:\\n displayName: Cat's rating\\n description: Rating of cat's awesomeness\\n strict: false\\n value: 50\\n year_of_birth: date-only\\n time_of_birth: time-only\\n dt_of_birth:\\n type: datetime-only\\n required: false\\n addition_date:\\n type: datetime\\n format: rfc2616\\n removal_date:\\n type: datetime\\n photo:\\n type: file\\n fileTypes:\\n - image/jpeg\\n - image/png\\n minLength: 1\\n maxLength: 307200\\n description: nil\\n habits?: string\\n character: nil | string\\n siblings: 'string[]'\\n parents: 'CatName[]'\\n ratingHistory: '(integer | number)[]'\\n additionalData:\\n type:\\n type: object\\n properties:\\n weight: number\\n"}`; + +const inputWithInvalidRAML = toInput(fs.readFileSync(path.resolve(__dirname, './invalid.json'), 'utf8')); + +describe('parse()', function() { + const parser = RamlSchemaParser(); + + it('should parse simple RAML data types', async function() { + await doTest(inputWithSimpleRAML, outputWithSimpleRAML); + }); + + it('should parse complex RAML data types', async function() { + await doTest(inputWithComplexRAML, outputWithComplexRAML); + }); + + async function doTest(originalInput: ParseSchemaInput, expectedOutput: string) { + const input = {...originalInput}; + const result = await parser.parse(input); + + // Check that the return value of parse() is the expected JSON Schema. + expect(result).toEqual(JSON.parse(expectedOutput).payload); + + // Check that the message got modified, i.e. adding extensions, setting the payload, etc. + const message = (input.meta as any).message; + expect(JSON.stringify(message)).toEqual(expectedOutput); + } +}); + +describe('validate()', function() { + const parser = RamlSchemaParser(); + + it('should validate valid RAML', async function() { + const result = await parser.validate(inputWithSimpleRAML); + expect(result).toHaveLength(0); + }); + it('should validate invalid RAML', async function() { + const results = await parser.validate(inputWithInvalidRAML); + expect(results).toHaveLength(1); + + const result = (results as SchemaValidateResult[])[0]; + expect(result.message).toEqual("Property 'examples' should be a map"); + expect(result.path).toEqual(inputWithInvalidRAML.path); + }); +}); + +function toInput(raw: string): ParseSchemaInput | ValidateSchemaInput { + const message = JSON.parse(raw); + const schemaInput = { + asyncapi: { + semver: { + version: "2.4.0", + major: 2, + minor: 4, + patch: 0 + }, + source: "", + parsed: {}, + }, + path: ["otherchannel", "subscribe", "message", "payload"], + data: message.payload, + meta: { + message: message, + }, + schemaFormat: message.schemaFormat, + defaultSchemaFormat: "application/vnd.aai.asyncapi;version=2.4.0", + }; + + return schemaInput; +} \ No newline at end of file diff --git a/test/schema-parser/raml/simple.json b/test/schema-parser/raml/simple.json new file mode 100644 index 000000000..4ba42fe61 --- /dev/null +++ b/test/schema-parser/raml/simple.json @@ -0,0 +1,21 @@ +{ + "schemaFormat": "application/raml+yaml;version=1.0", + "payload": { + "type": "object", + "properties": { + "title": "string", + "author": { + "type": "string", + "examples": { + "anExample": "Eva" + } + } + }, + "examples": { + "exampleOne": { + "title": "A book", + "author": "An author" + } + } + } +} \ No newline at end of file From 9cefe3339514ff9d2ebead6a7f0fa3e62a99eece Mon Sep 17 00:00:00 2001 From: Sergio Moya <1083296+smoya@users.noreply.github.com> Date: Thu, 25 Aug 2022 10:54:11 +0200 Subject: [PATCH 2/3] return empty array as error path --- src/schema-parser/raml-schema-parser.ts | 2 +- test/schema-parser/raml/raml-schema-parser.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/schema-parser/raml-schema-parser.ts b/src/schema-parser/raml-schema-parser.ts index bf47ec310..3a7569c8e 100644 --- a/src/schema-parser/raml-schema-parser.ts +++ b/src/schema-parser/raml-schema-parser.ts @@ -52,7 +52,7 @@ async function validate(input: ValidateSchemaInput): Promise { validateResult.push({ message: result.message, - path: input.path, // Info provided by the RAML parser doesn't provide a real path to the error. + path: [], // RAML parser doesn't provide a path to the error. } as SchemaValidateResult); }); diff --git a/test/schema-parser/raml/raml-schema-parser.spec.ts b/test/schema-parser/raml/raml-schema-parser.spec.ts index 7accf3bf0..5afabbb11 100644 --- a/test/schema-parser/raml/raml-schema-parser.spec.ts +++ b/test/schema-parser/raml/raml-schema-parser.spec.ts @@ -49,7 +49,7 @@ describe('validate()', function() { const result = (results as SchemaValidateResult[])[0]; expect(result.message).toEqual("Property 'examples' should be a map"); - expect(result.path).toEqual(inputWithInvalidRAML.path); + expect(result.path).toEqual([]); // Validator doesn't provide info about the error path }); }); From 42485fe5fd6d52e8bdc45da790a86ed74e7dfc8e Mon Sep 17 00:00:00 2001 From: Sergio Moya <1083296+smoya@users.noreply.github.com> Date: Thu, 25 Aug 2022 17:38:59 +0200 Subject: [PATCH 3/3] remove try catch --- src/schema-parser/raml-schema-parser.ts | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/schema-parser/raml-schema-parser.ts b/src/schema-parser/raml-schema-parser.ts index 3a7569c8e..450de2394 100644 --- a/src/schema-parser/raml-schema-parser.ts +++ b/src/schema-parser/raml-schema-parser.ts @@ -14,22 +14,18 @@ export function RamlSchemaParser(): SchemaParser { } async function parse(input: ParseSchemaInput): Promise { - const message = (input.meta as any).message - try { - const payload = formatPayload(input.data); + const message = (input.meta as any).message; + const payload = formatPayload(input.data); - // Draft 6 is compatible with 7. - const jsonModel = await r2j.dt2js(payload, 'tmpType', { draft: '06' }); - const convertedType = jsonModel.definitions.tmpType; - - message['x-parser-original-schema-format'] = input.schemaFormat || input.defaultSchemaFormat; - message['x-parser-original-payload'] = payload; - message.payload = convertedType; - delete message.schemaFormat; - } catch (e) { - console.error(e); - } + // Draft 6 is compatible with 7. + const jsonModel = await r2j.dt2js(payload, 'tmpType', { draft: '06' }); + const convertedType = jsonModel.definitions.tmpType; + message['x-parser-original-schema-format'] = input.schemaFormat || input.defaultSchemaFormat; + message['x-parser-original-payload'] = payload; + message.payload = convertedType; + delete message.schemaFormat; + return message.payload; }