From 85087e4a7af763db3db1ea811a3d19a67aff45b4 Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani <32505158+KhudaDad414@users.noreply.github.com> Date: Fri, 18 Mar 2022 17:48:15 +0500 Subject: [PATCH] refactor: the parsing process (#85) --- package-lock.json | 53 ++++++++----- package.json | 1 + src/ComponentProvider.ts | 122 +++++++++++------------------ src/Optimizers/MoveToComponents.ts | 2 +- src/Utils/Helpers.ts | 4 +- test/Utils/Helpers.spec.ts | 12 +-- 6 files changed, 89 insertions(+), 105 deletions(-) diff --git a/package-lock.json b/package-lock.json index 35f75125..d0e6bb6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,17 @@ { "name": "@asyncapi/optimizer", - "version": "0.1.2", - "lockfileVersion": 2, "version": "0.1.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@asyncapi/optimizer", - "version": "0.1.2", + "version": "0.1.6", "license": "Apache-2.0", "dependencies": { - "@asyncapi/parser": "^1.10.2", + "@asyncapi/parser": "^1.11.1", "js-yaml": "^4.1.0", + "jsonpath-plus": "^6.0.1", "lodash": "^4.17.21", "merge-deep": "^3.0.3" }, @@ -52,12 +51,12 @@ } }, "node_modules/@asyncapi/parser": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@asyncapi/parser/-/parser-1.10.2.tgz", - "integrity": "sha512-qz33bkvPqhJN5wsjoZw9GzGvEFbQqfRzjPFsbc+wmG5+8nm8tiYoIa0d1SX/HR2dI+CkXa5CsH4jn0YUe65YOQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@asyncapi/parser/-/parser-1.14.1.tgz", + "integrity": "sha512-ZsNQc/HXmGDrxkYBRrGou/JrWn+FLHYdQQI0WPOylSL+LNfaWtLlIcYwbcj61l+jLLFqIe0DK/yZbmjxm0ydqw==", "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.6", - "@asyncapi/specs": "^2.11.0", + "@asyncapi/specs": "^2.13.0", "@fmvilas/pseudo-yaml-ast": "^0.3.1", "ajv": "^6.10.1", "js-yaml": "^3.13.1", @@ -80,9 +79,9 @@ } }, "node_modules/@asyncapi/specs": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-2.11.0.tgz", - "integrity": "sha512-H2GNlAgrI9TZGWdlTUit4Ue7YFCTmELY1vMmDtgNTsFtpWQOTRf6NlTcItagrKHSo7zh6gnyhGLQCFC/rSfjUg==" + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-2.13.0.tgz", + "integrity": "sha512-X0OrxJtzwRH8iLILO/gUTDqjGVPmagmdlgdyuBggYAoGXzF6ZuAws3XCLxtPNve5eA/0V/1puwpUYEGekI22og==" }, "node_modules/@babel/code-frame": { "version": "7.12.11", @@ -6228,6 +6227,14 @@ "node >= 0.2.0" ] }, + "node_modules/jsonpath-plus": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-6.0.1.tgz", + "integrity": "sha512-EvGovdvau6FyLexFH2OeXfIITlgIbgZoAZe3usiySeaIDm5QS+A10DKNpaPBBqqRSZr2HN6HVNXxtwUAr2apEw==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", @@ -9906,6 +9913,11 @@ "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, "engines": { "node": ">=0.10.0" } @@ -12793,12 +12805,12 @@ } }, "@asyncapi/parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@asyncapi/parser/-/parser-1.11.1.tgz", - "integrity": "sha512-9ugfMHw1IYQVuUk5e4NQol4XPmfLUvniTX6tIo/AxUToDv/ag3Wy+m+2Pc92wPiuHQBYFbBnCam+hN8/cHwMuA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@asyncapi/parser/-/parser-1.14.1.tgz", + "integrity": "sha512-ZsNQc/HXmGDrxkYBRrGou/JrWn+FLHYdQQI0WPOylSL+LNfaWtLlIcYwbcj61l+jLLFqIe0DK/yZbmjxm0ydqw==", "requires": { "@apidevtools/json-schema-ref-parser": "^9.0.6", - "@asyncapi/specs": "^2.11.0", + "@asyncapi/specs": "^2.13.0", "@fmvilas/pseudo-yaml-ast": "^0.3.1", "ajv": "^6.10.1", "js-yaml": "^3.13.1", @@ -12820,9 +12832,9 @@ } }, "@asyncapi/specs": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-2.12.0.tgz", - "integrity": "sha512-X4Xkrl+9WXSk5EJhsueIxNx6ymHI5wpkw4ofetV+VRnPLNob/XO4trPSJClrL5hlknxbGADLvlrkI5d3XJ996g==" + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-2.13.0.tgz", + "integrity": "sha512-X0OrxJtzwRH8iLILO/gUTDqjGVPmagmdlgdyuBggYAoGXzF6ZuAws3XCLxtPNve5eA/0V/1puwpUYEGekI22og==" }, "@babel/code-frame": { "version": "7.12.11", @@ -17878,6 +17890,11 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, + "jsonpath-plus": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-6.0.1.tgz", + "integrity": "sha512-EvGovdvau6FyLexFH2OeXfIITlgIbgZoAZe3usiySeaIDm5QS+A10DKNpaPBBqqRSZr2HN6HVNXxtwUAr2apEw==" + }, "JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", diff --git a/package.json b/package.json index c02e65cf..4f15fb31 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "dependencies": { "@asyncapi/parser": "^1.11.1", "js-yaml": "^4.1.0", + "jsonpath-plus": "^6.0.1", "lodash": "^4.17.21", "merge-deep": "^3.0.3" } diff --git a/src/ComponentProvider.ts b/src/ComponentProvider.ts index 8d339f4b..40f6d5a7 100644 --- a/src/ComponentProvider.ts +++ b/src/ComponentProvider.ts @@ -1,94 +1,60 @@ -import type { AsyncAPIDocument, ChannelParameter, Message, MessageTrait, Schema } from '@asyncapi/parser'; - +import type { + AsyncAPIDocument, + ChannelParameter, + Message, + Schema, +} from '@asyncapi/parser'; + +import { JSONPath } from 'jsonpath-plus'; +import _ from 'lodash'; /** * This class will provide all sorts of data for optimizers. * * @private */ export class ComponentProvider { + messagePaths = ['$.channels.*.*.message[?(@property !== "oneOf")]^', '$.channels.*.*.message.oneOf.*', '$.components.messages.*']; + schemaPaths = [ + '$.channels.*.*.message.traits[*]..[?(@.type)]', + '$.channels.*.*.message.headers', + '$.channels.*.*.message.headers..[?(@.type)]', + '$.channels.*.*.message.payload', + '$.channels.*.*.message.payload..[?(@.type)]', + '$.channels.*.parameters.*.schema[?(@.type)]', + '$.channels.*.parameters.*.schema..[?(@.type)]', + '$.components.schemas..[?(@.type)]', + ]; + + parameterPaths = ['$.channels.*.parameters.*', '$.components.parameters.*']; messages = new Map(); schemas = new Map(); parameters = new Map(); constructor(private document: AsyncAPIDocument) { - this.scanChannels(); - this.scanComponents(); - } - - private scanChannels(): void { - const channels = this.document.channels(); - for (const channelName in channels) { - const path = `channels.${channelName}`; - const channel = this.document.channel(channelName); - - if (channel.hasPublish()) { - const message = channel.publish().message(); - const currentPath = `${path}.publish.message`; - this.scanMessage(currentPath, message); - this.messages.set(currentPath, message); - } - - if (channel.hasSubscribe()) { - const message = channel.subscribe().message(); - const currentPath = `${path}.subscribe.message`; - this.scanMessage(currentPath, message); - this.messages.set(currentPath, message); - } - - if (channel.hasParameters()) { - const currentPath = `${path}.parameters`; - for (const [name, channelParameter] of Object.entries(channel.parameters())) { - this.parameters.set(`${currentPath}.${name}`, channelParameter); - } - } - } - } - - private scanSchema(path: string, schema: Schema): void { - if (!schema) {return;} - this.schemas.set(path, schema); - const schemaProperties = schema.properties(); - for (const [propertyName, propertySchema] of Object.entries(schemaProperties)) { - this.schemas.set(`${path}.properties.${propertyName}`, propertySchema); - } - } - - private scanMessageTraits(path: string, traits: MessageTrait[]): void { - for (const [index, trait] of Object.entries(traits)) { - if (trait.headers()) { - this.scanSchema(`${path}[${index}].headers`, trait.headers()); - } - } + this.messages = this.parseComponents(this.messagePaths); + this.schemas = this.parseComponents(this.schemaPaths); + this.parameters = this.parseComponents(this.parameterPaths); } - private scanMessage(path: string, message: Message): void { - this.scanSchema(`${path}.payload`, message.payload()); - this.scanSchema(`${path}.headers`, message.headers()); - this.scanMessageTraits(`${path}.traits`, message.traits()); + private toLodashPath(path: string) { + return path.replace(/'\]\['/g, '.') + .slice(3, -2) + .replace(/'\]/g, '') + .replace(/\['/g, '.'); } - - private scanComponents(): void { - const components = this.document.components(); - if (!components) { - return; - } - - if (components.hasMessages()) { - for (const [messageName, message] of Object.entries(components.messages())) { - this.messages.set(`components.messages.${messageName}`, message); - } - } - - if (components.hasSchemas()) { - for (const [schemaName, schema] of Object.entries(components.schemas())) { - this.schemas.set(`components.schemas.${schemaName}`, schema); - } - } - - if (components.hasParameters()) { - for (const [parameterName, parameter] of Object.entries(components.parameters())) { - this.parameters.set(`components.parameters.${parameterName}`, parameter); - } - } + private parseComponents(paths: string[]): any { + return _.chain(paths) + .map((path) => { + return JSONPath({ + resultType: 'all', + json: this.document.json(), + path + }); + }) + .flatten() + .reduce((red, component) => { + return red.set(this.toLodashPath(component.path), component.value); + }, new Map()) + .value(); } } diff --git a/src/Optimizers/MoveToComponents.ts b/src/Optimizers/MoveToComponents.ts index 5dd55e16..0789cb09 100644 --- a/src/Optimizers/MoveToComponents.ts +++ b/src/Optimizers/MoveToComponents.ts @@ -81,7 +81,7 @@ export class MoveToComponents implements OptimizerInterface { //check if the component already has a copy in components section of the specification. If it already has then we don't need to apply this optimization. //It will be taken care of by ReuseComponents if (this.doesHaveACopy(value1, components)) { continue; } - const componentName = value1.json().name || `${componentType}-${counter++}`; + const componentName = value1.name || `${componentType}-${counter++}`; const target = `components.${componentType}s.${componentName}`; elements.push({ path: key1, diff --git a/src/Utils/Helpers.ts b/src/Utils/Helpers.ts index e8332de0..2a19c902 100644 --- a/src/Utils/Helpers.ts +++ b/src/Utils/Helpers.ts @@ -40,9 +40,9 @@ const compareComponents = (x: any, y: any): boolean => { //Compares two components but also considers equality check. the referential equality check can be disabled by referentialEqualityCheck argument. const isEqual = (component1: any, component2: any, referentialEqualityCheck: boolean): boolean => { if (referentialEqualityCheck) { - return component1.json() === component2.json() || compareComponents(component1.json(), component2.json()); + return component1 === component2 || compareComponents(component1, component2); } - return component1.json() !== component2.json() && compareComponents(component1.json(), component2.json()); + return component1 !== component2 && compareComponents(component1, component2); }; const isInComponents = (path: string): boolean => { diff --git a/test/Utils/Helpers.spec.ts b/test/Utils/Helpers.spec.ts index c64a8528..0cdea6b3 100644 --- a/test/Utils/Helpers.spec.ts +++ b/test/Utils/Helpers.spec.ts @@ -3,28 +3,28 @@ import _ from 'lodash'; import { compareComponents, isEqual, isInComponents, isInChannels, toJS } from '../../src/Utils'; describe('Helpers', () => { - const testObject1 = { json: jest.fn().mockReturnValueOnce({ streetlightId: { schema: { type: 'string', 'x-extension': 'different_value' } } }) }; - const testObject1_copy = { json: jest.fn().mockReturnValueOnce({ streetlightId: { schema: { type: 'string', 'x-extension': 'value' } } }) }; - const testObject2 = { json: jest.fn().mockReturnValueOnce({ streetlightId: { schema: { type: 'number' } } }) }; + const testObject1 = { streetlightId: { schema: { type: 'string', 'x-extension': 'different_value' } } }; + const testObject1_copy = { streetlightId: { schema: { type: 'string', 'x-extension': 'value' } } }; + const testObject2 = { streetlightId: { schema: { type: 'number' } } }; const testObject2_reference = testObject2; describe('compareComponents', () => { test('should return true.', () => { - expect(compareComponents(testObject1.json(), testObject1_copy.json())).toEqual(true); + expect(compareComponents(testObject1, testObject1_copy)).toEqual(true); }); test('should return false.', () => { - expect(compareComponents(testObject1.json(), testObject2.json())).toEqual(false); + expect(compareComponents(testObject1, testObject2)).toEqual(false); }); }); describe('isEqual', () => { test('should return true.', () => { expect(isEqual(testObject2, testObject2_reference, true)).toEqual(true); expect(isEqual(testObject1, testObject1_copy, true)).toEqual(true); + expect(isEqual(testObject1, testObject1_copy, false)).toEqual(true); }); test('should return false.', () => { - expect(isEqual(testObject1, testObject1_copy, false)).toEqual(false); expect(isEqual(testObject2, testObject2_reference, false)).toEqual(false); }); });