From 803a30d6a0a478f3d9592049b64da145af7fea2a Mon Sep 17 00:00:00 2001 From: Animesh Kumar Date: Thu, 20 Jun 2024 03:09:53 +0530 Subject: [PATCH] fix: update script, update spec, json merge patch Changes: - update script to be suited for json-merge-patch, - suggested by Sergio via Slack DM more details about merge-patch here: https://datatracker.ietf.org/doc/html/rfc7396 - update spec file with comments suited for json-merge-patch - update dependencies --- .../embedded-examples-validation.js | 79 +------- scripts/validation/package-lock.json | 13 +- scripts/validation/package.json | 4 +- spec/asyncapi.md | 174 +++++++++--------- 4 files changed, 112 insertions(+), 158 deletions(-) diff --git a/scripts/validation/embedded-examples-validation.js b/scripts/validation/embedded-examples-validation.js index 5ea7ae7a..9c14cf41 100644 --- a/scripts/validation/embedded-examples-validation.js +++ b/scripts/validation/embedded-examples-validation.js @@ -1,7 +1,8 @@ const fs = require('fs'); -const { JSONPath } = require('jsonpath-plus'); const yaml = require('js-yaml'); const { Parser } = require('@asyncapi/parser'); +const mergePatch = require('json-merge-patch'); +const jsonpointer = require('jsonpointer'); const parser = new Parser(); // Read the markdown file @@ -19,8 +20,6 @@ function extractCommentsAndExamples(content) { const format = match[2].trim(); const exampleContent = match[3].trim(); - // console.log(`Extracted example in format: ${format}`); - let example; if (format === 'json') { example = JSON.parse(exampleContent); @@ -48,75 +47,18 @@ function extractCommentsAndExamples(content) { // Extract comments and examples from the markdown file const combinedData = extractCommentsAndExamples(markdownContent); -// Function to deeply merge two objects without overwriting existing nested structures -function deepMerge(target, source) { - for (const key of Object.keys(source)) { - if (source[key] instanceof Object && key in target && target[key] instanceof Object) { - target[key] = deepMerge(target[key], source[key]); - } else { - target[key] = source[key]; - } - } - return target; -} - -// Function to set a value in a JSON object using JSONPath, creating missing fields if necessary -function setValueByPath(obj, path, value) { - const pathParts = path.replace(/\$/g, '').split('.').slice(1); // Remove the root "$" and split path - let current = obj; - - pathParts.forEach((part, index) => { - if (index === pathParts.length - 1) { - current[part] = value; // Set the new value directly - } else { - if (!current[part]) { - current[part] = {}; // Create object if it doesn't exist - } - current = current[part]; - } - }); -} - -// Function to apply updates to the document +// Function to apply JSON Merge Patch updates to the document function applyUpdates(updates, baseDoc) { updates.forEach(update => { try { if (update.json_path === "$") { - // console.log(`\nProcessing update for '${update.name}' at root path '$'`); - for (const key in update.example) { - baseDoc[key] = update.example[key]; - } + // Apply patch directly at the root + baseDoc = mergePatch.apply(baseDoc, update.example); } else { - const results = JSONPath({ path: update.json_path, json: baseDoc, resultType: 'all' }); - - // console.log(`\nProcessing update for '${update.name}-${update.format}-format' at path '${update.json_path}'`); - - const pathParts = update.json_path.split('.'); - const targetKey = pathParts[pathParts.length - 1]; - - // Check if the top-level key of the example JSON matches the target key - let dataToMerge = update.example; - if (dataToMerge.hasOwnProperty(targetKey)) { - dataToMerge = dataToMerge[targetKey]; - } - - if (results.length === 0) { - // console.log(`\nPath not found, creating path: '${update.json_path}'`); - setValueByPath(baseDoc, update.json_path, dataToMerge); // Create the path if it doesn't exist - } else { - results.forEach(result => { - const parent = result.parent; - const parentProperty = result.parentProperty; - // console.log(`\nMerging data at path: '${update.json_path}'`); - if (Array.isArray(parent[parentProperty])) { - // If the existing data is an array, add the update data as an array item - parent[parentProperty].push(dataToMerge); - } else { - // Otherwise, deep merge the existing data with the new data - parent[parentProperty] = deepMerge(parent[parentProperty], dataToMerge); - } - }); - } + // Apply patch at a specified JSON Pointer path + const targetObject = jsonpointer.get(baseDoc, update.json_path); + const patchedObject = mergePatch.apply(targetObject || {}, update.example); + jsonpointer.set(baseDoc, update.json_path, patchedObject); } } catch (e) { console.error(`\nError processing update for '${update.name}' at path '${update.json_path}'`, e); @@ -147,7 +89,6 @@ async function validateParser(document, name) { } } - // Iterate over the combinedData array, apply updates, and validate each document const baseDocPath = './base-doc.json'; const baseDocSecuritySchemePath = './base-doc-security-scheme-object.json'; @@ -175,4 +116,4 @@ Promise.all(validationPromises) .catch((error) => { console.error('Error during validations:', error); // process.exit(1); - }); + }); \ No newline at end of file diff --git a/scripts/validation/package-lock.json b/scripts/validation/package-lock.json index 7909f7c3..adc757a7 100644 --- a/scripts/validation/package-lock.json +++ b/scripts/validation/package-lock.json @@ -11,7 +11,9 @@ "devDependencies": { "@asyncapi/parser": "^3.1.0", "js-yaml": "^4.1.0", - "jsonpath-plus": "^9.0.0" + "json-merge-patch": "^1.0.2", + "jsonpath-plus": "^9.0.0", + "jsonpointer": "^5.0.1" } }, "node_modules/@asyncapi/parser": { @@ -1385,6 +1387,15 @@ "node": ">= 10.16.0" } }, + "node_modules/json-merge-patch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-merge-patch/-/json-merge-patch-1.0.2.tgz", + "integrity": "sha512-M6Vp2GN9L7cfuMXiWOmHj9bEFbeC250iVtcKQbqVgEsDVYnIsrNsbU+h/Y/PkbBQCtEa4Bez+Ebv0zfbC8ObLg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", diff --git a/scripts/validation/package.json b/scripts/validation/package.json index 2d41f540..4765394c 100644 --- a/scripts/validation/package.json +++ b/scripts/validation/package.json @@ -11,6 +11,8 @@ "devDependencies": { "@asyncapi/parser": "^3.1.0", "js-yaml": "^4.1.0", - "jsonpath-plus": "^9.0.0" + "json-merge-patch": "^1.0.2", + "jsonpath-plus": "^9.0.0", + "jsonpointer": "^5.0.1" } } diff --git a/spec/asyncapi.md b/spec/asyncapi.md index 07545d4e..1bb9f62a 100644 --- a/spec/asyncapi.md +++ b/spec/asyncapi.md @@ -273,7 +273,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### Info Object Example - + ```json { "title": "AsyncAPI Sample App", @@ -301,7 +301,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml title: AsyncAPI Sample App version: 1.0.1 @@ -335,7 +335,7 @@ Field Name | Type | Description This object MAY be extended with [Specification Extensions](#specificationExtensions). - + ```json { "name": "API Support", @@ -344,7 +344,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml name: API Support url: https://www.example.com/support @@ -366,7 +366,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### License Object Example - + ```json { "name": "Apache 2.0", @@ -374,7 +374,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0.html @@ -392,7 +392,7 @@ Field Pattern | Type | Description ##### Servers Object Example - + ```json { "development": { @@ -434,7 +434,7 @@ Field Pattern | Type | Description } ``` - + ```yaml development: host: localhost:5672 @@ -487,7 +487,7 @@ Field Name | Type | Description A single server would be described as: - + ```json { "host": "kafka.in.mycompany.com:9092", @@ -497,7 +497,7 @@ A single server would be described as: } ``` - + ```yaml host: kafka.in.mycompany.com:9092 description: Production Kafka broker. @@ -507,7 +507,7 @@ protocolVersion: '3.2' An example of a server that has a `pathname`: - + ```json { "host": "rabbitmq.in.mycompany.com:5672", @@ -517,7 +517,7 @@ An example of a server that has a `pathname`: } ``` - + ```yaml host: rabbitmq.in.mycompany.com:5672 pathname: /production @@ -542,7 +542,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### Server Variable Object Example - + ```json { "host": "rabbitmq.in.mycompany.com:5672", @@ -561,7 +561,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml host: 'rabbitmq.in.mycompany.com:5672' pathname: '/{env}' @@ -607,7 +607,7 @@ Field Pattern | Type | Description ##### Channels Object Example - + ```json { "userSignedUp": { @@ -621,7 +621,7 @@ Field Pattern | Type | Description } ``` - + ```yaml userSignedUp: address: 'user.signedup' @@ -653,7 +653,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### Channel Object Example - + ```json { "address": "users.{userId}", @@ -695,7 +695,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml address: 'users.{userId}' title: Users channel @@ -742,7 +742,7 @@ Field Pattern | Type | Description ##### Messages Object Example - + ```json { "userSignedUp": { @@ -754,7 +754,7 @@ Field Pattern | Type | Description } ``` - + ```yaml userSignedUp: $ref: '#/components/messages/userSignedUp' @@ -776,7 +776,7 @@ Field Pattern | Type | Description ##### Operations Object Example - + ```json { "onUserSignUp": { @@ -804,7 +804,7 @@ Field Pattern | Type | Description } ``` - + ```yaml onUserSignUp: title: User sign up @@ -849,7 +849,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### Operation Object Example - + ```json { "title": "User sign up", @@ -897,7 +897,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml title: User sign up summary: Action to sign a user up. @@ -951,7 +951,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### Operation Trait Object Example - + ```json { "bindings": { @@ -962,7 +962,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml bindings: amqp: @@ -1000,7 +1000,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### Examples - + ```json { "description": "Consumer inbox", @@ -1008,7 +1008,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml description: Consumer Inbox location: $message.header#/replyTo @@ -1028,7 +1028,7 @@ Field Pattern | Type | Description ##### Parameters Object Example - + ```json { "address": "user/{userId}/signedup", @@ -1040,7 +1040,7 @@ Field Pattern | Type | Description } ``` - + ```yaml address: user/{userId}/signedup parameters: @@ -1066,7 +1066,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### Parameter Object Example - + ```json { "address": "user/{userId}/signedup", @@ -1079,7 +1079,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml address: user/{userId}/signedup parameters: @@ -1234,7 +1234,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### Message Object Example - + ```json { "name": "UserSignup", @@ -1299,7 +1299,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml name: UserSignup title: User signup @@ -1346,7 +1346,7 @@ examples: Example using Avro to define the payload: - + ```json { "name": "UserSignup", @@ -1367,7 +1367,7 @@ Example using Avro to define the payload: } ``` - + ```yaml name: UserSignup title: User signup @@ -1409,14 +1409,14 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### Message Trait Object Example - + ```json { "contentType": "application/json" } ``` - + ```yaml contentType: application/json ``` @@ -1438,7 +1438,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### Message Example Object Example - + ```json { "name": "SimpleSignup", @@ -1458,7 +1458,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml name: SimpleSignup summary: A simple UserSignup example message @@ -1492,7 +1492,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### Tag Object Example - + ```json { "name": "user", @@ -1500,7 +1500,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml name: user description: User-related messages @@ -1521,7 +1521,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### External Documentation Object Example - + ```json { "description": "Find more info here", @@ -1529,7 +1529,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml description: Find more info here url: https://example.com @@ -1553,14 +1553,14 @@ This object cannot be extended with additional properties and any properties add ##### Reference Object Example - + ```json { "$ref": "#/components/schemas/Pet" } ``` - + ```yaml $ref: '#/components/schemas/Pet' ``` @@ -1610,7 +1610,7 @@ my.org.User ##### Components Object Example - + ```json { "components": { @@ -1744,7 +1744,7 @@ my.org.User } ``` - + ```yaml components: schemas: @@ -1866,7 +1866,7 @@ Name | Allowed values | Notes ###### Multi Format Schema Object Example with Avro - + ```yaml channels: example: @@ -1972,7 +1972,7 @@ As such, inline schema definitions, which do not have a given id, _cannot_ be us ###### Primitive Sample - + ```json { "type": "string", @@ -1980,7 +1980,7 @@ As such, inline schema definitions, which do not have a given id, _cannot_ be us } ``` - + ```yaml type: string format: email @@ -1988,7 +1988,7 @@ format: email ###### Simple Model - + ```json { "type": "object", @@ -2011,7 +2011,7 @@ format: email } ``` - + ```yaml type: object required: @@ -2031,7 +2031,7 @@ properties: For a simple string to string mapping: - + ```json { "type": "object", @@ -2041,7 +2041,7 @@ For a simple string to string mapping: } ``` - + ```yaml type: object additionalProperties: @@ -2050,7 +2050,7 @@ additionalProperties: For a string to model mapping: - + ```json { "type": "object", @@ -2060,7 +2060,7 @@ For a string to model mapping: } ``` - + ```yaml type: object additionalProperties: @@ -2069,7 +2069,7 @@ additionalProperties: ###### Model with Example - + ```json { "type": "object", @@ -2094,7 +2094,7 @@ additionalProperties: } ``` - + ```yaml type: object properties: @@ -2112,7 +2112,7 @@ examples: ###### Model with Boolean Schemas - + ```json { "type": "object", @@ -2126,7 +2126,7 @@ examples: } ``` - + ```yaml type: object required: @@ -2138,7 +2138,7 @@ properties: ###### Models with Composition - + ```json { "schemas": { @@ -2181,7 +2181,7 @@ properties: } ``` - + ```yaml schemas: ErrorModel: @@ -2209,7 +2209,7 @@ schemas: ###### Models with Polymorphism Support - + ```json { "schemas": { @@ -2303,7 +2303,7 @@ schemas: } ``` - + ```yaml schemas: Pet: @@ -2401,21 +2401,21 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ###### User/Password Authentication Sample - + ```json { "type": "userPassword" } ``` - + ```yaml type: userPassword ``` ###### API Key Authentication Sample - + ```json { "type": "apiKey", @@ -2423,7 +2423,7 @@ type: userPassword } ``` - + ```yaml type: apiKey in: user @@ -2431,35 +2431,35 @@ in: user ###### X.509 Authentication Sample - + ```json { "type": "X509" } ``` - + ```yaml type: X509 ``` ###### End-to-end Encryption Authentication Sample - + ```json { "type": "symmetricEncryption" } ``` - + ```yaml type: symmetricEncryption ``` ###### Basic Authentication Sample - + ```json { "type": "http", @@ -2467,7 +2467,7 @@ type: symmetricEncryption } ``` - + ```yaml type: http scheme: basic @@ -2475,7 +2475,7 @@ scheme: basic ###### API Key Sample - + ```json { "type": "httpApiKey", @@ -2484,7 +2484,7 @@ scheme: basic } ``` - + ```yaml type: httpApiKey name: api_key @@ -2493,7 +2493,7 @@ in: header ###### JWT Bearer Sample - + ```json { "type": "http", @@ -2502,7 +2502,7 @@ in: header } ``` - + ```yaml type: http scheme: bearer @@ -2511,7 +2511,7 @@ bearerFormat: JWT ###### Implicit OAuth2 Sample - + ```json { "type": "oauth2", @@ -2530,7 +2530,7 @@ bearerFormat: JWT } ``` - + ```yaml type: oauth2 flows: @@ -2545,14 +2545,14 @@ scopes: ###### SASL Sample - + ```json { "type": "scramSha512" } ``` - + ```yaml type: scramSha512 ``` @@ -2589,7 +2589,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### OAuth Flow Object Examples - + ```json { "authorizationUrl": "https://example.com/api/oauth/dialog", @@ -2601,7 +2601,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml authorizationUrl: https://example.com/api/oauth/dialog tokenUrl: https://example.com/api/oauth/token @@ -2627,7 +2627,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ##### Examples - + ```json { "description": "Default Correlation ID", @@ -2635,7 +2635,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens } ``` - + ```yaml description: Default Correlation ID location: $message.header#/correlationId