diff --git a/examples/valid/asyncapi.v2.overlayed.yml b/examples/valid/asyncapi.v2.overlayed.yml new file mode 100644 index 00000000..4d61b1ee --- /dev/null +++ b/examples/valid/asyncapi.v2.overlayed.yml @@ -0,0 +1,177 @@ +# Some comments in the API definition +asyncapi: 2.2.0 +info: + title: Streetlights API + version: 1.0.0 + description: | + Turn lights on or off. And get notified when lights are dimmed or switched. + license: + name: Apache 2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0' +servers: + # This is a Production MQTT server + production: + url: '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: + - apiKey: [] + - supportedOauthFlows: + - 'streetlights:on' + - 'streetlights:off' + - 'streetlights:dim' + - openIdConnectWellKnown: [] +defaultContentType: application/json +channels: + 'smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured': + description: The topic on which measured values may be produced and consumed. + parameters: + streetlightId: + $ref: 'http://example.org/param-lights.json' + publish: + summary: Inform about environmental lighting conditions of a particular streetlight. + operationId: receiveLightMeasurement + traits: + - $ref: ./traits/kafka.yml + message: + $ref: '#/components/messages/lightMeasured' + 'smartylighting/streetlights/1/0/action/{streetlightId}/turn/on': + parameters: + streetlightId: + $ref: ./params/streetlightId.json + subscribe: + operationId: turnOn + traits: + - $ref: ./traits/kafka.yml + message: + $ref: '#/components/messages/turnOnOff' + 'smartylighting/streetlights/1/0/action/{streetlightId}/turn/off': + parameters: + streetlightId: + $ref: ./params/streetlightId.json + 'smartylighting/streetlights/1/0/action/{streetlightId}/dim': + parameters: + streetlightId: + $ref: params/streetlightId.json + subscribe: + operationId: dimLight + traits: + - $ref: ./traits/kafka.yml + message: + $ref: '#/components/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' + scopes: + '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' + scopes: + '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' + scopes: + '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' + scopes: + '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' + messageTraits: + commonHeaders: + headers: + type: object + properties: + my-app-header: + type: integer + minimum: 0 + maximum: 100 diff --git a/examples/valid/asyncapi.v2.yml b/examples/valid/asyncapi.v2.yml index 9cb97413..30d6b80d 100644 --- a/examples/valid/asyncapi.v2.yml +++ b/examples/valid/asyncapi.v2.yml @@ -1,3 +1,4 @@ +# Some comments in the API definition asyncapi: '2.2.0' info: title: Streetlights API @@ -9,6 +10,7 @@ info: url: https://www.apache.org/licenses/LICENSE-2.0 servers: + # This is a Production MQTT server production: url: test.mosquitto.org:{port} protocol: mqtt @@ -60,6 +62,7 @@ channels: streetlightId: $ref: './params/streetlightId.json' subscribe: + x-beta: true operationId: turnOff traits: - $ref: './traits/kafka.yml' diff --git a/examples/valid/overlay-async.yaml b/examples/valid/overlay-async.yaml new file mode 100644 index 00000000..74f35e61 --- /dev/null +++ b/examples/valid/overlay-async.yaml @@ -0,0 +1,13 @@ +overlay: 1.0.0 +info: + title: Overlay to customise API for Streetlights + version: 0.0.1 +actions: + - target: '$.info.description' + description: Provide a better introduction for our end users than this techno babble. + update: | + Turn lights on or off. And get notified when lights are dimmed or switched. + + - target: '$..[?(@["x-beta"]==true)]' + description: Remove all beta operations + remove: true diff --git a/package-lock.json b/package-lock.json index 059fccbc..168de8d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3586,6 +3586,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.5.tgz", "integrity": "sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg==", + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -3594,6 +3595,7 @@ "version": "14.1.1", "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-14.1.1.tgz", "integrity": "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g==", + "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" @@ -3606,6 +3608,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/@stoplight/yaml/-/yaml-4.3.0.tgz", "integrity": "sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w==", + "license": "Apache-2.0", "dependencies": { "@stoplight/ordered-object-literal": "^1.0.5", "@stoplight/types": "^14.1.1", @@ -3619,7 +3622,8 @@ "node_modules/@stoplight/yaml-ast-parser": { "version": "0.0.50", "resolved": "https://registry.npmjs.org/@stoplight/yaml-ast-parser/-/yaml-ast-parser-0.0.50.tgz", - "integrity": "sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ==" + "integrity": "sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ==", + "license": "Apache-2.0" }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", @@ -15141,6 +15145,7 @@ "version": "3.11.0", "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "license": "MIT", "engines": { "node": ">= 4" } diff --git a/src/definition.ts b/src/definition.ts index 56b0f8d2..993e4393 100644 --- a/src/definition.ts +++ b/src/definition.ts @@ -1,7 +1,7 @@ import {default as $RefParser, getJsonSchemaRefParserDefaultOptions} from '@apidevtools/json-schema-ref-parser' import asyncapi from '@asyncapi/specs' import {CLIError} from '@oclif/core/errors' -import {safeStringify} from '@stoplight/yaml' +import {parseWithPointers, safeStringify} from '@stoplight/yaml' import debug from 'debug' import { JSONSchema4, @@ -277,9 +277,11 @@ class API { serializeDefinition(outputPath?: string): string { if (this.overlayedDefinition) { + const {comments} = parseWithPointers(this.rawDefinition, {attachComments: true}) + const dumpOptions = {comments, lineWidth: Number.POSITIVE_INFINITY} return this.guessFormat(outputPath) === 'json' ? JSON.stringify(this.overlayedDefinition) - : safeStringify(this.overlayedDefinition) + : safeStringify(this.overlayedDefinition, dumpOptions) } return this.rawDefinition diff --git a/test/unit/definition.test.ts b/test/unit/definition.test.ts index 996c1071..435c37e3 100644 --- a/test/unit/definition.test.ts +++ b/test/unit/definition.test.ts @@ -1,6 +1,7 @@ import * as YAML from '@stoplight/yaml' import {expect} from 'chai' import nock from 'nock' +import * as fs from 'node:fs' import path from 'node:path' import {API, APIDefinition} from '../../src/definition' @@ -136,7 +137,20 @@ describe('API class', () => { expect(api.serializeDefinition()).to.equal(JSON.stringify(api.overlayedDefinition)) - expect(api.serializeDefinition('destination/file.yaml')).to.equal(YAML.safeStringify(api.overlayedDefinition)) + expect(api.serializeDefinition('destination/file.yaml')).to.equal( + YAML.safeStringify(api.overlayedDefinition, {lineWidth: Number.POSITIVE_INFINITY}), + ) + }) + + it('preserves line width and YAML comments', async () => { + nock('http://example.org').get('/param-lights.json').reply(200, {}) + + const api = await API.load('examples/valid/asyncapi.v2.yml') + await api.applyOverlay('examples/valid/overlay-async.yaml') + + expect(api.serializeDefinition()).to.equal( + fs.readFileSync('examples/valid/asyncapi.v2.overlayed.yml', 'utf8').replaceAll('\r', ''), + ) }) }) })