diff --git a/.eslintrc b/.eslintrc index c33c1ff6..43b209de 100644 --- a/.eslintrc +++ b/.eslintrc @@ -121,7 +121,7 @@ } ], "no-console": "error", - "wrap-regex": 2, + "wrap-regex": 0, //"linebreak-style": ["error", "unix"], "linebreak-style": 0, "arrow-spacing": [ diff --git a/examples/index.js b/examples/index.js index 93c0f31c..ddf4f36b 100644 --- a/examples/index.js +++ b/examples/index.js @@ -1,94 +1,19 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires const { Optimizer } = require('../lib/Optimizer') -const yaml = ` -asyncapi: 2.0.0 -info: - title: Streetlights API - version: '1.0.0' -channels: - smartylighting/event/{streetlightId}/lighting/measured: - parameters: - #this parameter is duplicated. it can be moved to components and ref-ed from here. - streetlightId: - schema: - type: string - subscribe: - operationId: receiveLightMeasurement - traits: - - bindings: - kafka: - clientId: my-app-id - message: - name: lightMeasured - title: Light measured - contentType: application/json - traits: - - headers: - type: object - properties: - my-app-header: - type: integer - minimum: 0 - maximum: 100 - payload: - type: object - properties: - lumens: - type: integer - minimum: 0 - #full form is used, we can ref it to: #/components/schemas/sentAt - sentAt: - type: string - format: date-time - smartylighting/action/{streetlightId}/turn/on: - parameters: - streetlightId: - schema: - type: string - publish: - operationId: turnOn - traits: - - bindings: - kafka: - clientId: my-app-id - message: - name: turnOnOff - title: Turn on/off - traits: - - headers: - type: object - properties: - my-app-header: - type: integer - minimum: 0 - maximum: 100 - payload: - type: object - properties: - sentAt: - $ref: "#/components/schemas/sentAt" -components: - messages: - #libarary should be able to find and delete this message, because it is not used anywhere. - unusedMessage: - name: unusedMessage - title: This message is not used in any channel. - - schemas: - #this schema is ref-ed in one channel and used full form in another. library should be able to identify and ref the second channel as well. - sentAt: - type: string - format: date-time` -const optimizer = new Optimizer(yaml) +// read input.yaml file synconously and store it as an string +const input = require('fs').readFileSync('./examples/input.yaml', 'utf8') +const optimizer = new Optimizer(input) optimizer.getReport().then((report) => { - //console.log(JSON.stringify(report)) + console.log(JSON.stringify(report)) const optimizedDocument = optimizer.getOptimizedDocument({ + output: 'YAML', rules: { reuseComponents: true, removeComponents: true, moveToComponents: true, }, }) - console.log(optimizedDocument) + //store optimizedDocument as to output.yaml + require('fs').writeFileSync('./examples/output.yaml', optimizedDocument) }) diff --git a/examples/input.yaml b/examples/input.yaml new file mode 100644 index 00000000..c94091c6 --- /dev/null +++ b/examples/input.yaml @@ -0,0 +1,253 @@ +asyncapi: 3.0.0 +info: + title: Streetlights MQTT API + version: 1.0.0 + 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: + production: + host: 'test.mosquitto.org:{port}' + protocol: mqtt + description: Test broker + variables: + port: + description: Secure connection (TLS) is available through port 8883. + default: '1883' + enum: + - '1883' + - '8883' + security: + - $ref: '#/components/securitySchemes/apiKey' + - type: oauth2 + description: Flows to support OAuth 2.0 + flows: + implicit: + authorizationUrl: 'https://authserver.example/auth' + availableScopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + password: + tokenUrl: 'https://authserver.example/token' + availableScopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + clientCredentials: + tokenUrl: 'https://authserver.example/token' + availableScopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + authorizationCode: + authorizationUrl: 'https://authserver.example/auth' + tokenUrl: 'https://authserver.example/token' + refreshUrl: 'https://authserver.example/refresh' + availableScopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + scopes: + - 'streetlights:on' + - 'streetlights:off' + - 'streetlights:dim' + - $ref: '#/components/securitySchemes/openIdConnectWellKnown' + tags: + - name: 'env:production' + description: This environment is meant for production use case + - name: 'kind:remote' + description: This server is a remote server. Not exposed by the application + - name: 'visibility:public' + description: This resource is public and available to everyone +channels: + 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/mqtt' + messages: + - $ref: '#/channels/lightingMeasured/messages/lightMeasured' + turnOn: + action: send + channel: + $ref: '#/channels/lightTurnOn' + traits: + - $ref: '#/components/operationTraits/mqtt' + messages: + - $ref: '#/channels/lightTurnOn/messages/turnOn' + turnOff: + action: send + channel: + $ref: '#/channels/lightTurnOff' + traits: + - $ref: '#/components/operationTraits/mqtt' + messages: + - $ref: '#/channels/lightTurnOff/messages/turnOff' + dimLight: + action: send + channel: + $ref: '#/channels/lightsDim' + traits: + - $ref: '#/components/operationTraits/mqtt' + 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: + lightMeasuredPayload: + type: object + properties: + 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: + apiKey: + type: apiKey + in: user + description: Provide your API key as the user and leave the password empty. + supportedOauthFlows: + type: oauth2 + description: Flows to support OAuth 2.0 + flows: + implicit: + authorizationUrl: 'https://authserver.example/auth' + availableScopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + password: + tokenUrl: 'https://authserver.example/token' + availableScopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + clientCredentials: + tokenUrl: 'https://authserver.example/token' + availableScopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + authorizationCode: + authorizationUrl: 'https://authserver.example/auth' + tokenUrl: 'https://authserver.example/token' + refreshUrl: 'https://authserver.example/refresh' + availableScopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + openIdConnectWellKnown: + type: openIdConnect + openIdConnectUrl: 'https://authserver.example/.well-known' + parameters: + streetlightId: + description: The ID of the streetlight. + messageTraits: + commonHeaders: + headers: + type: object + properties: + my-app-header: + type: integer + minimum: 0 + maximum: 100 + operationTraits: + mqtt: + bindings: + mqtt: + qos: 1 \ No newline at end of file diff --git a/examples/output.yaml b/examples/output.yaml new file mode 100644 index 00000000..d57614e7 --- /dev/null +++ b/examples/output.yaml @@ -0,0 +1,231 @@ +asyncapi: 3.0.0 +info: + title: Streetlights MQTT API + version: 1.0.0 + description: > + The Smartylighting Streetlights API allows you to remotely manage the city + lights. + + + ### Check out its awesome features: + + + * Turn a specific streetlight on/off 🌃 + + * Dim a specific streetlight 😎 + + * Receive real-time information about environmental lighting conditions 📈 + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0 +defaultContentType: application/json +servers: + production: + host: test.mosquitto.org:{port} + protocol: mqtt + description: Test broker + variables: + port: + description: Secure connection (TLS) is available through port 8883. + default: '1883' + enum: + - '1883' + - '8883' + security: + - $ref: '#/components/securitySchemes/apiKey' + - type: oauth2 + description: Flows to support OAuth 2.0 + flows: + implicit: + authorizationUrl: https://authserver.example/auth + availableScopes: + streetlights:on: Ability to switch lights on + streetlights:off: Ability to switch lights off + streetlights:dim: Ability to dim the lights + password: + tokenUrl: https://authserver.example/token + availableScopes: + streetlights:on: Ability to switch lights on + streetlights:off: Ability to switch lights off + streetlights:dim: Ability to dim the lights + clientCredentials: + tokenUrl: https://authserver.example/token + availableScopes: + streetlights:on: Ability to switch lights on + streetlights:off: Ability to switch lights off + streetlights:dim: Ability to dim the lights + authorizationCode: + authorizationUrl: https://authserver.example/auth + tokenUrl: https://authserver.example/token + refreshUrl: https://authserver.example/refresh + availableScopes: + streetlights:on: Ability to switch lights on + streetlights:off: Ability to switch lights off + streetlights:dim: Ability to dim the lights + scopes: + - streetlights:on + - streetlights:off + - streetlights:dim + - $ref: '#/components/securitySchemes/openIdConnectWellKnown' + tags: + - name: env:production + description: This environment is meant for production use case + - name: kind:remote + description: This server is a remote server. Not exposed by the application + - name: visibility:public + description: This resource is public and available to everyone +channels: + 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/schemas/schema-1' + lightTurnOn: + address: smartylighting/streetlights/1/0/action/{streetlightId}/turn/on + messages: + turnOn: + $ref: '#/components/messages/turnOnOff' + parameters: + streetlightId: + $ref: '#/components/schemas/schema-1' + lightTurnOff: + address: smartylighting/streetlights/1/0/action/{streetlightId}/turn/off + messages: + turnOff: + $ref: '#/components/messages/turnOnOff' + parameters: + streetlightId: + $ref: '#/components/schemas/schema-2' + lightsDim: + address: smartylighting/streetlights/1/0/action/{streetlightId}/dim + messages: + dimLight: + $ref: '#/components/messages/dimLight' + parameters: + streetlightId: + $ref: '#/components/schemas/schema-2' +operations: + receiveLightMeasurement: + action: receive + channel: + $ref: '#/channels/lightingMeasured' + summary: >- + Inform about environmental lighting conditions of a particular + streetlight. + traits: + - $ref: '#/components/operationTraits/mqtt' + messages: + - $ref: '#/channels/lightingMeasured/messages/lightMeasured' + turnOn: + action: send + channel: + $ref: '#/channels/lightTurnOn' + traits: + - $ref: '#/components/operationTraits/mqtt' + messages: + - $ref: '#/channels/lightTurnOn/messages/turnOn' + turnOff: + action: send + channel: + $ref: '#/channels/lightTurnOff' + traits: + - $ref: '#/components/operationTraits/mqtt' + messages: + - $ref: '#/channels/lightTurnOff/messages/turnOff' + dimLight: + action: send + channel: + $ref: '#/channels/lightsDim' + traits: + - $ref: '#/components/operationTraits/mqtt' + 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: + lightMeasuredPayload: + type: object + properties: + 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. + schema-1: + $ref: '#/components/parameters/streetlightId' + schema-2: + $ref: '#/components/parameters/streetlightId' + parameters: + streetlightId: + description: The ID of the streetlight. + messageTraits: + commonHeaders: + headers: + type: object + properties: + my-app-header: + type: integer + minimum: 0 + maximum: 100 + operationTraits: + mqtt: + bindings: + mqtt: + qos: 1 diff --git a/package-lock.json b/package-lock.json index fbb569a5..8b31dd51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,17 @@ { "name": "@asyncapi/optimizer", - "version": "0.2.7", + "version": "0.2.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@asyncapi/optimizer", - "version": "0.2.7", + "version": "0.2.1", "license": "Apache-2.0", "dependencies": { "@asyncapi/parser": "^3.0.2", + "@types/debug": "^4.1.8", + "debug": "^4.3.4", "js-yaml": "^4.1.0", "jsonpath-plus": "^6.0.1", "lodash": "^4.17.21", @@ -1963,6 +1965,14 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/es-aggregate-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/es-aggregate-error/-/es-aggregate-error-1.0.2.tgz", @@ -2034,6 +2044,11 @@ "integrity": "sha512-t5B5UfacpaP8opUvFGUwT0uQetFrD+qm1/I2ksxokJFLT0Tb4B2NI2G2LYz3ugMDKOE7adkNBZ6coK7RW6MAqA==", "dev": true }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, "node_modules/@types/node": { "version": "15.12.4", "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==" @@ -3031,9 +3046,9 @@ } }, "node_modules/debug": { - "version": "4.3.1", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { "ms": "2.1.2" }, @@ -7041,8 +7056,7 @@ }, "node_modules/ms": { "version": "2.1.2", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/natural-compare": { "version": "1.4.0", @@ -10537,6 +10551,14 @@ "@babel/types": "^7.3.0" } }, + "@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "requires": { + "@types/ms": "*" + } + }, "@types/es-aggregate-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/es-aggregate-error/-/es-aggregate-error-1.0.2.tgz", @@ -10608,6 +10630,11 @@ "integrity": "sha512-t5B5UfacpaP8opUvFGUwT0uQetFrD+qm1/I2ksxokJFLT0Tb4B2NI2G2LYz3ugMDKOE7adkNBZ6coK7RW6MAqA==", "dev": true }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, "@types/node": { "version": "15.12.4", "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==" @@ -11322,9 +11349,9 @@ } }, "debug": { - "version": "4.3.1", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } @@ -14346,8 +14373,7 @@ }, "ms": { "version": "2.1.2", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "natural-compare": { "version": "1.4.0", diff --git a/package.json b/package.json index 453a79e7..590a6608 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "test": "jest --coverage", "test:watch": "jest --watch", "build": "tsc", + "dev": "tsc --watch", "docs": "jsdoc2md lib/Optimizer.js -f lib/**/*.js > API.md", "example": "tsc && node examples/index.js", "lint": "eslint --max-warnings 0 --config .eslintrc .", @@ -56,10 +57,12 @@ }, "dependencies": { "@asyncapi/parser": "^3.0.2", + "@types/debug": "^4.1.8", + "debug": "^4.3.4", "js-yaml": "^4.1.0", "jsonpath-plus": "^6.0.1", "lodash": "^4.17.21", "merge-deep": "^3.0.3", "yaml": "^2.3.1" } -} +} \ No newline at end of file diff --git a/src/ComponentProvider.ts b/src/ComponentProvider.ts index bea1ccaf..7e8e6c78 100644 --- a/src/ComponentProvider.ts +++ b/src/ComponentProvider.ts @@ -1,41 +1,35 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable security/detect-object-injection */ import type { AsyncAPIDocumentInterface } from '@asyncapi/parser' import { OptimizableComponentGroup, OptimizableComponent } from 'index.d' import { JSONPath } from 'jsonpath-plus' import _ from 'lodash' -//the type of object should match exactly with one of the fixed fields in asyncapi components object. -const OPTIMIZABLE_PATHS = [ - { - type: 'messages', - paths: [ - '$.channels.*.*.message[?(@property !== "oneOf")]^', - '$.channels.*.*.message.oneOf.*', - '$.components.messages.*', - ], - }, - { - type: 'schemas', - paths: [ - '$.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)]', - ], - }, - { type: 'parameters', paths: ['$.channels.*.parameters.*', '$.components.parameters.*'] }, -] +export const toLodashPath = (jsonPointer: string): string => { + // Remove leading slash if present + if (jsonPointer.startsWith('/')) { + jsonPointer = jsonPointer.slice(1) + } + + // Split the JSON Pointer into tokens + const tokens = jsonPointer.split('/') + + // Unescape the special characters and transform into Lodash path + return tokens + .map((token) => { + // Replace tilde representations + token = token.replace(/~1/g, '/') + token = token.replace(/~0/g, '~') -export const toLodashPath = (path: string): string => { - return path - .replace(/'\]\['/g, '.') - .slice(3, -2) - .replace(/'\]/g, '') - .replace(/\['/g, '.') + // Check if token can be treated as an array index (non-negative integer) + if (/^\d+$/.test(token)) { + return `[${token}]` + } + // For nested properties, use dot notation + return token + }) + .join('.') } export const parseComponentsFromPath = ( @@ -62,10 +56,43 @@ export const getOptimizableComponents = ( asyncAPIDocument: AsyncAPIDocumentInterface ): OptimizableComponentGroup[] => { const optimizeableComponents: OptimizableComponentGroup[] = [] - for (const componentsPaths of OPTIMIZABLE_PATHS) { + + const getAllComponents = (type: string) => { + // @ts-ignore + if (typeof asyncAPIDocument[type] !== 'function') return [] + // @ts-ignore + return asyncAPIDocument[type]().all().concat(asyncAPIDocument.components()[type]().all()); + }; + const optimizableComponents = { + servers: getAllComponents('servers'), + messages: getAllComponents('messages'), + channels: getAllComponents('channels'), + schemas: getAllComponents('schemas'), + operations: getAllComponents('operations'), + securitySchemes: getAllComponents('securitySchemes'), + serverVariables: getAllComponents('serverVariables'), + parameters: getAllComponents('parameters'), + correlationIds: getAllComponents('correlationIds'), + replies: getAllComponents('replies'), + externalDocs: getAllComponents('externalDocs'), + tags: getAllComponents('tags'), + operationTraits: getAllComponents('operationTraits'), + messageTraits: getAllComponents('messageTraits'), + serverBindings: getAllComponents('serverBindings'), + channelBindings: getAllComponents('channelBindings'), + operationBindings: getAllComponents('operationBindings'), + messageBindings: getAllComponents('messageBindings'), + } + for (const [type, components] of Object.entries(optimizableComponents)) { + if (components.length === 0) continue optimizeableComponents.push({ - type: componentsPaths.type, - components: parseComponentsFromPath(asyncAPIDocument, componentsPaths.paths), + type, + components: components.map((component: any) => ({ + path: toLodashPath(component.jsonPath()), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + component: component.json(), + })), }) } return optimizeableComponents diff --git a/src/Optimizer.ts b/src/Optimizer.ts index e542ad71..c0678aa7 100644 --- a/src/Optimizer.ts +++ b/src/Optimizer.ts @@ -12,7 +12,8 @@ import YAML from 'js-yaml' import merge from 'merge-deep' import * as _ from 'lodash' import { getOptimizableComponents } from './ComponentProvider' -import { hasParent, sortReportElements, toJS } from './Utils' +import { filterReportElements, hasParent, sortReportElements, toJS } from './Utils' +import Debug from 'debug' export enum Action { Move = 'move', @@ -51,15 +52,21 @@ export class Optimizer { async getReport(): Promise { const parser = new Parser() const parsedDocument = await parser.parse(this.YAMLorJSON, { applyTraits: false }) - if (!parsedDocument.document) throw new Error('Parsing failed.') + if (!parsedDocument.document) { + // eslint-disable-next-line no-undef, no-console + console.error(parsedDocument.diagnostics) + throw new Error('Parsing failed.') + } this.components = getOptimizableComponents(parsedDocument.document) const rawReports = this.reporters.map((reporter) => reporter(this.components)) - const filteredReports = rawReports.map((report) => ({ + const reportsWithParents = rawReports.map((report) => ({ type: report.type, elements: report.elements.filter((reportElement) => hasParent(reportElement, this.outputObject) ), })) + + const filteredReports = filterReportElements(reportsWithParents) const sortedReports = filteredReports.map((report) => sortReportElements(report)) this.reports = sortedReports @@ -124,6 +131,7 @@ export class Optimizer { } private applyReport(changes: ReportElement[]): void { + const debug = Debug('optimizer:applyReport') for (const change of changes) { switch (change.action) { case Action.Move: @@ -131,6 +139,7 @@ export class Optimizer { _.set(this.outputObject, change.path, { $ref: `#/${change.target?.replace(/\./g, '/')}`, }) + debug('moved %s to %s', change.path, change.target) break case Action.Reuse: @@ -139,12 +148,14 @@ export class Optimizer { $ref: `#/${change.target?.replace(/\./g, '/')}`, }) } + debug('%s reused %s', change.path, change.target) break case Action.Remove: _.unset(this.outputObject, change.path) //if parent becomes empty after removing, parent should be removed as well. this.removeEmptyParent(change.path) + debug('removed %s', change.path) break } } diff --git a/src/Reporters/MoveToComponents.ts b/src/Reporters/MoveToComponents.ts index c4151c9e..5806fdd9 100644 --- a/src/Reporters/MoveToComponents.ts +++ b/src/Reporters/MoveToComponents.ts @@ -1,7 +1,8 @@ import { Action } from '../Optimizer' import { createReport, isEqual, isInComponents } from '../Utils' import { OptimizableComponent, OptimizableComponentGroup, ReportElement, Reporter } from 'index.d' - +import Debug from 'debug' +const debug = Debug('reporter:moveToComponents') /** * * @param optimizableComponentGroup components that you want to analyze for duplicates. @@ -49,7 +50,11 @@ const findDuplicateComponents = ( } } } - + debug( + 'duplicte %s: %O', + optimizableComponentGroup.type, + resultElements.map((element) => element.path) + ) return resultElements } diff --git a/src/Reporters/RemoveComponents.ts b/src/Reporters/RemoveComponents.ts index 45a2496a..d13643ca 100644 --- a/src/Reporters/RemoveComponents.ts +++ b/src/Reporters/RemoveComponents.ts @@ -1,6 +1,8 @@ import { Action } from '../Optimizer' import { OptimizableComponentGroup, ReportElement, Reporter } from '../index.d' import { createReport, isInComponents } from '../Utils' +import Debug from 'debug' +const debug = Debug('reporter:removeComponents') const findUnusedComponents = (componentsGroup: OptimizableComponentGroup): ReportElement[] => { const allComponents = componentsGroup.components @@ -13,6 +15,11 @@ const findUnusedComponents = (componentsGroup: OptimizableComponentGroup): Repor const unusedComponents = [...insideComponentsSection].filter( (component) => !outsideComponentsSection.has(component.component) ) + debug( + 'unused %s inside components section: %O', + componentsGroup.type, + unusedComponents.map((component) => component.path) + ) return unusedComponents.map((component) => ({ path: component.path, action: Action.Remove })) } diff --git a/src/Reporters/ReuseComponents.ts b/src/Reporters/ReuseComponents.ts index 98f4ae06..320b26e3 100644 --- a/src/Reporters/ReuseComponents.ts +++ b/src/Reporters/ReuseComponents.ts @@ -6,6 +6,8 @@ import { Reporter, } from '../index.d' import { createReport, isEqual, isInChannels, isInComponents } from '../Utils' +import Debug from 'debug' +const debug = Debug('reporter:reuseComponents') const isChannelToComponent = ( component1: OptimizableComponent, @@ -35,6 +37,10 @@ const findDuplicateComponents = ( } } } + for (const element of elements) { + debug('%s can reuse %s', element.path, element.target) + } + return elements } diff --git a/src/Utils/Helpers.ts b/src/Utils/Helpers.ts index 9cf90fa8..2bed0314 100644 --- a/src/Utils/Helpers.ts +++ b/src/Utils/Helpers.ts @@ -29,6 +29,41 @@ export const sortReportElements = (report: NewReport): NewReport => { report.elements.sort((a, b) => a.action.length - b.action.length || b.path.length - a.path.length) return report } + +// Utility function to get all targets of reports with type 'reuseComponents' +const getReuseComponentTargets = ( + reports: { type: string; elements: ReportElement[] }[] +): Set => { + const targets = new Set() + for (const report of reports) { + if (report.type === 'reuseComponents') { + for (const element of report.elements) { + if (element.target) { + targets.add(element.target) + } + } + } + } + return targets +} + +// Main function to filter report elements +export const filterReportElements = ( + reports: { type: string; elements: ReportElement[] }[] +): NewReport[] => { + const reuseTargets = getReuseComponentTargets(reports) + + return reports.map((report) => { + if (report.type === 'removeComponents') { + const filteredElements = report.elements.filter((element) => { + return !reuseTargets.has(element.path) + }) + return { type: report.type, elements: filteredElements } + } + return report + }) +} + const isExtension = (fieldName: string): boolean => { return fieldName.startsWith('x-') } diff --git a/test/Reporters/Reporters.spec.ts b/test/Reporters/Reporters.spec.ts index 119cb556..067a8bba 100644 --- a/test/Reporters/Reporters.spec.ts +++ b/test/Reporters/Reporters.spec.ts @@ -4,47 +4,48 @@ import { Parser } from '@asyncapi/parser' import { getOptimizableComponents } from '../../src/ComponentProvider' import { OptimizableComponentGroup } from '../../src/index.d' -const MoveToComponentsExpectedResult = [ +const MoveToComponentsExpectedResult: any[] = [ { - path: 'channels.smartylighting/event/{streetlightId}/lighting/measured.subscribe.message.traits[0].headers', + path: 'channels.withDuplicatedMessage1.messages.duped1', action: 'move', - target: 'components.schemas.schema-1', + target: 'components.messages.message-1', }, { - path: 'channels.smartylighting/action/{streetlightId}/turn/on.publish.message.traits[0].headers', + path: 'channels.withDuplicatedMessage2.messages.duped2', action: 'reuse', - target: 'components.schemas.schema-1', + target: 'components.messages.message-1', }, { - path: 'channels.smartylighting/event/{streetlightId}/lighting/measured.subscribe.message.traits[0].headers.properties.my-app-header', + path: 'channels.UserSignedUp1', action: 'move', - target: 'components.schemas.schema-2', + target: 'components.channels.channel-1', }, { - path: 'channels.smartylighting/action/{streetlightId}/turn/on.publish.message.traits[0].headers.properties.my-app-header', + path: 'channels.UserSignedUp2', action: 'reuse', - target: 'components.schemas.schema-2', + target: 'components.channels.channel-1', }, { - path: 'channels.smartylighting/event/{streetlightId}/lighting/measured.parameters.streetlightId', + path: 'channels.withDuplicatedMessage1.messages.duped1.payload', action: 'move', - target: 'components.parameters.parameter-1', + target: 'components.schemas.schema-1', }, { - path: 'channels.smartylighting/action/{streetlightId}/turn/on.parameters.streetlightId', + path: 'channels.withDuplicatedMessage2.messages.duped2.payload', action: 'reuse', - target: 'components.parameters.parameter-1', + target: 'components.schemas.schema-1', }, ] const RemoveComponentsExpectedResult = [ - { path: 'components.messages.unusedMessage', action: 'remove' }, - { path: 'components.parameters.unusedParameter', action: 'remove' }, + { path: 'components.messages.unUsedMessage', action: 'remove' }, + { path: 'components.channels.unUsedChannel', action: 'remove' }, + { path: 'components.schemas.canBeReused', action: 'remove' }, ] const ReuseComponentsExpectedResult = [ { - path: 'channels.smartylighting/event/{streetlightId}/lighting/measured.subscribe.message.payload.properties.sentAt', + path: 'channels.withFullFormMessage.messages.canBeReused.payload', action: 'reuse', - target: 'components.schemas.sentAt', + target: 'components.schemas.canBeReused', }, ] diff --git a/test/fixtures.ts b/test/fixtures.ts index a9fb270b..1a112d4c 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -1,21 +1,12 @@ -export const asyncapiYAMLWithoutComponents = `asyncapi: 2.0.0 +export const asyncapiYAMLWithoutComponents = `asyncapi: 3.0.0 info: title: Streetlights API - version: '1.0.0' + version: 1.0.0 channels: - smartylighting/event/{streetlightId}/lighting/measured: - parameters: - #this parameter is duplicated. it can be moved to components and ref-ed from here. - streetlightId: - schema: - type: string - subscribe: - operationId: receiveLightMeasurement - traits: - - bindings: - kafka: - clientId: my-app-id - message: + 'smartylighting/event/{streetlightId}/lighting/measured': + address: 'smartylighting/event/{streetlightId}/lighting/measured' + messages: + receiveLightMeasurement.message: name: lightMeasured title: Light measured contentType: application/json @@ -33,22 +24,15 @@ channels: lumens: type: integer minimum: 0 - #full form is used, we can ref it to: #/components/schemas/sentAt sentAt: type: string format: date-time - smartylighting/action/{streetlightId}/turn/on: parameters: - streetlightId: - schema: - type: string - publish: - operationId: turnOn - traits: - - bindings: - kafka: - clientId: my-app-id - message: + streetlightId: {} + 'smartylighting/action/{streetlightId}/turn/on': + address: 'smartylighting/action/{streetlightId}/turn/on' + messages: + turnOn.message: name: turnOnOff title: Turn on/off headers: @@ -65,159 +49,289 @@ channels: properties: sentAt: type: string - format: date-time`; + format: date-time + parameters: + streetlightId: {} +operations: + receiveLightMeasurement: + action: send + channel: + $ref: '#/channels/smartylighting~1event~1{streetlightId}~1lighting~1measured' + traits: + - bindings: + kafka: + clientId: + type: string + enum: ['myClientId'] -export const inputYAML = `asyncapi: 2.0.0 + messages: + - $ref: >- + #/channels/smartylighting~1event~1{streetlightId}~1lighting~1measured/messages/receiveLightMeasurement.message + turnOn: + action: receive + channel: + $ref: '#/channels/smartylighting~1action~1{streetlightId}~1turn~1on' + traits: + - bindings: + kafka: + clientId: + type: string + enum: ['myClientId'] + messages: + - $ref: >- + #/channels/smartylighting~1action~1{streetlightId}~1turn~1on/messages/turnOn.message +` + +export const inputYAML = `asyncapi: 3.0.0 info: - title: Streetlights API - version: '1.0.0' + title: Untidy AsyncAPI file + version: 1.0.0 + description: >- + This file contains duplicate and unused messages across the file and is used to test the optimizer. channels: - smartylighting/event/{streetlightId}/lighting/measured: - parameters: - #this parameter is duplicated. it can be moved to components and ref-ed from here. - streetlightId: - schema: - type: string - subscribe: - operationId: receiveLightMeasurement - traits: - - bindings: - kafka: - clientId: my-app-id - message: - name: lightMeasured - title: Light measured - contentType: application/json - traits: - - headers: - type: object - properties: - my-app-header: - type: integer - minimum: 0 - maximum: 100 + withDuplicatedMessage1: + address: user/signedup + messages: + duped1: payload: type: object - properties: - lumens: - type: integer - minimum: 0 - #full form is used, we can ref it to: #/components/schemas/sentAt - sentAt: - type: string - format: date-time - smartylighting/action/{streetlightId}/turn/on: - parameters: - streetlightId: - schema: - type: string - publish: - operationId: turnOn - traits: - - bindings: - kafka: - clientId: my-app-id - message: - name: turnOnOff - title: Turn on/off - traits: - - headers: - type: object - properties: - my-app-header: - type: integer - minimum: 0 - maximum: 100 + description: I am duplicated + withDuplicatedMessage2: + address: user/signedup + messages: + duped2: payload: type: object - properties: - sentAt: - $ref: "#/components/schemas/sentAt" + description: I am duplicated + withFullFormMessage: + address: user/signedup + messages: + canBeReused: + payload: + type: object + description: I can be reused. + UserSignedUp1: + address: user/signedup + messages: + myMessage: + $ref: '#/components/messages/UserSignedUp' + UserSignedUp2: + address: user/signedup + messages: + myMessage: + $ref: '#/components/messages/UserSignedUp' + deleteAccount: + address: user/deleteAccount + messages: + deleteUser: + $ref: '#/components/messages/DeleteUser' +operations: + user/deleteAccount.subscribe: + action: send + channel: + $ref: '#/channels/deleteAccount' + messages: + - $ref: '#/channels/deleteAccount/messages/deleteUser' components: - messages: - #libarary should be able to find and delete this message, because it is not used anywhere. - unusedMessage: - name: unusedMessage - title: This message is not used in any channel. - + channels: + unUsedChannel: + address: user/unused + messages: + myMessage: + $ref: '#/components/messages/UserSignedUp' schemas: - #this schema is ref-ed in one channel and used full form in another. library should be able to identify and ref the second channel as well. - sentAt: - type: string - format: date-time - parameters: - unusedParameter: - schema: - type: number`; -export const outputYAML = `asyncapi: 2.0.0 + canBeReused: + type: object + description: I can be reused. + messages: + unUsedMessage: + payload: + type: boolean + DeleteUser: + payload: + type: string + description: userId of the user that is going to be deleted + UserSignedUp: + payload: + type: object + properties: + displayName: + type: string + description: Name of the user + email: + type: string + format: email + description: Email of the user +` +export const outputYAML = `asyncapi: 3.0.0 info: - title: Streetlights API + title: Untidy AsyncAPI file version: 1.0.0 + description: >- + This file contains duplicate and unused messages across the file and is used + to test the optimizer. channels: - smartylighting/event/{streetlightId}/lighting/measured: - parameters: - streetlightId: - $ref: '#/components/parameters/parameter-1' - subscribe: - operationId: receiveLightMeasurement - traits: - - bindings: - kafka: - clientId: my-app-id - message: - name: lightMeasured - title: Light measured - contentType: application/json - traits: - - headers: - $ref: '#/components/schemas/schema-1' - payload: - type: object - properties: - lumens: - type: integer - minimum: 0 - sentAt: - $ref: '#/components/schemas/sentAt' - smartylighting/action/{streetlightId}/turn/on: - parameters: - streetlightId: - $ref: '#/components/parameters/parameter-1' - publish: - operationId: turnOn - traits: - - bindings: - kafka: - clientId: my-app-id - message: - name: turnOnOff - title: Turn on/off - traits: - - headers: - $ref: '#/components/schemas/schema-1' + withDuplicatedMessage1: + address: user/signedup + messages: + duped1: + $ref: '#/components/messages/message-1' + withDuplicatedMessage2: + address: user/signedup + messages: + duped2: + $ref: '#/components/messages/message-1' + withFullFormMessage: + address: user/signedup + messages: + canBeReused: payload: - type: object - properties: - sentAt: - $ref: '#/components/schemas/sentAt' + $ref: '#/components/schemas/canBeReused' + UserSignedUp1: + $ref: '#/components/channels/channel-1' + UserSignedUp2: + $ref: '#/components/channels/channel-1' + deleteAccount: + address: user/deleteAccount + messages: + deleteUser: + $ref: '#/components/messages/DeleteUser' +operations: + user/deleteAccount.subscribe: + action: send + channel: + $ref: '#/channels/deleteAccount' + messages: + - $ref: '#/channels/deleteAccount/messages/deleteUser' components: schemas: - sentAt: - type: string - format: date-time - schema-2: - type: integer - minimum: 0 - maximum: 100 + canBeReused: + type: object + description: I can be reused. schema-1: type: object - properties: - my-app-header: - $ref: '#/components/schemas/schema-2' - parameters: - parameter-1: - schema: - type: string`; - -export const inputJSON = '{"asyncapi":"2.0.0","info":{"title":"Streetlights API","version":"1.0.0"},"channels":{"smartylighting/event/{streetlightId}/lighting/measured":{"parameters":{"streetlightId":{"schema":{"type":"string"}}},"subscribe":{"operationId":"receiveLightMeasurement","traits":[{"bindings":{"kafka":{"clientId":"my-app-id"}}}],"message":{"name":"lightMeasured","title":"Light measured","contentType":"application/json","traits":[{"headers":{"type":"object","properties":{"my-app-header":{"type":"integer","minimum":0,"maximum":100}}}}],"payload":{"type":"object","properties":{"lumens":{"type":"integer","minimum":0},"sentAt":{"type":"string","format":"date-time"}}}}}},"smartylighting/action/{streetlightId}/turn/on":{"parameters":{"streetlightId":{"schema":{"type":"string"}}},"publish":{"operationId":"turnOn","traits":[{"bindings":{"kafka":{"clientId":"my-app-id"}}}],"message":{"name":"turnOnOff","title":"Turn on/off","traits":[{"headers":{"type":"object","properties":{"my-app-header":{"type":"integer","minimum":0,"maximum":100}}}}],"payload":{"type":"object","properties":{"sentAt":{"$ref":"#/components/schemas/sentAt"}}}}}}},"components":{"messages":{"unusedMessage":{"name":"unusedMessage","title":"This message is not used in any channel."}},"schemas":{"sentAt":{"type":"string","format":"date-time"}},"parameters":{"unusedParameter":{"schema":{"type":"number"}}}}}'; -export const outputJSON = '{"asyncapi":"2.0.0","info":{"title":"Streetlights API","version":"1.0.0"},"channels":{"smartylighting/event/{streetlightId}/lighting/measured":{"parameters":{"streetlightId":{"$ref":"#/components/parameters/parameter-1"}},"subscribe":{"operationId":"receiveLightMeasurement","traits":[{"bindings":{"kafka":{"clientId":"my-app-id"}}}],"message":{"name":"lightMeasured","title":"Light measured","contentType":"application/json","traits":[{"headers":{"$ref":"#/components/schemas/schema-1"}}],"payload":{"type":"object","properties":{"lumens":{"type":"integer","minimum":0},"sentAt":{"$ref":"#/components/schemas/sentAt"}}}}}},"smartylighting/action/{streetlightId}/turn/on":{"parameters":{"streetlightId":{"$ref":"#/components/parameters/parameter-1"}},"publish":{"operationId":"turnOn","traits":[{"bindings":{"kafka":{"clientId":"my-app-id"}}}],"message":{"name":"turnOnOff","title":"Turn on/off","traits":[{"headers":{"$ref":"#/components/schemas/schema-1"}}],"payload":{"type":"object","properties":{"sentAt":{"$ref":"#/components/schemas/sentAt"}}}}}}},"components":{"schemas":{"sentAt":{"type":"string","format":"date-time"},"schema-2":{"type":"integer","minimum":0,"maximum":100},"schema-1":{"type":"object","properties":{"my-app-header":{"$ref":"#/components/schemas/schema-2"}}}},"parameters":{"parameter-1":{"schema":{"type":"string"}}}}}'; + description: I am duplicated + messages: + DeleteUser: + payload: + type: string + description: userId of the user that is going to be deleted + UserSignedUp: + payload: + type: object + properties: + displayName: + type: string + description: Name of the user + email: + type: string + format: email + description: Email of the user + message-1: + payload: + $ref: '#/components/schemas/schema-1' + channels: + channel-1: + address: user/signedup + messages: + myMessage: + $ref: '#/components/messages/UserSignedUp' +` + +export const inputJSON = `{ + 'asyncapi': '3.0.0', + 'info': + { + 'title': 'Untidy AsyncAPI file', + 'version': '1.0.0', + 'description': 'This file contains duplicate and unused messages across the file and is used to test the optimizer.', + }, + 'channels': + { + 'withDuplicatedMessage1': + { + 'address': 'user/signedup', + 'messages': + { 'duped1': { 'payload': { 'type': 'object', 'description': 'I am duplicated' } } }, + }, + 'withDuplicatedMessage2': + { + 'address': 'user/signedup', + 'messages': + { 'duped2': { 'payload': { 'type': 'object', 'description': 'I am duplicated' } } }, + }, + 'withFullFormMessage': + { + 'address': 'user/signedup', + 'messages': + { + 'canBeReused': { 'payload': { 'type': 'object', 'description': 'I can be reused.' } }, + }, + }, + 'UserSignedUp1': + { + 'address': 'user/signedup', + 'messages': { 'myMessage': { '$ref': '#/components/messages/UserSignedUp' } }, + }, + 'UserSignedUp2': + { + 'address': 'user/signedup', + 'messages': { 'myMessage': { '$ref': '#/components/messages/UserSignedUp' } }, + }, + 'deleteAccount': + { + 'address': 'user/deleteAccount', + 'messages': { 'deleteUser': { '$ref': '#/components/messages/DeleteUser' } }, + }, + }, + 'operations': + { + 'user/deleteAccount.subscribe': + { + 'action': 'send', + 'channel': { '$ref': '#/channels/deleteAccount' }, + 'messages': [{ '$ref': '#/channels/deleteAccount/messages/deleteUser' }], + }, + }, + 'components': + { + 'channels': + { + 'unUsedChannel': + { + 'address': 'user/unused', + 'messages': { 'myMessage': { '$ref': '#/components/messages/UserSignedUp' } }, + }, + }, + 'schemas': { 'canBeReused': { 'type': 'object', 'description': 'I can be reused.' } }, + 'messages': + { + 'unUsedMessage': { 'payload': { 'type': 'boolean' } }, + 'DeleteUser': + { + 'payload': + { + 'type': 'string', + 'description': 'userId of the user that is going to be deleted', + }, + }, + 'UserSignedUp': + { + 'payload': + { + 'type': 'object', + 'properties': + { + 'displayName': { 'type': 'string', 'description': 'Name of the user' }, + 'email': + { 'type': 'string', 'format': 'email', 'description': 'Email of the user' }, + }, + }, + }, + }, + }, +} +` + +// eslint-disable-next-line quotes +export const outputJSON = `{"asyncapi":"3.0.0","info":{"title":"Untidy AsyncAPI file","version":"1.0.0","description":"This file contains duplicate and unused messages across the file and is used to test the optimizer."},"channels":{"withDuplicatedMessage1":{"address":"user/signedup","messages":{"duped1":{"$ref":"#/components/messages/message-1"}}},"withDuplicatedMessage2":{"address":"user/signedup","messages":{"duped2":{"$ref":"#/components/messages/message-1"}}},"withFullFormMessage":{"address":"user/signedup","messages":{"canBeReused":{"payload":{"$ref":"#/components/schemas/canBeReused"}}}},"UserSignedUp1":{"$ref":"#/components/channels/channel-1"},"UserSignedUp2":{"$ref":"#/components/channels/channel-1"},"deleteAccount":{"address":"user/deleteAccount","messages":{"deleteUser":{"$ref":"#/components/messages/DeleteUser"}}}},"operations":{"user/deleteAccount.subscribe":{"action":"send","channel":{"$ref":"#/channels/deleteAccount"},"messages":[{"$ref":"#/channels/deleteAccount/messages/deleteUser"}]}},"components":{"schemas":{"canBeReused":{"type":"object","description":"I can be reused."},"schema-1":{"type":"object","description":"I am duplicated"}},"messages":{"DeleteUser":{"payload":{"type":"string","description":"userId of the user that is going to be deleted"}},"UserSignedUp":{"payload":{"type":"object","properties":{"displayName":{"type":"string","description":"Name of the user"},"email":{"type":"string","format":"email","description":"Email of the user"}}}},"message-1":{"payload":{"$ref":"#/components/schemas/schema-1"}}},"channels":{"channel-1":{"address":"user/signedup","messages":{"myMessage":{"$ref":"#/components/messages/UserSignedUp"}}}}}}`