From 06dd98a26de19272459c5a5013d25d9c5f8bbde5 Mon Sep 17 00:00:00 2001 From: Sambhav Gupta Date: Sun, 24 Mar 2024 17:36:38 +0530 Subject: [PATCH 01/11] feat: add extensions from extensions catalog to schema on master --- .sonarcloud.properties | 1 + README.md | 12 +++ definitions/3.0.0/info.json | 118 +++++++++++++------------- definitions/3.0.0/infoExtensions.json | 11 +++ examples/3.0.0/info.json | 98 +++++++++++++++------ extensions/x/0.1.0/schema.json | 10 +++ 6 files changed, 167 insertions(+), 83 deletions(-) create mode 100644 .sonarcloud.properties create mode 100644 definitions/3.0.0/infoExtensions.json create mode 100644 extensions/x/0.1.0/schema.json diff --git a/.sonarcloud.properties b/.sonarcloud.properties new file mode 100644 index 00000000..4cc6c5a3 --- /dev/null +++ b/.sonarcloud.properties @@ -0,0 +1 @@ +sonar.exclusions=tools/**/* \ No newline at end of file diff --git a/README.md b/README.md index 0eb6ef1e..5eb090a4 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,8 @@ This is the current project structure explained: - [./examples](./examples) - contain most individual definition examples that will automatically be bundled together to provide example for each definition in the schemas in [./schemas](./schemas). - [./tools/bundler](./tools/bundler) - is the tool that bundles all the individual schemas together. - [./schemas](./schemas) - contain all automatically bundled and complete schemas for each AsyncAPI version. These schemas should **NOT** be manually changed as they are automatically generated. Any changes should be done in [./definitions](./definitions). +- [./extensions](./extensions) - contains all the schemas of the extensions that will automatically be bundled to provide informations about extensions. + ## Schema Bundling @@ -210,7 +212,17 @@ Whenever you make changes in AsyncAPI JSON Schema, you should always manually ve ```yaml # yaml-language-server: $schema=YOUR-PROJECTS-DIRECTORY/spec-json-schemas/schemas/2.6.0-without-$id.json ``` + +## Extensions + +Extensions are a way to [extend AsyncAPI specification](https://www.asyncapi.com/docs/concepts/asyncapi-document/extending-specification) with fields that are not yet defined inside the specification. To add JSON schema of the extension in this repository, you need to first make sure it is added to the [extension-catalog](https://github.com/asyncapi/extensions-catalog) repository. +### How to add schema of the extension +1. All the extensions must be present in [./extensions](./extensions) folder. +2. A proper folder structure must be followed to add the extensions. +3. A new folder just as [x extension](./extensions/x) must be added with proper `versioning` and `schema file`. +4. All the schemas must be added in a file named `schema.json` just as one is defined for [x extension](./extensions/x/0.1.0/schema.json). +5. Extension schema should not be referenced directly in the definition of the object it extends. For example if you add an extension for `info`, your extension's schema should not be referenced from `info.json` but [infoExtensions.json](./definitions/3.0.0/infoExtensions.json). If the object that you extend doesn't have a corresponding `*Extensions.json` file, you need to create one. diff --git a/definitions/3.0.0/info.json b/definitions/3.0.0/info.json index 6c9b70fa..034eb5ab 100644 --- a/definitions/3.0.0/info.json +++ b/definitions/3.0.0/info.json @@ -1,66 +1,70 @@ { - "type": "object", "description": "The object provides metadata about the API. The metadata can be used by the clients if needed.", - "required": [ - "version", - "title" - ], - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d\\.\\x2d_]+$": { - "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" - } - }, - "properties": { - "title": { - "type": "string", - "description": "A unique and precise title of the API." - }, - "version": { - "type": "string", - "description": "A semantic version number of the API." - }, - "description": { - "type": "string", - "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." - }, - "termsOfService": { - "type": "string", - "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", - "format": "uri" - }, - "contact": { - "$ref": "http://asyncapi.com/definitions/3.0.0/contact.json" - }, - "license": { - "$ref": "http://asyncapi.com/definitions/3.0.0/license.json" - }, - "tags": { - "type": "array", - "description": "A list of tags for application API documentation control. Tags can be used for logical grouping of applications.", - "items": { - "oneOf": [ - { - "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" - }, - { - "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" - } - ] + "allOf": [ + { + "type": "object", + "required": ["version", "title"], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } }, - "uniqueItems": true - }, - "externalDocs": { - "oneOf": [ - { - "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." }, - { - "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." + }, + "termsOfService": { + "type": "string", + "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", + "format": "uri" + }, + "contact": { + "$ref": "http://asyncapi.com/definitions/3.0.0/contact.json" + }, + "license": { + "$ref": "http://asyncapi.com/definitions/3.0.0/license.json" + }, + "tags": { + "type": "array", + "description": "A list of tags for application API documentation control. Tags can be used for logical grouping of applications.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] } - ] + } + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/infoExtensions.json" } - }, + ], "example": { "$ref": "http://asyncapi.com/examples/3.0.0/info.json" }, diff --git a/definitions/3.0.0/infoExtensions.json b/definitions/3.0.0/infoExtensions.json new file mode 100644 index 00000000..47e71402 --- /dev/null +++ b/definitions/3.0.0/infoExtensions.json @@ -0,0 +1,11 @@ +{ + "type": "object", + "description": "The object that lists all the extensions of Info", + "properties": { + "x-x":{ + "$ref": "http://asyncapi.com/extensions/x/0.1.0/schema.json" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/definitions/3.0.0/infoExtensions.json" +} diff --git a/examples/3.0.0/info.json b/examples/3.0.0/info.json index 26bcdf95..985c3813 100644 --- a/examples/3.0.0/info.json +++ b/examples/3.0.0/info.json @@ -1,27 +1,73 @@ -[ - { - "title": "AsyncAPI Sample App", - "version": "1.0.1", - "description": "This is a sample app.", - "termsOfService": "https://asyncapi.org/terms/", - "contact": { - "name": "API Support", - "url": "https://www.asyncapi.org/support", - "email": "support@asyncapi.org" - }, - "license": { - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html" - }, - "externalDocs": { - "description": "Find more info here", - "url": "https://www.asyncapi.org" - }, - "tags": [ - { - "name": "e-commerce" +{ + "description": "The object provides metadata about the API. The metadata can be used by the clients if needed.", + "allOf": [ + { + "type": "object", + "required": ["version", "title"], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." + }, + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." + }, + "termsOfService": { + "type": "string", + "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", + "format": "uri" + }, + "contact": { + "$ref": "http://asyncapi.com/definitions/3.0.0/contact.json" + }, + "license": { + "$ref": "http://asyncapi.com/definitions/3.0.0/license.json" + }, + "tags": { + "type": "array", + "description": "A list of tags for application API documentation control. Tags can be used for logical grouping of applications.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + } } - ] - } - -] \ No newline at end of file + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/infoExtensions.json" + } + ], + "example": { + "$ref": "http://asyncapi.com/examples/3.0.0/info.json" + }, + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/definitions/3.0.0/info.json" + } \ No newline at end of file diff --git a/extensions/x/0.1.0/schema.json b/extensions/x/0.1.0/schema.json new file mode 100644 index 00000000..6bfdc146 --- /dev/null +++ b/extensions/x/0.1.0/schema.json @@ -0,0 +1,10 @@ +{ + "type": "string", + "description": "This extension allows you to provide the Twitter username of the account representing the team/company of the API.", + "example": [ + "sambhavgupta75", + "AsyncAPISpec" + ], + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/extensions/x/0.1.0/schema.json" +} From 5bbbcd36e7f9dcd174ec5db53c765aa86cb79022 Mon Sep 17 00:00:00 2001 From: Sambhav Gupta Date: Sun, 24 Mar 2024 22:38:18 +0530 Subject: [PATCH 02/11] revert example changes --- README.md | 1 - examples/3.0.0/info.json | 98 +++++++++++----------------------------- 2 files changed, 26 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 5eb090a4..da079908 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,6 @@ Whenever you make changes in AsyncAPI JSON Schema, you should always manually ve Extensions are a way to [extend AsyncAPI specification](https://www.asyncapi.com/docs/concepts/asyncapi-document/extending-specification) with fields that are not yet defined inside the specification. To add JSON schema of the extension in this repository, you need to first make sure it is added to the [extension-catalog](https://github.com/asyncapi/extensions-catalog) repository. ### How to add schema of the extension - 1. All the extensions must be present in [./extensions](./extensions) folder. 2. A proper folder structure must be followed to add the extensions. 3. A new folder just as [x extension](./extensions/x) must be added with proper `versioning` and `schema file`. diff --git a/examples/3.0.0/info.json b/examples/3.0.0/info.json index 985c3813..927490bb 100644 --- a/examples/3.0.0/info.json +++ b/examples/3.0.0/info.json @@ -1,73 +1,27 @@ -{ - "description": "The object provides metadata about the API. The metadata can be used by the clients if needed.", - "allOf": [ - { - "type": "object", - "required": ["version", "title"], - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d\\.\\x2d_]+$": { - "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" - } - }, - "properties": { - "title": { - "type": "string", - "description": "A unique and precise title of the API." - }, - "version": { - "type": "string", - "description": "A semantic version number of the API." - }, - "description": { - "type": "string", - "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." - }, - "termsOfService": { - "type": "string", - "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", - "format": "uri" - }, - "contact": { - "$ref": "http://asyncapi.com/definitions/3.0.0/contact.json" - }, - "license": { - "$ref": "http://asyncapi.com/definitions/3.0.0/license.json" - }, - "tags": { - "type": "array", - "description": "A list of tags for application API documentation control. Tags can be used for logical grouping of applications.", - "items": { - "oneOf": [ - { - "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" - }, - { - "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" - } - ] - }, - "uniqueItems": true - }, - "externalDocs": { - "oneOf": [ - { - "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" - }, - { - "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" - } - ] - } - } +[ + { + "title": "AsyncAPI Sample App", + "version": "1.0.1", + "description": "This is a sample app.", + "termsOfService": "https://asyncapi.org/terms/", + "contact": { + "name": "API Support", + "url": "https://www.asyncapi.org/support", + "email": "support@asyncapi.org" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" }, - { - "$ref": "http://asyncapi.com/definitions/3.0.0/infoExtensions.json" - } - ], - "example": { - "$ref": "http://asyncapi.com/examples/3.0.0/info.json" - }, - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "http://asyncapi.com/definitions/3.0.0/info.json" - } \ No newline at end of file + "externalDocs": { + "description": "Find more info here", + "url": "https://www.asyncapi.org" + }, + "tags": [ + { + "name": "e-commerce" + } + ] + } + + ] \ No newline at end of file From 2c488db961622665c7cb1e0042229d104ac23914 Mon Sep 17 00:00:00 2001 From: Sambhav Gupta Date: Sun, 24 Mar 2024 22:41:47 +0530 Subject: [PATCH 03/11] revert example changes --- examples/3.0.0/info.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/3.0.0/info.json b/examples/3.0.0/info.json index 927490bb..403c69f6 100644 --- a/examples/3.0.0/info.json +++ b/examples/3.0.0/info.json @@ -1,10 +1,10 @@ [ - { - "title": "AsyncAPI Sample App", - "version": "1.0.1", - "description": "This is a sample app.", - "termsOfService": "https://asyncapi.org/terms/", - "contact": { + { + "title": "AsyncAPI Sample App", + "version": "1.0.1", + "description": "This is a sample app.", + "termsOfService": "https://asyncapi.org/terms/", + "contact": { "name": "API Support", "url": "https://www.asyncapi.org/support", "email": "support@asyncapi.org" From 3bbb0a498a4702da204ea018cff3c1cf5f9e4ee7 Mon Sep 17 00:00:00 2001 From: Sambhav Gupta Date: Sun, 24 Mar 2024 22:44:30 +0530 Subject: [PATCH 04/11] revert example changes --- examples/3.0.0/info.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/examples/3.0.0/info.json b/examples/3.0.0/info.json index 403c69f6..9af6f16f 100644 --- a/examples/3.0.0/info.json +++ b/examples/3.0.0/info.json @@ -1,27 +1,27 @@ [ - { + { "title": "AsyncAPI Sample App", "version": "1.0.1", "description": "This is a sample app.", "termsOfService": "https://asyncapi.org/terms/", "contact": { - "name": "API Support", - "url": "https://www.asyncapi.org/support", - "email": "support@asyncapi.org" - }, - "license": { - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html" - }, - "externalDocs": { - "description": "Find more info here", - "url": "https://www.asyncapi.org" - }, - "tags": [ - { - "name": "e-commerce" - } - ] - } + "name": "API Support", + "url": "https://www.asyncapi.org/support", + "email": "support@asyncapi.org" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + }, + "externalDocs": { + "description": "Find more info here", + "url": "https://www.asyncapi.org" + }, + "tags": [ + { + "name": "e-commerce" + } + ] + } - ] \ No newline at end of file +] From 0fa88f316cf15a2c171999559fcbe7a5e5a1b714 Mon Sep 17 00:00:00 2001 From: Sambhav Gupta Date: Sun, 24 Mar 2024 22:47:26 +0530 Subject: [PATCH 05/11] revert example changes --- examples/3.0.0/info.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/3.0.0/info.json b/examples/3.0.0/info.json index 9af6f16f..26bcdf95 100644 --- a/examples/3.0.0/info.json +++ b/examples/3.0.0/info.json @@ -5,23 +5,23 @@ "description": "This is a sample app.", "termsOfService": "https://asyncapi.org/terms/", "contact": { - "name": "API Support", - "url": "https://www.asyncapi.org/support", - "email": "support@asyncapi.org" + "name": "API Support", + "url": "https://www.asyncapi.org/support", + "email": "support@asyncapi.org" }, "license": { - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" }, "externalDocs": { - "description": "Find more info here", - "url": "https://www.asyncapi.org" + "description": "Find more info here", + "url": "https://www.asyncapi.org" }, "tags": [ - { - "name": "e-commerce" + { + "name": "e-commerce" } ] } - -] + +] \ No newline at end of file From eaa800c99d998db42b06dd338e07f6107b6784ec Mon Sep 17 00:00:00 2001 From: Sambhav Gupta Date: Sun, 24 Mar 2024 22:48:51 +0530 Subject: [PATCH 06/11] file changes --- tools/bundler/index.js | 115 +++++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 51 deletions(-) diff --git a/tools/bundler/index.js b/tools/bundler/index.js index c405b2e4..2975a20b 100644 --- a/tools/bundler/index.js +++ b/tools/bundler/index.js @@ -1,27 +1,32 @@ const path = require('path'); const fs = require('fs'); const traverse = require('json-schema-traverse'); +const { url } = require('inspector'); const definitionsDirectory = path.resolve(__dirname, '../../definitions'); -const commonSchemasDirectory = path.resolve(__dirname, '../../common'); const bindingsDirectory = path.resolve(__dirname, '../../bindings'); +const extensionsDirectory = path.resolve(__dirname, '../../extensions'); const outputDirectory = path.resolve(__dirname, '../../schemas'); const JSON_SCHEMA_PROP_NAME = 'json-schema-draft-07-schema'; console.log(`Looking for separate definitions in the following directory: ${definitionsDirectory}`); console.log(`Looking for binding version schemas in the following directory: ${bindingsDirectory}`); +console.log(`Looking for extension version schemas in the following directory: ${extensionsDirectory}`); console.log(`Using the following output directory: ${outputDirectory}`); // definitionsRegex is used to transform the name of a definition into a valid one to be used in the -without-$id.json files. -const definitionsRegex = /http:\/\/asyncapi\.com\/definitions\/[^/]*\/(.+)\.json#?(.*)/i; +const definitionsRegex = /http:\/\/asyncapi\.com\/definitions\/[^/]*\/(.+)\.json#?(.*)/i // definitionsRegex is used to transform the name of a binding into a valid one to be used in the -without-$id.json files. -const bindingsRegex = /http:\/\/asyncapi\.com\/(bindings\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i; +const bindingsRegex = /http:\/\/asyncapi\.com\/(bindings\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i + +// definitionsRegex is used to transform the name of a binding into a valid one to be used in the -without-$id.json files. +const extensionsRegex = /http:\/\/asyncapi\.com\/(extensions\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i /** * Function to load all the core AsyncAPI spec definition (except the root asyncapi schema, as that will be loaded later) into the bundler. */ async function loadDefinitions(bundler, versionDir) { const definitions = await fs.promises.readdir(versionDir); - const definitionFiles = definitions.filter((value) => {return !value.includes('asyncapi');}).map((file) => fs.readFileSync(path.resolve(versionDir, file))); + const definitionFiles = definitions.filter((value) => {return !value.includes('asyncapi')}).map((file) => fs.readFileSync(path.resolve(versionDir, file))); const definitionJson = definitionFiles.map((file) => JSON.parse(file)); for (const jsonFile of definitionJson) { @@ -40,35 +45,39 @@ async function loadDefinitions(bundler, versionDir) { } } + /** - * Function to load all the binding version schemas into the bundler + * Function to load all schemas into bundler, by "type" you specify if these are "bindings" or "extensions" */ -async function loadBindings(bundler) { - const bindingDirectories = await fs.promises.readdir(bindingsDirectory); - for (const bindingDirectory of bindingDirectories) { - const bindingVersionDirectories = await fs.promises.readdir(path.resolve(bindingsDirectory, bindingDirectory)); - const bindingVersionDirectoriesFiltered = bindingVersionDirectories.filter((file) => fs.lstatSync(path.resolve(bindingsDirectory, bindingDirectory, file)).isDirectory()); - for (const bindingVersionDirectory of bindingVersionDirectoriesFiltered) { - const bindingFiles = await fs.promises.readdir(path.resolve(bindingsDirectory, bindingDirectory, bindingVersionDirectory)); - const bindingFilesFiltered = bindingFiles.filter((bindingFile) => path.extname(bindingFile) === '.json').map((bindingFile) => path.resolve(bindingsDirectory, bindingDirectory, bindingVersionDirectory, bindingFile)); - for (const bindingFile of bindingFilesFiltered) { - const bindingFileContent = require(bindingFile); - bundler.add(bindingFileContent); - } - } +async function loadSchemas(bundler, type) { + + let directory; + + switch (type) { + case "bindings": + directory = bindingsDirectory; + break; + case "extensions": + directory = extensionsDirectory; + break; + default: + console.error("Invalid input. I'm not going to assume if you want bindings or extensions - these are different beasts."); } -} -async function loadCommonSchemas(bundler) { - // Add common schemas to all versions - const commonSchemas = await fs.promises.readdir(commonSchemasDirectory); - const commonSchemaFiles = commonSchemas.map((file) => path.resolve(commonSchemasDirectory, file)); - for (const commonSchemaFile of commonSchemaFiles) { - const commonSchemaFileContent = require(commonSchemaFile); - bundler.add(commonSchemaFileContent); + const directories = await fs.promises.readdir(directory); + for (const nestedDir of directories) { + const versionDirectories = await fs.promises.readdir(path.resolve(directory, nestedDir)); + const versionDirectoriesFiltered = versionDirectories.filter((file) => fs.lstatSync(path.resolve(directory, nestedDir, file)).isDirectory()); + for (const versionDir of versionDirectoriesFiltered) { + const files = await fs.promises.readdir(path.resolve(directory, nestedDir, versionDir)); + const filesFiltered = files.filter((file) => path.extname(file) === '.json').map((file) => path.resolve(directory, nestedDir, versionDir, file)); + for (const filteredFile of filesFiltered) { + const fileContent = require(filteredFile); + bundler.add(fileContent); + } + } } } - /** * When run, go through all versions that have split definitions and bundles them together. */ @@ -80,15 +89,15 @@ async function loadCommonSchemas(bundler) { } console.log(`The following versions have separate definitions: ${versions.join(',')}`); for (const version of versions) { - const Bundler = require('@hyperjump/json-schema-bundle'); - try { + const Bundler = require("@hyperjump/json-schema-bundle"); + try{ console.log(`Bundling the following version together: ${version}`); const outputFileWithId = path.resolve(outputDirectory, `${version}.json`); const outputFileWithoutId = path.resolve(outputDirectory, `${version}-without-$id.json`); const versionDir = path.resolve(definitionsDirectory, version); await loadDefinitions(Bundler, versionDir); - await loadCommonSchemas(Bundler); - await loadBindings(Bundler); + await loadSchemas(Bundler, 'bindings'); + await loadSchemas(Bundler, 'extensions'); const filePathToBundle = `file://${versionDir}/asyncapi.json`; const fileToBundle = await Bundler.get(filePathToBundle); @@ -97,7 +106,7 @@ async function loadCommonSchemas(bundler) { * bundling schemas into one file with $id */ const bundledSchemaWithId = await Bundler.bundle(fileToBundle); - bundledSchemaWithId.description = `!!Auto generated!! \n Do not manually edit. ${bundledSchemaWithId.description !== undefined && bundledSchemaWithId.description !== null ? bundledSchemaWithId.description : ''}`; + bundledSchemaWithId.description = `!!Auto generated!! \n Do not manually edit. ${bundledSchemaWithId.description ?? ''}`; console.log(`Writing the bundled file WITH $ids to: ${outputFileWithId}`); await fs.promises.writeFile(outputFileWithId, JSON.stringify(bundledSchemaWithId, null, 4)); @@ -108,7 +117,7 @@ async function loadCommonSchemas(bundler) { const bundledSchemaWithoutIds = modifyRefsAndDefinitions(bundledSchemaWithId); console.log(`Writing the bundled file WITHOUT $ids to: ${outputFileWithoutId}`); await fs.promises.writeFile(outputFileWithoutId, JSON.stringify(bundledSchemaWithoutIds, null, 4)); - } catch (e) { + }catch(e) { throw new Error(e); } } @@ -118,6 +127,7 @@ async function loadCommonSchemas(bundler) { /** * Extract file data from reference file path */ + async function loadRefProperties(filePath) { const schemaPath = filePath.$ref; // first we need to turn the path to an absolute file path instead of a generic url @@ -126,23 +136,24 @@ async function loadRefProperties(filePath) { try { const data = await fs.promises.readFile(`../../examples${versionPath}`); return JSON.parse(data); - } catch (e) { - throw new Error(e); + }catch(e) { + throw new Error(e); + } } -} /** * we first update definitions from URL to normal names * than update refs to point to new definitions, always inline never remote */ function modifyRefsAndDefinitions(bundledSchema) { + //first we need to improve names of the definitions from URL to their names for (const def of Object.keys(bundledSchema.definitions)) { const newDefName = getDefinitionName(def); //creating copy of definition under new name so later definition stored under URL name can be removed bundledSchema.definitions[newDefName] = bundledSchema.definitions[def]; - delete bundledSchema.definitions[def]; + delete bundledSchema.definitions[def] } traverse(bundledSchema, replaceRef); @@ -150,7 +161,7 @@ function modifyRefsAndDefinitions(bundledSchema) { traverse(bundledSchema.definitions.openapiSchema_3_0, updateOpenApi); traverse(bundledSchema.definitions['json-schema-draft-07-schema'], updateJsonSchema); - return bundledSchema; + return bundledSchema } /** @@ -167,8 +178,12 @@ function getDefinitionName(def) { const result = bindingsRegex.exec(def); if (result) return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}`; } + if (def.startsWith('http://asyncapi.com/extensions')) { + const result = extensionsRegex.exec(def); + if (result) return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}`; + } - return path.basename(def, '.json'); + return path.basename(def, '.json') } /** @@ -177,11 +192,10 @@ function getDefinitionName(def) { */ function replaceRef(schema) { //new refs will only work if we remove $id that all point to asyncapi.com - delete schema.$id; - - //traversing should take place only in case of schemas with refs - if (schema.$ref === undefined) return; - + delete schema.$id + + //traversing shoudl take place only in case of schemas with refs + if (schema.$ref === undefined ) return; // updating refs that are related to remote URL refs that need to be update and point to inlined versions if (!schema.$ref.startsWith('#')) schema.$ref = `#/definitions/${getDefinitionName(schema.$ref)}`; } @@ -190,12 +204,11 @@ function replaceRef(schema) { * this is a callback used when traversing through json schema * to fix avro schema definitions to point to right direction */ -function updateAvro(schema) { - //traversing should take place only in case of schemas with refs +function updateAvro(schema){ + //traversing shoudl take place only in case of schemas with refs if (schema.$ref === undefined) return; schema.$ref = schema.$ref.replace( - /* eslint-disable sonarjs/no-duplicate-string */ '#/definitions/', '#/definitions/avroSchema_v1/definitions/' ); @@ -205,8 +218,8 @@ function updateAvro(schema) { * this is a callback used when traversing through json schema * to fix open api schema definitions to point to right direction */ -function updateOpenApi(schema) { - //traversing should take place only in case of schemas with refs +function updateOpenApi(schema){ + //traversing shoudl take place only in case of schemas with refs if (schema.$ref === undefined) return; const openApiPropName = 'openapiSchema_3_0'; @@ -224,8 +237,8 @@ function updateOpenApi(schema) { * this is a callback used when traversing through json schema * to fix open api schema definitions to point to right direction */ -function updateJsonSchema(schema) { - //traversing should take place only in case of schemas with refs +function updateJsonSchema(schema){ + //traversing shoudl take place only in case of schemas with refs if (schema.$ref === undefined) return; schema.$ref = schema.$ref.replace( From 02c1eff4e6e5af484ec406023cf1eacacb44b595 Mon Sep 17 00:00:00 2001 From: Sambhav Gupta Date: Mon, 25 Mar 2024 00:47:51 +0530 Subject: [PATCH 07/11] revert --- tools/bundler/index.js | 115 ++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 64 deletions(-) diff --git a/tools/bundler/index.js b/tools/bundler/index.js index 2975a20b..c405b2e4 100644 --- a/tools/bundler/index.js +++ b/tools/bundler/index.js @@ -1,32 +1,27 @@ const path = require('path'); const fs = require('fs'); const traverse = require('json-schema-traverse'); -const { url } = require('inspector'); const definitionsDirectory = path.resolve(__dirname, '../../definitions'); +const commonSchemasDirectory = path.resolve(__dirname, '../../common'); const bindingsDirectory = path.resolve(__dirname, '../../bindings'); -const extensionsDirectory = path.resolve(__dirname, '../../extensions'); const outputDirectory = path.resolve(__dirname, '../../schemas'); const JSON_SCHEMA_PROP_NAME = 'json-schema-draft-07-schema'; console.log(`Looking for separate definitions in the following directory: ${definitionsDirectory}`); console.log(`Looking for binding version schemas in the following directory: ${bindingsDirectory}`); -console.log(`Looking for extension version schemas in the following directory: ${extensionsDirectory}`); console.log(`Using the following output directory: ${outputDirectory}`); // definitionsRegex is used to transform the name of a definition into a valid one to be used in the -without-$id.json files. -const definitionsRegex = /http:\/\/asyncapi\.com\/definitions\/[^/]*\/(.+)\.json#?(.*)/i +const definitionsRegex = /http:\/\/asyncapi\.com\/definitions\/[^/]*\/(.+)\.json#?(.*)/i; // definitionsRegex is used to transform the name of a binding into a valid one to be used in the -without-$id.json files. -const bindingsRegex = /http:\/\/asyncapi\.com\/(bindings\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i - -// definitionsRegex is used to transform the name of a binding into a valid one to be used in the -without-$id.json files. -const extensionsRegex = /http:\/\/asyncapi\.com\/(extensions\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i +const bindingsRegex = /http:\/\/asyncapi\.com\/(bindings\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i; /** * Function to load all the core AsyncAPI spec definition (except the root asyncapi schema, as that will be loaded later) into the bundler. */ async function loadDefinitions(bundler, versionDir) { const definitions = await fs.promises.readdir(versionDir); - const definitionFiles = definitions.filter((value) => {return !value.includes('asyncapi')}).map((file) => fs.readFileSync(path.resolve(versionDir, file))); + const definitionFiles = definitions.filter((value) => {return !value.includes('asyncapi');}).map((file) => fs.readFileSync(path.resolve(versionDir, file))); const definitionJson = definitionFiles.map((file) => JSON.parse(file)); for (const jsonFile of definitionJson) { @@ -45,39 +40,35 @@ async function loadDefinitions(bundler, versionDir) { } } - /** - * Function to load all schemas into bundler, by "type" you specify if these are "bindings" or "extensions" + * Function to load all the binding version schemas into the bundler */ -async function loadSchemas(bundler, type) { - - let directory; - - switch (type) { - case "bindings": - directory = bindingsDirectory; - break; - case "extensions": - directory = extensionsDirectory; - break; - default: - console.error("Invalid input. I'm not going to assume if you want bindings or extensions - these are different beasts."); - } - - const directories = await fs.promises.readdir(directory); - for (const nestedDir of directories) { - const versionDirectories = await fs.promises.readdir(path.resolve(directory, nestedDir)); - const versionDirectoriesFiltered = versionDirectories.filter((file) => fs.lstatSync(path.resolve(directory, nestedDir, file)).isDirectory()); - for (const versionDir of versionDirectoriesFiltered) { - const files = await fs.promises.readdir(path.resolve(directory, nestedDir, versionDir)); - const filesFiltered = files.filter((file) => path.extname(file) === '.json').map((file) => path.resolve(directory, nestedDir, versionDir, file)); - for (const filteredFile of filesFiltered) { - const fileContent = require(filteredFile); - bundler.add(fileContent); +async function loadBindings(bundler) { + const bindingDirectories = await fs.promises.readdir(bindingsDirectory); + for (const bindingDirectory of bindingDirectories) { + const bindingVersionDirectories = await fs.promises.readdir(path.resolve(bindingsDirectory, bindingDirectory)); + const bindingVersionDirectoriesFiltered = bindingVersionDirectories.filter((file) => fs.lstatSync(path.resolve(bindingsDirectory, bindingDirectory, file)).isDirectory()); + for (const bindingVersionDirectory of bindingVersionDirectoriesFiltered) { + const bindingFiles = await fs.promises.readdir(path.resolve(bindingsDirectory, bindingDirectory, bindingVersionDirectory)); + const bindingFilesFiltered = bindingFiles.filter((bindingFile) => path.extname(bindingFile) === '.json').map((bindingFile) => path.resolve(bindingsDirectory, bindingDirectory, bindingVersionDirectory, bindingFile)); + for (const bindingFile of bindingFilesFiltered) { + const bindingFileContent = require(bindingFile); + bundler.add(bindingFileContent); } } } } + +async function loadCommonSchemas(bundler) { + // Add common schemas to all versions + const commonSchemas = await fs.promises.readdir(commonSchemasDirectory); + const commonSchemaFiles = commonSchemas.map((file) => path.resolve(commonSchemasDirectory, file)); + for (const commonSchemaFile of commonSchemaFiles) { + const commonSchemaFileContent = require(commonSchemaFile); + bundler.add(commonSchemaFileContent); + } +} + /** * When run, go through all versions that have split definitions and bundles them together. */ @@ -89,15 +80,15 @@ async function loadSchemas(bundler, type) { } console.log(`The following versions have separate definitions: ${versions.join(',')}`); for (const version of versions) { - const Bundler = require("@hyperjump/json-schema-bundle"); - try{ + const Bundler = require('@hyperjump/json-schema-bundle'); + try { console.log(`Bundling the following version together: ${version}`); const outputFileWithId = path.resolve(outputDirectory, `${version}.json`); const outputFileWithoutId = path.resolve(outputDirectory, `${version}-without-$id.json`); const versionDir = path.resolve(definitionsDirectory, version); await loadDefinitions(Bundler, versionDir); - await loadSchemas(Bundler, 'bindings'); - await loadSchemas(Bundler, 'extensions'); + await loadCommonSchemas(Bundler); + await loadBindings(Bundler); const filePathToBundle = `file://${versionDir}/asyncapi.json`; const fileToBundle = await Bundler.get(filePathToBundle); @@ -106,7 +97,7 @@ async function loadSchemas(bundler, type) { * bundling schemas into one file with $id */ const bundledSchemaWithId = await Bundler.bundle(fileToBundle); - bundledSchemaWithId.description = `!!Auto generated!! \n Do not manually edit. ${bundledSchemaWithId.description ?? ''}`; + bundledSchemaWithId.description = `!!Auto generated!! \n Do not manually edit. ${bundledSchemaWithId.description !== undefined && bundledSchemaWithId.description !== null ? bundledSchemaWithId.description : ''}`; console.log(`Writing the bundled file WITH $ids to: ${outputFileWithId}`); await fs.promises.writeFile(outputFileWithId, JSON.stringify(bundledSchemaWithId, null, 4)); @@ -117,7 +108,7 @@ async function loadSchemas(bundler, type) { const bundledSchemaWithoutIds = modifyRefsAndDefinitions(bundledSchemaWithId); console.log(`Writing the bundled file WITHOUT $ids to: ${outputFileWithoutId}`); await fs.promises.writeFile(outputFileWithoutId, JSON.stringify(bundledSchemaWithoutIds, null, 4)); - }catch(e) { + } catch (e) { throw new Error(e); } } @@ -127,7 +118,6 @@ async function loadSchemas(bundler, type) { /** * Extract file data from reference file path */ - async function loadRefProperties(filePath) { const schemaPath = filePath.$ref; // first we need to turn the path to an absolute file path instead of a generic url @@ -136,24 +126,23 @@ async function loadRefProperties(filePath) { try { const data = await fs.promises.readFile(`../../examples${versionPath}`); return JSON.parse(data); - }catch(e) { - throw new Error(e); - } + } catch (e) { + throw new Error(e); } +} /** * we first update definitions from URL to normal names * than update refs to point to new definitions, always inline never remote */ function modifyRefsAndDefinitions(bundledSchema) { - //first we need to improve names of the definitions from URL to their names for (const def of Object.keys(bundledSchema.definitions)) { const newDefName = getDefinitionName(def); //creating copy of definition under new name so later definition stored under URL name can be removed bundledSchema.definitions[newDefName] = bundledSchema.definitions[def]; - delete bundledSchema.definitions[def] + delete bundledSchema.definitions[def]; } traverse(bundledSchema, replaceRef); @@ -161,7 +150,7 @@ function modifyRefsAndDefinitions(bundledSchema) { traverse(bundledSchema.definitions.openapiSchema_3_0, updateOpenApi); traverse(bundledSchema.definitions['json-schema-draft-07-schema'], updateJsonSchema); - return bundledSchema + return bundledSchema; } /** @@ -178,12 +167,8 @@ function getDefinitionName(def) { const result = bindingsRegex.exec(def); if (result) return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}`; } - if (def.startsWith('http://asyncapi.com/extensions')) { - const result = extensionsRegex.exec(def); - if (result) return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}`; - } - return path.basename(def, '.json') + return path.basename(def, '.json'); } /** @@ -192,10 +177,11 @@ function getDefinitionName(def) { */ function replaceRef(schema) { //new refs will only work if we remove $id that all point to asyncapi.com - delete schema.$id - - //traversing shoudl take place only in case of schemas with refs - if (schema.$ref === undefined ) return; + delete schema.$id; + + //traversing should take place only in case of schemas with refs + if (schema.$ref === undefined) return; + // updating refs that are related to remote URL refs that need to be update and point to inlined versions if (!schema.$ref.startsWith('#')) schema.$ref = `#/definitions/${getDefinitionName(schema.$ref)}`; } @@ -204,11 +190,12 @@ function replaceRef(schema) { * this is a callback used when traversing through json schema * to fix avro schema definitions to point to right direction */ -function updateAvro(schema){ - //traversing shoudl take place only in case of schemas with refs +function updateAvro(schema) { + //traversing should take place only in case of schemas with refs if (schema.$ref === undefined) return; schema.$ref = schema.$ref.replace( + /* eslint-disable sonarjs/no-duplicate-string */ '#/definitions/', '#/definitions/avroSchema_v1/definitions/' ); @@ -218,8 +205,8 @@ function updateAvro(schema){ * this is a callback used when traversing through json schema * to fix open api schema definitions to point to right direction */ -function updateOpenApi(schema){ - //traversing shoudl take place only in case of schemas with refs +function updateOpenApi(schema) { + //traversing should take place only in case of schemas with refs if (schema.$ref === undefined) return; const openApiPropName = 'openapiSchema_3_0'; @@ -237,8 +224,8 @@ function updateOpenApi(schema){ * this is a callback used when traversing through json schema * to fix open api schema definitions to point to right direction */ -function updateJsonSchema(schema){ - //traversing shoudl take place only in case of schemas with refs +function updateJsonSchema(schema) { + //traversing should take place only in case of schemas with refs if (schema.$ref === undefined) return; schema.$ref = schema.$ref.replace( From 6297efd24fdcb38fc513d228694b123f91b99e9f Mon Sep 17 00:00:00 2001 From: Sambhav Gupta Date: Mon, 25 Mar 2024 00:52:58 +0530 Subject: [PATCH 08/11] revert --- tools/bundler/index.js | 115 +++++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 51 deletions(-) diff --git a/tools/bundler/index.js b/tools/bundler/index.js index c405b2e4..2975a20b 100644 --- a/tools/bundler/index.js +++ b/tools/bundler/index.js @@ -1,27 +1,32 @@ const path = require('path'); const fs = require('fs'); const traverse = require('json-schema-traverse'); +const { url } = require('inspector'); const definitionsDirectory = path.resolve(__dirname, '../../definitions'); -const commonSchemasDirectory = path.resolve(__dirname, '../../common'); const bindingsDirectory = path.resolve(__dirname, '../../bindings'); +const extensionsDirectory = path.resolve(__dirname, '../../extensions'); const outputDirectory = path.resolve(__dirname, '../../schemas'); const JSON_SCHEMA_PROP_NAME = 'json-schema-draft-07-schema'; console.log(`Looking for separate definitions in the following directory: ${definitionsDirectory}`); console.log(`Looking for binding version schemas in the following directory: ${bindingsDirectory}`); +console.log(`Looking for extension version schemas in the following directory: ${extensionsDirectory}`); console.log(`Using the following output directory: ${outputDirectory}`); // definitionsRegex is used to transform the name of a definition into a valid one to be used in the -without-$id.json files. -const definitionsRegex = /http:\/\/asyncapi\.com\/definitions\/[^/]*\/(.+)\.json#?(.*)/i; +const definitionsRegex = /http:\/\/asyncapi\.com\/definitions\/[^/]*\/(.+)\.json#?(.*)/i // definitionsRegex is used to transform the name of a binding into a valid one to be used in the -without-$id.json files. -const bindingsRegex = /http:\/\/asyncapi\.com\/(bindings\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i; +const bindingsRegex = /http:\/\/asyncapi\.com\/(bindings\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i + +// definitionsRegex is used to transform the name of a binding into a valid one to be used in the -without-$id.json files. +const extensionsRegex = /http:\/\/asyncapi\.com\/(extensions\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i /** * Function to load all the core AsyncAPI spec definition (except the root asyncapi schema, as that will be loaded later) into the bundler. */ async function loadDefinitions(bundler, versionDir) { const definitions = await fs.promises.readdir(versionDir); - const definitionFiles = definitions.filter((value) => {return !value.includes('asyncapi');}).map((file) => fs.readFileSync(path.resolve(versionDir, file))); + const definitionFiles = definitions.filter((value) => {return !value.includes('asyncapi')}).map((file) => fs.readFileSync(path.resolve(versionDir, file))); const definitionJson = definitionFiles.map((file) => JSON.parse(file)); for (const jsonFile of definitionJson) { @@ -40,35 +45,39 @@ async function loadDefinitions(bundler, versionDir) { } } + /** - * Function to load all the binding version schemas into the bundler + * Function to load all schemas into bundler, by "type" you specify if these are "bindings" or "extensions" */ -async function loadBindings(bundler) { - const bindingDirectories = await fs.promises.readdir(bindingsDirectory); - for (const bindingDirectory of bindingDirectories) { - const bindingVersionDirectories = await fs.promises.readdir(path.resolve(bindingsDirectory, bindingDirectory)); - const bindingVersionDirectoriesFiltered = bindingVersionDirectories.filter((file) => fs.lstatSync(path.resolve(bindingsDirectory, bindingDirectory, file)).isDirectory()); - for (const bindingVersionDirectory of bindingVersionDirectoriesFiltered) { - const bindingFiles = await fs.promises.readdir(path.resolve(bindingsDirectory, bindingDirectory, bindingVersionDirectory)); - const bindingFilesFiltered = bindingFiles.filter((bindingFile) => path.extname(bindingFile) === '.json').map((bindingFile) => path.resolve(bindingsDirectory, bindingDirectory, bindingVersionDirectory, bindingFile)); - for (const bindingFile of bindingFilesFiltered) { - const bindingFileContent = require(bindingFile); - bundler.add(bindingFileContent); - } - } +async function loadSchemas(bundler, type) { + + let directory; + + switch (type) { + case "bindings": + directory = bindingsDirectory; + break; + case "extensions": + directory = extensionsDirectory; + break; + default: + console.error("Invalid input. I'm not going to assume if you want bindings or extensions - these are different beasts."); } -} -async function loadCommonSchemas(bundler) { - // Add common schemas to all versions - const commonSchemas = await fs.promises.readdir(commonSchemasDirectory); - const commonSchemaFiles = commonSchemas.map((file) => path.resolve(commonSchemasDirectory, file)); - for (const commonSchemaFile of commonSchemaFiles) { - const commonSchemaFileContent = require(commonSchemaFile); - bundler.add(commonSchemaFileContent); + const directories = await fs.promises.readdir(directory); + for (const nestedDir of directories) { + const versionDirectories = await fs.promises.readdir(path.resolve(directory, nestedDir)); + const versionDirectoriesFiltered = versionDirectories.filter((file) => fs.lstatSync(path.resolve(directory, nestedDir, file)).isDirectory()); + for (const versionDir of versionDirectoriesFiltered) { + const files = await fs.promises.readdir(path.resolve(directory, nestedDir, versionDir)); + const filesFiltered = files.filter((file) => path.extname(file) === '.json').map((file) => path.resolve(directory, nestedDir, versionDir, file)); + for (const filteredFile of filesFiltered) { + const fileContent = require(filteredFile); + bundler.add(fileContent); + } + } } } - /** * When run, go through all versions that have split definitions and bundles them together. */ @@ -80,15 +89,15 @@ async function loadCommonSchemas(bundler) { } console.log(`The following versions have separate definitions: ${versions.join(',')}`); for (const version of versions) { - const Bundler = require('@hyperjump/json-schema-bundle'); - try { + const Bundler = require("@hyperjump/json-schema-bundle"); + try{ console.log(`Bundling the following version together: ${version}`); const outputFileWithId = path.resolve(outputDirectory, `${version}.json`); const outputFileWithoutId = path.resolve(outputDirectory, `${version}-without-$id.json`); const versionDir = path.resolve(definitionsDirectory, version); await loadDefinitions(Bundler, versionDir); - await loadCommonSchemas(Bundler); - await loadBindings(Bundler); + await loadSchemas(Bundler, 'bindings'); + await loadSchemas(Bundler, 'extensions'); const filePathToBundle = `file://${versionDir}/asyncapi.json`; const fileToBundle = await Bundler.get(filePathToBundle); @@ -97,7 +106,7 @@ async function loadCommonSchemas(bundler) { * bundling schemas into one file with $id */ const bundledSchemaWithId = await Bundler.bundle(fileToBundle); - bundledSchemaWithId.description = `!!Auto generated!! \n Do not manually edit. ${bundledSchemaWithId.description !== undefined && bundledSchemaWithId.description !== null ? bundledSchemaWithId.description : ''}`; + bundledSchemaWithId.description = `!!Auto generated!! \n Do not manually edit. ${bundledSchemaWithId.description ?? ''}`; console.log(`Writing the bundled file WITH $ids to: ${outputFileWithId}`); await fs.promises.writeFile(outputFileWithId, JSON.stringify(bundledSchemaWithId, null, 4)); @@ -108,7 +117,7 @@ async function loadCommonSchemas(bundler) { const bundledSchemaWithoutIds = modifyRefsAndDefinitions(bundledSchemaWithId); console.log(`Writing the bundled file WITHOUT $ids to: ${outputFileWithoutId}`); await fs.promises.writeFile(outputFileWithoutId, JSON.stringify(bundledSchemaWithoutIds, null, 4)); - } catch (e) { + }catch(e) { throw new Error(e); } } @@ -118,6 +127,7 @@ async function loadCommonSchemas(bundler) { /** * Extract file data from reference file path */ + async function loadRefProperties(filePath) { const schemaPath = filePath.$ref; // first we need to turn the path to an absolute file path instead of a generic url @@ -126,23 +136,24 @@ async function loadRefProperties(filePath) { try { const data = await fs.promises.readFile(`../../examples${versionPath}`); return JSON.parse(data); - } catch (e) { - throw new Error(e); + }catch(e) { + throw new Error(e); + } } -} /** * we first update definitions from URL to normal names * than update refs to point to new definitions, always inline never remote */ function modifyRefsAndDefinitions(bundledSchema) { + //first we need to improve names of the definitions from URL to their names for (const def of Object.keys(bundledSchema.definitions)) { const newDefName = getDefinitionName(def); //creating copy of definition under new name so later definition stored under URL name can be removed bundledSchema.definitions[newDefName] = bundledSchema.definitions[def]; - delete bundledSchema.definitions[def]; + delete bundledSchema.definitions[def] } traverse(bundledSchema, replaceRef); @@ -150,7 +161,7 @@ function modifyRefsAndDefinitions(bundledSchema) { traverse(bundledSchema.definitions.openapiSchema_3_0, updateOpenApi); traverse(bundledSchema.definitions['json-schema-draft-07-schema'], updateJsonSchema); - return bundledSchema; + return bundledSchema } /** @@ -167,8 +178,12 @@ function getDefinitionName(def) { const result = bindingsRegex.exec(def); if (result) return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}`; } + if (def.startsWith('http://asyncapi.com/extensions')) { + const result = extensionsRegex.exec(def); + if (result) return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}`; + } - return path.basename(def, '.json'); + return path.basename(def, '.json') } /** @@ -177,11 +192,10 @@ function getDefinitionName(def) { */ function replaceRef(schema) { //new refs will only work if we remove $id that all point to asyncapi.com - delete schema.$id; - - //traversing should take place only in case of schemas with refs - if (schema.$ref === undefined) return; - + delete schema.$id + + //traversing shoudl take place only in case of schemas with refs + if (schema.$ref === undefined ) return; // updating refs that are related to remote URL refs that need to be update and point to inlined versions if (!schema.$ref.startsWith('#')) schema.$ref = `#/definitions/${getDefinitionName(schema.$ref)}`; } @@ -190,12 +204,11 @@ function replaceRef(schema) { * this is a callback used when traversing through json schema * to fix avro schema definitions to point to right direction */ -function updateAvro(schema) { - //traversing should take place only in case of schemas with refs +function updateAvro(schema){ + //traversing shoudl take place only in case of schemas with refs if (schema.$ref === undefined) return; schema.$ref = schema.$ref.replace( - /* eslint-disable sonarjs/no-duplicate-string */ '#/definitions/', '#/definitions/avroSchema_v1/definitions/' ); @@ -205,8 +218,8 @@ function updateAvro(schema) { * this is a callback used when traversing through json schema * to fix open api schema definitions to point to right direction */ -function updateOpenApi(schema) { - //traversing should take place only in case of schemas with refs +function updateOpenApi(schema){ + //traversing shoudl take place only in case of schemas with refs if (schema.$ref === undefined) return; const openApiPropName = 'openapiSchema_3_0'; @@ -224,8 +237,8 @@ function updateOpenApi(schema) { * this is a callback used when traversing through json schema * to fix open api schema definitions to point to right direction */ -function updateJsonSchema(schema) { - //traversing should take place only in case of schemas with refs +function updateJsonSchema(schema){ + //traversing shoudl take place only in case of schemas with refs if (schema.$ref === undefined) return; schema.$ref = schema.$ref.replace( From 85506fb759feb41b158b51fa440c36b99f78e3c9 Mon Sep 17 00:00:00 2001 From: derberg Date: Wed, 17 Apr 2024 17:47:02 +0200 Subject: [PATCH 09/11] update to work with latest master --- schemas/3.0.0-without-$id.json | 138 +++++++++++-------- schemas/3.0.0.json | 140 ++++++++++++-------- test/fixtures/asyncapi.yml | 227 +++++++++++++++++++++++++++----- tools/bundler/index.js | 234 +++++++++++++++++++++------------ 4 files changed, 505 insertions(+), 234 deletions(-) diff --git a/schemas/3.0.0-without-$id.json b/schemas/3.0.0-without-$id.json index 297899e2..2be60cbc 100644 --- a/schemas/3.0.0-without-$id.json +++ b/schemas/3.0.0-without-$id.json @@ -50,68 +50,75 @@ "additionalItems": true }, "info": { - "type": "object", "description": "The object provides metadata about the API. The metadata can be used by the clients if needed.", - "required": [ - "version", - "title" - ], - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d\\.\\x2d_]+$": { - "$ref": "#/definitions/specificationExtension" - } - }, - "properties": { - "title": { - "type": "string", - "description": "A unique and precise title of the API." - }, - "version": { - "type": "string", - "description": "A semantic version number of the API." - }, - "description": { - "type": "string", - "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." - }, - "termsOfService": { - "type": "string", - "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", - "format": "uri" - }, - "contact": { - "$ref": "#/definitions/contact" - }, - "license": { - "$ref": "#/definitions/license" - }, - "tags": { - "type": "array", - "description": "A list of tags for application API documentation control. Tags can be used for logical grouping of applications.", - "items": { - "oneOf": [ - { - "$ref": "#/definitions/Reference" - }, - { - "$ref": "#/definitions/tag" - } - ] + "allOf": [ + { + "type": "object", + "required": [ + "version", + "title" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "#/definitions/specificationExtension" + } }, - "uniqueItems": true - }, - "externalDocs": { - "oneOf": [ - { - "$ref": "#/definitions/Reference" + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." }, - { - "$ref": "#/definitions/externalDocs" + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." + }, + "termsOfService": { + "type": "string", + "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", + "format": "uri" + }, + "contact": { + "$ref": "#/definitions/contact" + }, + "license": { + "$ref": "#/definitions/license" + }, + "tags": { + "type": "array", + "description": "A list of tags for application API documentation control. Tags can be used for logical grouping of applications.", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/tag" + } + ] + }, + "uniqueItems": true + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/externalDocs" + } + ] } - ] + } + }, + { + "$ref": "#/definitions/infoExtensions" } - }, + ], "examples": [ { "title": "AsyncAPI Sample App", @@ -292,6 +299,23 @@ } ] }, + "infoExtensions": { + "type": "object", + "description": "The object that lists all the extensions of Info", + "properties": { + "x-x": { + "$ref": "#/definitions/extensions-x-0.1.0-schema" + } + } + }, + "extensions-x-0.1.0-schema": { + "type": "string", + "description": "This extension allows you to provide the Twitter username of the account representing the team/company of the API.", + "example": [ + "sambhavgupta75", + "AsyncAPISpec" + ] + }, "servers": { "description": "An object representing multiple servers.", "type": "object", diff --git a/schemas/3.0.0.json b/schemas/3.0.0.json index 25bbc016..b100bf28 100644 --- a/schemas/3.0.0.json +++ b/schemas/3.0.0.json @@ -53,68 +53,75 @@ }, "http://asyncapi.com/definitions/3.0.0/info.json": { "$id": "http://asyncapi.com/definitions/3.0.0/info.json", - "type": "object", "description": "The object provides metadata about the API. The metadata can be used by the clients if needed.", - "required": [ - "version", - "title" - ], - "additionalProperties": false, - "patternProperties": { - "^x-[\\w\\d\\.\\x2d_]+$": { - "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" - } - }, - "properties": { - "title": { - "type": "string", - "description": "A unique and precise title of the API." - }, - "version": { - "type": "string", - "description": "A semantic version number of the API." - }, - "description": { - "type": "string", - "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." - }, - "termsOfService": { - "type": "string", - "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", - "format": "uri" - }, - "contact": { - "$ref": "http://asyncapi.com/definitions/3.0.0/contact.json" - }, - "license": { - "$ref": "http://asyncapi.com/definitions/3.0.0/license.json" - }, - "tags": { - "type": "array", - "description": "A list of tags for application API documentation control. Tags can be used for logical grouping of applications.", - "items": { - "oneOf": [ - { - "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" - }, - { - "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" - } - ] + "allOf": [ + { + "type": "object", + "required": [ + "version", + "title" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } }, - "uniqueItems": true - }, - "externalDocs": { - "oneOf": [ - { - "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." }, - { - "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." + }, + "termsOfService": { + "type": "string", + "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", + "format": "uri" + }, + "contact": { + "$ref": "http://asyncapi.com/definitions/3.0.0/contact.json" + }, + "license": { + "$ref": "http://asyncapi.com/definitions/3.0.0/license.json" + }, + "tags": { + "type": "array", + "description": "A list of tags for application API documentation control. Tags can be used for logical grouping of applications.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] } - ] + } + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/infoExtensions.json" } - }, + ], "examples": [ { "title": "AsyncAPI Sample App", @@ -301,6 +308,25 @@ } ] }, + "http://asyncapi.com/definitions/3.0.0/infoExtensions.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/infoExtensions.json", + "type": "object", + "description": "The object that lists all the extensions of Info", + "properties": { + "x-x": { + "$ref": "http://asyncapi.com/extensions/x/0.1.0/schema.json" + } + } + }, + "http://asyncapi.com/extensions/x/0.1.0/schema.json": { + "$id": "http://asyncapi.com/extensions/x/0.1.0/schema.json", + "type": "string", + "description": "This extension allows you to provide the Twitter username of the account representing the team/company of the API.", + "example": [ + "sambhavgupta75", + "AsyncAPISpec" + ] + }, "http://asyncapi.com/definitions/3.0.0/servers.json": { "$id": "http://asyncapi.com/definitions/3.0.0/servers.json", "description": "An object representing multiple servers.", diff --git a/test/fixtures/asyncapi.yml b/test/fixtures/asyncapi.yml index cbf0dc4d..3c72f2e6 100644 --- a/test/fixtures/asyncapi.yml +++ b/test/fixtures/asyncapi.yml @@ -1,42 +1,201 @@ -# yaml-language-server: $schema=YOUR-PROJECTS-DIRECTORY/spec-json-schemas/schemas/2.6.0-without-$id.json -asyncapi: 2.6.0 - +# yaml-language-server: $schema=YOUR-PROJECTS-DIRECTORY/spec-json-schemas/schemas/3.0.0-without-$id.json +asyncapi: 3.0.0 info: - - title: test.mosquitto.org - version: This service is in charge of processing all the events related to comments. - + title: Streetlights Kafka API + version: 1.0.0 + dupa: test + description: "The Smartylighting Streetlights API allows you to remotely manage the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time information about environmental lighting conditions \U0001F4C8\n" + license: + name: Apache 2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0' +defaultContentType: application/json servers: - dev: - url: test.mosquitto.org - protocol: mqtt - + scram-connections: + host: 'test.mykafkacluster.org:18092' + protocol: kafka-secure + description: Test broker secured with scramSha256 + security: + - $ref: '#/components/securitySchemes/saslScram' + tags: + - name: 'env:test-scram' + description: >- + This environment is meant for running internal tests through + scramSha256 + - name: 'kind:remote' + description: This server is a remote server. Not exposed by the application + - name: 'visibility:private' + description: This resource is private and only available to certain users + mtls-connections: + host: 'test.mykafkacluster.org:28092' + protocol: kafka-secure + description: Test broker secured with X509 + security: + - $ref: '#/components/securitySchemes/certs' + tags: + - name: 'env:test-mtls' + description: This environment is meant for running internal tests through mtls + - name: 'kind:remote' + description: This server is a remote server. Not exposed by the application + - name: 'visibility:private' + description: This resource is private and only available to certain users channels: - comment/liked: - - description: Updates the likes count in the database when new like is noticed. - publish: - operationId: commentLiked - message: - description: Message that is being sent when a comment has been liked by someone. - payload: - $ref: '#/components/schemas/commentId' - - comment/unliked: - description: Updates the likes count in the database when comment is unliked. - publish: - operationId: commentUnliked - message: - description: Message that is being sent when a comment has been unliked by someone. - messageId: ddd - payload: - $ref: '#/components/schemas/commentId' - + lightingMeasured: + address: 'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured' + messages: + lightMeasured: + $ref: '#/components/messages/lightMeasured' + description: The topic on which measured values may be produced and consumed. + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + lightTurnOn: + address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.on' + messages: + turnOn: + $ref: '#/components/messages/turnOnOff' + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + lightTurnOff: + address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.off' + messages: + turnOff: + $ref: '#/components/messages/turnOnOff' + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + lightsDim: + address: 'smartylighting.streetlights.1.0.action.{streetlightId}.dim' + messages: + dimLight: + $ref: '#/components/messages/dimLight' + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' +operations: + receiveLightMeasurement: + action: receive + channel: + $ref: '#/channels/lightingMeasured' + summary: >- + Inform about environmental lighting conditions of a particular + streetlight. + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/lightingMeasured/messages/lightMeasured' + turnOn: + action: send + channel: + $ref: '#/channels/lightTurnOn' + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/lightTurnOn/messages/turnOn' + turnOff: + action: send + channel: + $ref: '#/channels/lightTurnOff' + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/lightTurnOff/messages/turnOff' + dimLight: + action: send + channel: + $ref: '#/channels/lightsDim' + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/lightsDim/messages/dimLight' components: + messages: + lightMeasured: + name: lightMeasured + title: Light measured + summary: >- + Inform about environmental lighting conditions of a particular + streetlight. + contentType: application/json + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: '#/components/schemas/lightMeasuredPayload' + turnOnOff: + name: turnOnOff + title: Turn on/off + summary: Command a particular streetlight to turn the lights on or off. + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: '#/components/schemas/turnOnOffPayload' + dimLight: + name: dimLight + title: Dim light + summary: Command a particular streetlight to dim the lights. + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: '#/components/schemas/dimLightPayload' schemas: - commentId: + lightMeasuredPayload: type: object - additionalProperties: false properties: - commentId: + lumens: + type: integer + minimum: 0 + description: Light intensity measured in lumens. + sentAt: + $ref: '#/components/schemas/sentAt' + turnOnOffPayload: + type: object + properties: + command: type: string + enum: + - 'on' + - 'off' + description: Whether to turn on or off the light. + sentAt: + $ref: '#/components/schemas/sentAt' + dimLightPayload: + type: object + properties: + percentage: + type: integer + description: Percentage to which the light should be dimmed to. + minimum: 0 + maximum: 100 + sentAt: + $ref: '#/components/schemas/sentAt' + sentAt: + type: string + format: date-time + description: Date and time when the message was sent. + securitySchemes: + saslScram: + type: scramSha256 + description: Provide your username and password for SASL/SCRAM authentication + certs: + type: X509 + description: Download the certificate files from service provider + parameters: + streetlightId: + description: The ID of the streetlight. + messageTraits: + commonHeaders: + headers: + type: object + properties: + my-app-header: + type: integer + minimum: 0 + maximum: 100 + operationTraits: + kafka: + bindings: + kafka: + clientId: + type: string + enum: + - my-app-id \ No newline at end of file diff --git a/tools/bundler/index.js b/tools/bundler/index.js index ca8f5067..d2d54b62 100644 --- a/tools/bundler/index.js +++ b/tools/bundler/index.js @@ -1,32 +1,111 @@ const path = require('path'); const fs = require('fs'); const traverse = require('json-schema-traverse'); -const { url } = require('inspector'); const definitionsDirectory = path.resolve(__dirname, '../../definitions'); +const commonSchemasDirectory = path.resolve(__dirname, '../../common'); const bindingsDirectory = path.resolve(__dirname, '../../bindings'); const extensionsDirectory = path.resolve(__dirname, '../../extensions'); const outputDirectory = path.resolve(__dirname, '../../schemas'); const JSON_SCHEMA_PROP_NAME = 'json-schema-draft-07-schema'; -console.log(`Looking for separate definitions in the following directory: ${definitionsDirectory}`); -console.log(`Looking for binding version schemas in the following directory: ${bindingsDirectory}`); -console.log(`Looking for extension version schemas in the following directory: ${extensionsDirectory}`); +console.log( + `Looking for separate definitions in the following directory: ${definitionsDirectory}` +); +console.log( + `Looking for binding version schemas in the following directory: ${bindingsDirectory}` +); +console.log( + `Looking for extension version schemas in the following directory: ${extensionsDirectory}` +); console.log(`Using the following output directory: ${outputDirectory}`); // definitionsRegex is used to transform the name of a definition into a valid one to be used in the -without-$id.json files. -const definitionsRegex = /http:\/\/asyncapi\.com\/definitions\/[^/]*\/(.+)\.json#?(.*)/i +const definitionsRegex = + /http:\/\/asyncapi\.com\/definitions\/[^/]*\/(.+)\.json#?(.*)/i; // definitionsRegex is used to transform the name of a binding into a valid one to be used in the -without-$id.json files. -const bindingsRegex = /http:\/\/asyncapi\.com\/(bindings\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i +const bindingsRegex = + /http:\/\/asyncapi\.com\/(bindings\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i; // definitionsRegex is used to transform the name of a binding into a valid one to be used in the -without-$id.json files. -const extensionsRegex = /http:\/\/asyncapi\.com\/(extensions\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i +const extensionsRegex = + /http:\/\/asyncapi\.com\/(extensions\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i; + +/** + * When run, go through all versions that have split definitions and bundles them together. + */ +(async () => { + const versions = await fs.promises.readdir(definitionsDirectory); + console.log(`Ensuring output directory is present ${outputDirectory}`); + if (!fs.existsSync(outputDirectory)) { + await fs.promises.mkdir(outputDirectory); + } + console.log( + `The following versions have separate definitions: ${versions.join(',')}` + ); + for (const version of versions) { + const Bundler = require('@hyperjump/json-schema-bundle'); + try { + console.log(`Bundling the following version together: ${version}`); + const outputFileWithId = path.resolve(outputDirectory, `${version}.json`); + const outputFileWithoutId = path.resolve( + outputDirectory, + `${version}-without-$id.json` + ); + const versionDir = path.resolve(definitionsDirectory, version); + await loadDefinitions(Bundler, versionDir); + await loadCommonSchemas(Bundler); + await loadSchemas(Bundler, 'bindings'); + await loadSchemas(Bundler, 'extensions'); + + const filePathToBundle = `file://${versionDir}/asyncapi.json`; + const fileToBundle = await Bundler.get(filePathToBundle); + + /** + * bundling schemas into one file with $id + */ + const bundledSchemaWithId = await Bundler.bundle(fileToBundle); + bundledSchemaWithId.description = `!!Auto generated!! \n Do not manually edit. ${ + bundledSchemaWithId.description !== undefined && + bundledSchemaWithId.description !== null + ? bundledSchemaWithId.description + : '' + }`; + console.log(`Writing the bundled file WITH $ids to: ${outputFileWithId}`); + await fs.promises.writeFile( + outputFileWithId, + JSON.stringify(bundledSchemaWithId, null, 4) + ); + + /** + * removing ids from schemas and making modifications in definitions name to make sure schemas still work + * this is needed for tools that do not support $id feature in JSON Schema + */ + const bundledSchemaWithoutIds = + modifyRefsAndDefinitions(bundledSchemaWithId); + console.log( + `Writing the bundled file WITHOUT $ids to: ${outputFileWithoutId}` + ); + await fs.promises.writeFile( + outputFileWithoutId, + JSON.stringify(bundledSchemaWithoutIds, null, 4) + ); + } catch (e) { + throw new Error(e); + } + } + console.log('done'); +})(); /** * Function to load all the core AsyncAPI spec definition (except the root asyncapi schema, as that will be loaded later) into the bundler. */ async function loadDefinitions(bundler, versionDir) { const definitions = await fs.promises.readdir(versionDir); - const definitionFiles = definitions.filter((value) => {return !value.includes('asyncapi')}).map((file) => fs.readFileSync(path.resolve(versionDir, file))); + const definitionFiles = definitions + .filter((value) => { + return !value.includes('asyncapi'); + }) + .map((file) => fs.readFileSync(path.resolve(versionDir, file))); const definitionJson = definitionFiles.map((file) => JSON.parse(file)); for (const jsonFile of definitionJson) { @@ -35,7 +114,7 @@ async function loadDefinitions(bundler, versionDir) { const examples = await loadRefProperties(jsonFile.example); // Replacing example property with examples is because using example // to pass an array of example properties is not valid in JSON Schema. - // So replacing it when bundling is the goto solution. + // So replacing it when bundling is the goto solution. jsonFile.examples = examples; delete jsonFile.example; bundler.add(jsonFile); @@ -45,32 +124,40 @@ async function loadDefinitions(bundler, versionDir) { } } - /** * Function to load all schemas into bundler, by "type" you specify if these are "bindings" or "extensions" */ async function loadSchemas(bundler, type) { - let directory; switch (type) { - case "bindings": - directory = bindingsDirectory; - break; - case "extensions": - directory = extensionsDirectory; - break; - default: - console.error("Invalid input. I'm not going to assume if you want bindings or extensions - these are different beasts."); + case 'bindings': + directory = bindingsDirectory; + break; + case 'extensions': + directory = extensionsDirectory; + break; + default: + console.error( + 'Invalid input. I\'m not going to assume if you want bindings or extensions - these are different beasts.' + ); } const directories = await fs.promises.readdir(directory); for (const nestedDir of directories) { - const versionDirectories = await fs.promises.readdir(path.resolve(directory, nestedDir)); - const versionDirectoriesFiltered = versionDirectories.filter((file) => fs.lstatSync(path.resolve(directory, nestedDir, file)).isDirectory()); + const versionDirectories = await fs.promises.readdir( + path.resolve(directory, nestedDir) + ); + const versionDirectoriesFiltered = versionDirectories.filter((file) => + fs.lstatSync(path.resolve(directory, nestedDir, file)).isDirectory() + ); for (const versionDir of versionDirectoriesFiltered) { - const files = await fs.promises.readdir(path.resolve(directory, nestedDir, versionDir)); - const filesFiltered = files.filter((file) => path.extname(file) === '.json').map((file) => path.resolve(directory, nestedDir, versionDir, file)); + const files = await fs.promises.readdir( + path.resolve(directory, nestedDir, versionDir) + ); + const filesFiltered = files + .filter((file) => path.extname(file) === '.json') + .map((file) => path.resolve(directory, nestedDir, versionDir, file)); for (const filteredFile of filesFiltered) { const fileContent = require(filteredFile); bundler.add(fileContent); @@ -78,51 +165,18 @@ async function loadSchemas(bundler, type) { } } } -/** - * When run, go through all versions that have split definitions and bundles them together. - */ -(async () => { - const versions = await fs.promises.readdir(definitionsDirectory); - console.log(`Ensuring output directory is present ${outputDirectory}`); - if (!fs.existsSync(outputDirectory)) { - await fs.promises.mkdir(outputDirectory); - } - console.log(`The following versions have separate definitions: ${versions.join(',')}`); - for (const version of versions) { - const Bundler = require("@hyperjump/json-schema-bundle"); - try{ - console.log(`Bundling the following version together: ${version}`); - const outputFileWithId = path.resolve(outputDirectory, `${version}.json`); - const outputFileWithoutId = path.resolve(outputDirectory, `${version}-without-$id.json`); - const versionDir = path.resolve(definitionsDirectory, version); - await loadDefinitions(Bundler, versionDir); - await loadSchemas(Bundler, 'bindings'); - await loadSchemas(Bundler, 'extensions'); - - const filePathToBundle = `file://${versionDir}/asyncapi.json`; - const fileToBundle = await Bundler.get(filePathToBundle); - - /** - * bundling schemas into one file with $id - */ - const bundledSchemaWithId = await Bundler.bundle(fileToBundle); - bundledSchemaWithId.description = `!!Auto generated!! \n Do not manually edit. ${bundledSchemaWithId.description ?? ''}`; - console.log(`Writing the bundled file WITH $ids to: ${outputFileWithId}`); - await fs.promises.writeFile(outputFileWithId, JSON.stringify(bundledSchemaWithId, null, 4)); - /** - * removing ids from schemas and making modifications in definitions name to make sure schemas still work - * this is needed for tools that do not support $id feature in JSON Schema - */ - const bundledSchemaWithoutIds = modifyRefsAndDefinitions(bundledSchemaWithId); - console.log(`Writing the bundled file WITHOUT $ids to: ${outputFileWithoutId}`); - await fs.promises.writeFile(outputFileWithoutId, JSON.stringify(bundledSchemaWithoutIds, null, 4)); - }catch(e) { - throw new Error(e); - } +async function loadCommonSchemas(bundler) { + // Add common schemas to all versions + const commonSchemas = await fs.promises.readdir(commonSchemasDirectory); + const commonSchemaFiles = commonSchemas.map((file) => + path.resolve(commonSchemasDirectory, file) + ); + for (const commonSchemaFile of commonSchemaFiles) { + const commonSchemaFileContent = require(commonSchemaFile); + bundler.add(commonSchemaFileContent); } - console.log('done'); -})(); +} /** * Extract file data from reference file path @@ -136,32 +190,34 @@ async function loadRefProperties(filePath) { try { const data = await fs.promises.readFile(`../../examples${versionPath}`); return JSON.parse(data); - }catch(e) { - throw new Error(e); - } + } catch (e) { + throw new Error(e); } +} /** * we first update definitions from URL to normal names * than update refs to point to new definitions, always inline never remote */ function modifyRefsAndDefinitions(bundledSchema) { - //first we need to improve names of the definitions from URL to their names for (const def of Object.keys(bundledSchema.definitions)) { const newDefName = getDefinitionName(def); - + //creating copy of definition under new name so later definition stored under URL name can be removed bundledSchema.definitions[newDefName] = bundledSchema.definitions[def]; - delete bundledSchema.definitions[def] + delete bundledSchema.definitions[def]; } traverse(bundledSchema, replaceRef); traverse(bundledSchema.definitions.avroSchema_v1, updateAvro); traverse(bundledSchema.definitions.openapiSchema_3_0, updateOpenApi); - traverse(bundledSchema.definitions['json-schema-draft-07-schema'], updateJsonSchema); + traverse( + bundledSchema.definitions['json-schema-draft-07-schema'], + updateJsonSchema + ); - return bundledSchema + return bundledSchema; } /** @@ -191,15 +247,18 @@ function getDefinitionName(def) { return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}`; } - return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}/${result[4].replace('#/', '')}`; + return `${result[1].replace('/', '-')}-${result[2]}-${ + result[3] + }/${result[4].replace('#/', '')}`; } } if (def.startsWith('http://asyncapi.com/extensions')) { const result = extensionsRegex.exec(def); - if (result) return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}`; + if (result) + return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}`; } - - return path.basename(def, '.json') + + return path.basename(def, '.json'); } /** @@ -208,23 +267,25 @@ function getDefinitionName(def) { */ function replaceRef(schema) { //new refs will only work if we remove $id that all point to asyncapi.com - delete schema.$id - + delete schema.$id; + //traversing shoudl take place only in case of schemas with refs - if (schema.$ref === undefined ) return; + if (schema.$ref === undefined) return; // updating refs that are related to remote URL refs that need to be update and point to inlined versions - if (!schema.$ref.startsWith('#')) schema.$ref = `#/definitions/${getDefinitionName(schema.$ref)}`; + if (!schema.$ref.startsWith('#')) + schema.$ref = `#/definitions/${getDefinitionName(schema.$ref)}`; } /** * this is a callback used when traversing through json schema * to fix avro schema definitions to point to right direction */ -function updateAvro(schema){ +function updateAvro(schema) { //traversing shoudl take place only in case of schemas with refs if (schema.$ref === undefined) return; schema.$ref = schema.$ref.replace( + /* eslint-disable sonarjs/no-duplicate-string */ '#/definitions/', '#/definitions/avroSchema_v1/definitions/' ); @@ -234,12 +295,13 @@ function updateAvro(schema){ * this is a callback used when traversing through json schema * to fix open api schema definitions to point to right direction */ -function updateOpenApi(schema){ +function updateOpenApi(schema) { //traversing shoudl take place only in case of schemas with refs if (schema.$ref === undefined) return; const openApiPropName = 'openapiSchema_3_0'; schema.$ref = schema.$ref.replace( + /* eslint-disable sonarjs/no-duplicate-string */ '#/definitions/', `#/definitions/${openApiPropName}/definitions/` ); @@ -253,7 +315,7 @@ function updateOpenApi(schema){ * this is a callback used when traversing through json schema * to fix open api schema definitions to point to right direction */ -function updateJsonSchema(schema){ +function updateJsonSchema(schema) { //traversing shoudl take place only in case of schemas with refs if (schema.$ref === undefined) return; @@ -265,4 +327,4 @@ function updateJsonSchema(schema){ if (schema.$ref === '#') { schema.$ref = `#/definitions/${JSON_SCHEMA_PROP_NAME}`; } -} \ No newline at end of file +} From dfa021385393ff87b1b4cdcdd84b54a4bca91918 Mon Sep 17 00:00:00 2001 From: derberg Date: Wed, 17 Apr 2024 17:50:02 +0200 Subject: [PATCH 10/11] lint --- tools/bundler/index.js | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/tools/bundler/index.js b/tools/bundler/index.js index d2d54b62..ba384513 100644 --- a/tools/bundler/index.js +++ b/tools/bundler/index.js @@ -7,28 +7,19 @@ const bindingsDirectory = path.resolve(__dirname, '../../bindings'); const extensionsDirectory = path.resolve(__dirname, '../../extensions'); const outputDirectory = path.resolve(__dirname, '../../schemas'); const JSON_SCHEMA_PROP_NAME = 'json-schema-draft-07-schema'; -console.log( - `Looking for separate definitions in the following directory: ${definitionsDirectory}` -); -console.log( - `Looking for binding version schemas in the following directory: ${bindingsDirectory}` -); -console.log( - `Looking for extension version schemas in the following directory: ${extensionsDirectory}` -); +console.log(`Looking for separate definitions in the following directory: ${definitionsDirectory}`); +console.log(`Looking for binding version schemas in the following directory: ${bindingsDirectory}`); +console.log(`Looking for extension version schemas in the following directory: ${extensionsDirectory}`); console.log(`Using the following output directory: ${outputDirectory}`); // definitionsRegex is used to transform the name of a definition into a valid one to be used in the -without-$id.json files. -const definitionsRegex = - /http:\/\/asyncapi\.com\/definitions\/[^/]*\/(.+)\.json#?(.*)/i; +const definitionsRegex = /http:\/\/asyncapi\.com\/definitions\/[^/]*\/(.+)\.json#?(.*)/i; // definitionsRegex is used to transform the name of a binding into a valid one to be used in the -without-$id.json files. -const bindingsRegex = - /http:\/\/asyncapi\.com\/(bindings\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i; +const bindingsRegex = /http:\/\/asyncapi\.com\/(bindings\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i; // definitionsRegex is used to transform the name of a binding into a valid one to be used in the -without-$id.json files. -const extensionsRegex = - /http:\/\/asyncapi\.com\/(extensions\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i; +const extensionsRegex = /http:\/\/asyncapi\.com\/(extensions\/[^/]+)\/([^/]+)\/(.+)\.json(.*)/i; /** * When run, go through all versions that have split definitions and bundles them together. From 6954bdbe18adc1535e1042d6be7c6931b5dd4098 Mon Sep 17 00:00:00 2001 From: Lukasz Gornicki Date: Thu, 18 Apr 2024 11:19:31 +0200 Subject: [PATCH 11/11] remove unnecessary formatting --- tools/bundler/index.js | 46 ++++++++++++------------------------------ 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/tools/bundler/index.js b/tools/bundler/index.js index ba384513..44bf731a 100644 --- a/tools/bundler/index.js +++ b/tools/bundler/index.js @@ -30,18 +30,13 @@ const extensionsRegex = /http:\/\/asyncapi\.com\/(extensions\/[^/]+)\/([^/]+)\/( if (!fs.existsSync(outputDirectory)) { await fs.promises.mkdir(outputDirectory); } - console.log( - `The following versions have separate definitions: ${versions.join(',')}` - ); + console.log(`The following versions have separate definitions: ${versions.join(',')}`); for (const version of versions) { const Bundler = require('@hyperjump/json-schema-bundle'); try { console.log(`Bundling the following version together: ${version}`); const outputFileWithId = path.resolve(outputDirectory, `${version}.json`); - const outputFileWithoutId = path.resolve( - outputDirectory, - `${version}-without-$id.json` - ); + const outputFileWithoutId = path.resolve(outputDirectory, `${version}-without-$id.json`); const versionDir = path.resolve(definitionsDirectory, version); await loadDefinitions(Bundler, versionDir); await loadCommonSchemas(Bundler); @@ -62,24 +57,15 @@ const extensionsRegex = /http:\/\/asyncapi\.com\/(extensions\/[^/]+)\/([^/]+)\/( : '' }`; console.log(`Writing the bundled file WITH $ids to: ${outputFileWithId}`); - await fs.promises.writeFile( - outputFileWithId, - JSON.stringify(bundledSchemaWithId, null, 4) - ); + await fs.promises.writeFile(outputFileWithId, JSON.stringify(bundledSchemaWithId, null, 4)); /** * removing ids from schemas and making modifications in definitions name to make sure schemas still work * this is needed for tools that do not support $id feature in JSON Schema */ - const bundledSchemaWithoutIds = - modifyRefsAndDefinitions(bundledSchemaWithId); - console.log( - `Writing the bundled file WITHOUT $ids to: ${outputFileWithoutId}` - ); - await fs.promises.writeFile( - outputFileWithoutId, - JSON.stringify(bundledSchemaWithoutIds, null, 4) - ); + const bundledSchemaWithoutIds = modifyRefsAndDefinitions(bundledSchemaWithId); + console.log(`Writing the bundled file WITHOUT $ids to: ${outputFileWithoutId}`); + await fs.promises.writeFile(outputFileWithoutId, JSON.stringify(bundledSchemaWithoutIds, null, 4)); } catch (e) { throw new Error(e); } @@ -203,10 +189,7 @@ function modifyRefsAndDefinitions(bundledSchema) { traverse(bundledSchema, replaceRef); traverse(bundledSchema.definitions.avroSchema_v1, updateAvro); traverse(bundledSchema.definitions.openapiSchema_3_0, updateOpenApi); - traverse( - bundledSchema.definitions['json-schema-draft-07-schema'], - updateJsonSchema - ); + traverse(bundledSchema.definitions['json-schema-draft-07-schema'], updateJsonSchema); return bundledSchema; } @@ -238,9 +221,7 @@ function getDefinitionName(def) { return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}`; } - return `${result[1].replace('/', '-')}-${result[2]}-${ - result[3] - }/${result[4].replace('#/', '')}`; + return `${result[1].replace('/', '-')}-${result[2]}-${result[3]}/${result[4].replace('#/', '')}`; } } if (def.startsWith('http://asyncapi.com/extensions')) { @@ -260,11 +241,10 @@ function replaceRef(schema) { //new refs will only work if we remove $id that all point to asyncapi.com delete schema.$id; - //traversing shoudl take place only in case of schemas with refs + //traversing should take place only in case of schemas with refs if (schema.$ref === undefined) return; // updating refs that are related to remote URL refs that need to be update and point to inlined versions - if (!schema.$ref.startsWith('#')) - schema.$ref = `#/definitions/${getDefinitionName(schema.$ref)}`; + if (!schema.$ref.startsWith('#')) schema.$ref = `#/definitions/${getDefinitionName(schema.$ref)}`; } /** @@ -272,7 +252,7 @@ function replaceRef(schema) { * to fix avro schema definitions to point to right direction */ function updateAvro(schema) { - //traversing shoudl take place only in case of schemas with refs + //traversing should take place only in case of schemas with refs if (schema.$ref === undefined) return; schema.$ref = schema.$ref.replace( @@ -287,7 +267,7 @@ function updateAvro(schema) { * to fix open api schema definitions to point to right direction */ function updateOpenApi(schema) { - //traversing shoudl take place only in case of schemas with refs + //traversing should take place only in case of schemas with refs if (schema.$ref === undefined) return; const openApiPropName = 'openapiSchema_3_0'; @@ -307,7 +287,7 @@ function updateOpenApi(schema) { * to fix open api schema definitions to point to right direction */ function updateJsonSchema(schema) { - //traversing shoudl take place only in case of schemas with refs + //traversing should take place only in case of schemas with refs if (schema.$ref === undefined) return; schema.$ref = schema.$ref.replace(