@asyncapi/converter 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,6 +15,7 @@ Convert [AsyncAPI](https://asyncapi.com) documents older to newer versions.
15
15
  * [From CLI](#from-cli)
16
16
  * [In JS](#in-js)
17
17
  * [In TS](#in-ts)
18
+ - [Conversion 2.x.x to 3.x.x](#conversion-2xx-to-3xx)
18
19
  - [Known missing features](#known-missing-features)
19
20
  - [Development](#development)
20
21
  - [Contribution](#contribution)
@@ -91,6 +92,75 @@ try {
91
92
  }
92
93
  ```
93
94
 
95
+ ## Conversion 2.x.x to 3.x.x
96
+
97
+ > **NOTE**: This feature is still WIP, and is until the final release of `3.0.0`.
98
+
99
+ Conversion to version `3.x.x` from `2.x.x` has several assumptions that should be know before converting:
100
+
101
+ - The input must be valid AsyncAPI document.
102
+ - External references are not resolved and converted, they remain untouched, even if they are incorrect.
103
+ - In version `3.0.0`, the channel identifier is no longer its address, but due to the difficulty of defining a unique identifier, we still treat the address as an identifier. If there is a need to assign an identifier other than an address, an `x-channelId` extension should be defined at the level of the given channel.
104
+
105
+ ```yaml
106
+ # 2.x.x
107
+ channels:
108
+ users/signup:
109
+ x-channelId: 'userSignUp'
110
+ ...
111
+ users/logout:
112
+ ...
113
+
114
+ # 3.0.0
115
+ channels:
116
+ userSignUp:
117
+ ...
118
+ users/logout:
119
+ ...
120
+ ```
121
+
122
+ - The `publish` operation is treated as a `receive` action, and `subscribe` is treated as a `send` action. Conversion by default is embraced from the application perspective. If you want to change this logic, you need to specify `v2tov3.pointOfView` configuration as `client`.
123
+ - If the operation does not have an `operationId` field defined, the unique identifier of the operation will be defined as a combination of the identifier of the channel on which the operation was defined + the type of operation, `publish` or `subscribe`. Identical situation is with messages. However, here the priority is the `messageId` field and then the concatenation `{publish|subscribe}.messages.{optional index of oneOf messages}`.
124
+
125
+ ```yaml
126
+ # 2.x.x
127
+ channels:
128
+ users/signup:
129
+ publish:
130
+ message:
131
+ ...
132
+ subscribe:
133
+ operationId: 'userSignUpEvent'
134
+ message:
135
+ oneOf:
136
+ - messageId: 'userSignUpEventMessage'
137
+ ...
138
+ - ...
139
+
140
+
141
+ # 3.0.0
142
+ channels:
143
+ users/signup:
144
+ messages:
145
+ publish.message:
146
+ ...
147
+ userSignUpEventMessage:
148
+ ...
149
+ userSignUpEvent.message.1:
150
+ ...
151
+ operations:
152
+ users/signup.publish:
153
+ action: receive
154
+ ...
155
+ userSignUpEvent:
156
+ action: send
157
+ ...
158
+ ```
159
+
160
+ - Security requirements that use scopes are defined in the appropriate places inline, the rest as a reference to the `components.securitySchemes` objects.
161
+ - If servers are defined at the channel level, they are converted as references to the corresponding objects defined in the `servers` field.
162
+ - Channels and servers defined in components are also converted (unless configured otherwise).
163
+
94
164
  ## Known missing features
95
165
 
96
166
  * When converting from 1.x to 2.x, Streaming APIs (those using `stream` instead of `topics` or `events`) are converted correctly but information about framing type and delimiter is missing until a [protocolInfo](https://github.com/asyncapi/extensions-catalog/issues/1) for that purpose is created.
package/lib/convert.js CHANGED
@@ -4,11 +4,12 @@ exports.convert = void 0;
4
4
  const js_yaml_1 = require("js-yaml");
5
5
  const first_version_1 = require("./first-version");
6
6
  const second_version_1 = require("./second-version");
7
+ const third_version_1 = require("./third-version");
7
8
  const utils_1 = require("./utils");
8
9
  /**
9
10
  * Value for key (version) represents the function which converts specification from previous version to the given as key.
10
11
  */
11
- const converters = Object.assign(Object.assign({}, first_version_1.converters), second_version_1.converters);
12
+ const converters = Object.assign(Object.assign(Object.assign({}, first_version_1.converters), second_version_1.converters), third_version_1.converters);
12
13
  const conversionVersions = Object.keys(converters);
13
14
  function convert(asyncapi, version = '2.6.0', options = {}) {
14
15
  const { format, document } = (0, utils_1.serializeInput)(asyncapi);
@@ -4,8 +4,24 @@
4
4
  export declare type AsyncAPIDocument = {
5
5
  asyncapi: string;
6
6
  } & Record<string, any>;
7
- export declare type ConvertVersion = '1.1.0' | '1.2.0' | '2.0.0-rc1' | '2.0.0-rc2' | '2.0.0' | '2.1.0' | '2.2.0' | '2.3.0' | '2.4.0' | '2.5.0' | '2.6.0';
8
- export declare type ConvertOptions = {};
7
+ export declare type ConvertVersion = '1.1.0' | '1.2.0' | '2.0.0-rc1' | '2.0.0-rc2' | '2.0.0' | '2.1.0' | '2.2.0' | '2.3.0' | '2.4.0' | '2.5.0' | '2.6.0' | '3.0.0';
8
+ export declare type ConvertV2ToV3Options = {
9
+ idGenerator?: (data: {
10
+ asyncapi: AsyncAPIDocument;
11
+ kind: 'channel' | 'operation' | 'message';
12
+ key: string | number | undefined;
13
+ path: Array<string | number>;
14
+ object: any;
15
+ parentId?: string;
16
+ }) => string;
17
+ pointOfView?: 'application' | 'client';
18
+ useChannelIdExtension?: boolean;
19
+ convertServerComponents?: boolean;
20
+ convertChannelComponents?: boolean;
21
+ };
22
+ export declare type ConvertOptions = {
23
+ v2tov3?: ConvertV2ToV3Options;
24
+ };
9
25
  /**
10
26
  * PRIVATE TYPES
11
27
  */
@@ -0,0 +1,2 @@
1
+ import type { ConvertFunction } from './interfaces';
2
+ export declare const converters: Record<string, ConvertFunction>;
@@ -0,0 +1,452 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.converters = void 0;
4
+ const utils_1 = require("./utils");
5
+ exports.converters = {
6
+ '3.0.0': from__2_6_0__to__3_0_0,
7
+ };
8
+ function from__2_6_0__to__3_0_0(asyncapi, options) {
9
+ var _a;
10
+ asyncapi.asyncapi = '3.0.0';
11
+ const v2tov3Options = Object.assign({ pointOfView: 'application', useChannelIdExtension: true, convertServerComponents: true, convertChannelComponents: true }, ((_a = options.v2tov3) !== null && _a !== void 0 ? _a : {}));
12
+ v2tov3Options.idGenerator = v2tov3Options.idGenerator || idGeneratorFactory(v2tov3Options);
13
+ const context = {
14
+ refs: new Map(),
15
+ };
16
+ convertInfoObject(asyncapi, context);
17
+ if ((0, utils_1.isPlainObject)(asyncapi.servers)) {
18
+ asyncapi.servers = convertServerObjects(asyncapi.servers, asyncapi);
19
+ }
20
+ if ((0, utils_1.isPlainObject)(asyncapi.channels)) {
21
+ asyncapi.channels = convertChannelObjects(asyncapi.channels, asyncapi, v2tov3Options, context);
22
+ }
23
+ convertComponents(asyncapi, v2tov3Options, context);
24
+ replaceDeepRefs(asyncapi, context.refs, '', asyncapi);
25
+ return (0, utils_1.sortObjectKeys)(asyncapi, ['asyncapi', 'id', 'info', 'defaultContentType', 'servers', 'channels', 'operations', 'components']);
26
+ }
27
+ /**
28
+ * Moving Tags and ExternalDocs to the Info Object.
29
+ */
30
+ function convertInfoObject(asyncapi, context) {
31
+ if (asyncapi.tags) {
32
+ asyncapi.info.tags = asyncapi.tags;
33
+ context.refs.set((0, utils_1.createRefPath)('tags'), (0, utils_1.createRefPath)('info', 'tags'));
34
+ delete asyncapi.tags;
35
+ }
36
+ if (asyncapi.externalDocs) {
37
+ asyncapi.info.externalDocs = asyncapi.externalDocs;
38
+ context.refs.set((0, utils_1.createRefPath)('externalDocs'), (0, utils_1.createRefPath)('info', 'externalDocs'));
39
+ delete asyncapi.externalDocs;
40
+ }
41
+ asyncapi.info = (0, utils_1.sortObjectKeys)(asyncapi.info, ['title', 'version', 'description', 'termsOfService', 'contact', 'license', 'tags', 'externalDocs']);
42
+ }
43
+ /**
44
+ * Split `url` field to the `host` and `pathname` (optional) fields.
45
+ * Unify referencing mechanism in security field.
46
+ */
47
+ function convertServerObjects(servers, asyncapi) {
48
+ const newServers = {};
49
+ Object.entries(servers).forEach(([serverName, server]) => {
50
+ if ((0, utils_1.isRefObject)(server)) {
51
+ newServers[serverName] = server;
52
+ return;
53
+ }
54
+ const { host, pathname, protocol } = resolveServerUrl(server.url);
55
+ server.host = host;
56
+ if (pathname !== undefined) {
57
+ server.pathname = pathname;
58
+ }
59
+ // Dont overwrite anything
60
+ if (protocol !== undefined && server.protocol === undefined) {
61
+ server.protocol = protocol;
62
+ }
63
+ delete server.url;
64
+ if (server.security) {
65
+ server.security = convertSecurityObject(server.security, asyncapi);
66
+ }
67
+ newServers[serverName] = (0, utils_1.sortObjectKeys)(server, ['host', 'pathname', 'protocol', 'protocolVersion', 'title', 'summary', 'description', 'variables', 'security', 'tags', 'externalDocs', 'bindings']);
68
+ });
69
+ return newServers;
70
+ }
71
+ /**
72
+ * Convert operation part of channels into standalone operations
73
+ */
74
+ function toStandaloneOperation(data) {
75
+ const { kind, channel, asyncapi, operations, context, inComponents, channelId, channelAddress, options, oldPath } = data;
76
+ let operation = channel[kind];
77
+ const operationPath = inComponents ? ['components', 'operations'] : ['operations'];
78
+ if ((0, utils_1.isPlainObject)(operation)) {
79
+ const { operationId, operation: newOperation, messages } = convertOperationObject({ asyncapi, kind, channel, channelId, oldChannelId: channelAddress, operation, inComponents }, options, context);
80
+ if (operation.security) {
81
+ newOperation.security = convertSecurityObject(operation.security, asyncapi);
82
+ }
83
+ operationPath.push(operationId);
84
+ context.refs.set((0, utils_1.createRefPath)(...oldPath, kind), (0, utils_1.createRefPath)(...operationPath));
85
+ operations[operationId] = newOperation;
86
+ delete channel[kind];
87
+ return messages !== null && messages !== void 0 ? messages : {};
88
+ }
89
+ return {};
90
+ }
91
+ /**
92
+ * Split Channel Objects to the Channel Objects and Operation Objects.
93
+ */
94
+ function convertChannelObjects(channels, asyncapi, options, context, inComponents = false) {
95
+ const newChannels = {};
96
+ Object.entries(channels).forEach(([channelAddress, channel]) => {
97
+ const oldPath = inComponents ? ['components', 'channels', channelAddress] : ['channels', channelAddress];
98
+ const channelId = options.idGenerator({ asyncapi, kind: 'channel', key: channelAddress, path: oldPath, object: channel });
99
+ const newPath = inComponents ? ['components', 'channels', channelId] : ['channels', channelId];
100
+ context.refs.set((0, utils_1.createRefPath)(...oldPath), (0, utils_1.createRefPath)(...newPath));
101
+ if ((0, utils_1.isRefObject)(channel)) {
102
+ newChannels[channelId] = channel;
103
+ return;
104
+ }
105
+ // assign address
106
+ channel.address = channelAddress;
107
+ // change the Server names to the Refs
108
+ const servers = channel.servers;
109
+ if (Array.isArray(servers)) {
110
+ channel.servers = servers.map((serverName) => (0, utils_1.createRefObject)('servers', serverName));
111
+ }
112
+ const operations = {};
113
+ // serialize publish and subscribe Operation Objects to standalone object
114
+ const publishMessages = toStandaloneOperation({ kind: 'publish', channel, asyncapi, operations, context, inComponents, channelId, channelAddress, options, oldPath });
115
+ const subscribeMessages = toStandaloneOperation({ kind: 'subscribe', channel, asyncapi, operations, context, inComponents, channelId, channelAddress, options, oldPath });
116
+ if (publishMessages || subscribeMessages) {
117
+ const allOperationMessages = Object.assign(Object.assign({}, publishMessages), subscribeMessages);
118
+ channel.messages = convertMessages({
119
+ messages: allOperationMessages
120
+ });
121
+ }
122
+ setOperationsOnRoot({ operations, inComponents, asyncapi, oldPath });
123
+ newChannels[channelId] = (0, utils_1.sortObjectKeys)(channel, ['address', 'messages', 'title', 'summary', 'description', 'servers', 'parameters', 'tags', 'externalDocs', 'bindings']);
124
+ });
125
+ return newChannels;
126
+ }
127
+ /**
128
+ * Assign the operations to the root AsyncAPI object.
129
+ */
130
+ function setOperationsOnRoot(data) {
131
+ var _a, _b, _c, _d, _e;
132
+ const { operations, inComponents, asyncapi, oldPath } = data;
133
+ if (Object.keys(operations)) {
134
+ if (inComponents) {
135
+ const components = asyncapi.components = (_a = asyncapi.components) !== null && _a !== void 0 ? _a : {};
136
+ components.operations = Object.assign(Object.assign({}, (_b = components.operations) !== null && _b !== void 0 ? _b : {}), operations);
137
+ // if given component is used in the `channels` object then create references for operations in the `operations` object
138
+ if (channelIsUsed((_c = asyncapi.channels) !== null && _c !== void 0 ? _c : {}, oldPath)) {
139
+ const referencedOperations = Object.keys(operations).reduce((acc, current) => {
140
+ acc[current] = (0, utils_1.createRefObject)('components', 'operations', current);
141
+ return acc;
142
+ }, {});
143
+ asyncapi.operations = Object.assign(Object.assign({}, (_d = asyncapi.operations) !== null && _d !== void 0 ? _d : {}), referencedOperations);
144
+ }
145
+ }
146
+ else {
147
+ asyncapi.operations = Object.assign(Object.assign({}, (_e = asyncapi.operations) !== null && _e !== void 0 ? _e : {}), operations);
148
+ }
149
+ }
150
+ }
151
+ /**
152
+ * Points to the connected channel and split messages for channel
153
+ */
154
+ function convertOperationObject(data, options, context) {
155
+ const { asyncapi, channelId, oldChannelId, kind, inComponents } = data;
156
+ const operation = Object.assign({}, data.operation);
157
+ const oldChannelPath = ['channels', oldChannelId];
158
+ if (inComponents) {
159
+ oldChannelPath.unshift('components');
160
+ }
161
+ const newChannelPath = ['channels', channelId];
162
+ if (inComponents) {
163
+ newChannelPath.unshift('components');
164
+ }
165
+ const operationId = options.idGenerator({ asyncapi, kind: 'operation', key: kind, path: oldChannelPath, object: data.operation, parentId: channelId });
166
+ operation.channel = (0, utils_1.createRefObject)(...newChannelPath);
167
+ try {
168
+ delete operation.operationId;
169
+ }
170
+ catch (err) { }
171
+ const isPublish = kind === 'publish';
172
+ if (options.pointOfView === 'application') {
173
+ operation.action = isPublish ? 'receive' : 'send';
174
+ }
175
+ else {
176
+ operation.action = isPublish ? 'send' : 'receive';
177
+ }
178
+ const message = operation.message;
179
+ let serializedMessages = {};
180
+ if (message) {
181
+ delete operation.message;
182
+ const oldMessagePath = ['channels', oldChannelId, kind, 'message'];
183
+ const newMessagePath = ['channels', channelId, 'messages'];
184
+ if (inComponents) {
185
+ oldMessagePath.unshift('components');
186
+ newMessagePath.unshift('components');
187
+ }
188
+ serializedMessages = moveMessagesFromOperation(message, newMessagePath, oldMessagePath, asyncapi, options, context, operationId);
189
+ applyMessageRefsToOperation(serializedMessages, newMessagePath, operation);
190
+ }
191
+ const sortedOperation = (0, utils_1.sortObjectKeys)(operation, ['action', 'channel', 'title', 'summary', 'description', 'security', 'tags', 'externalDocs', 'bindings', 'traits']);
192
+ return { operationId, operation: sortedOperation, messages: serializedMessages };
193
+ }
194
+ /**
195
+ * Remove all messages under operations and return an object of them.
196
+ */
197
+ function moveMessagesFromOperation(message, newMessagePath, oldMessagePath, asyncapi, options, context, operationId) {
198
+ if (Array.isArray(message.oneOf)) {
199
+ //Message oneOf no longer exists, it's implicit by having multiple entires in the message object.
200
+ return message.oneOf.reduce((acc, current, index) => {
201
+ const messagePath = [...oldMessagePath, 'oneOf', index];
202
+ const messageId = options.idGenerator({ asyncapi, kind: 'message', key: index, path: messagePath, object: current, parentId: operationId });
203
+ context.refs.set((0, utils_1.createRefPath)(...messagePath), (0, utils_1.createRefPath)(...newMessagePath, messageId));
204
+ acc[messageId] = current;
205
+ return acc;
206
+ }, {});
207
+ }
208
+ else {
209
+ const messageId = options.idGenerator({ asyncapi, kind: 'message', key: 'message', path: oldMessagePath, object: message, parentId: operationId });
210
+ context.refs.set((0, utils_1.createRefPath)(...oldMessagePath), (0, utils_1.createRefPath)(...newMessagePath, messageId));
211
+ return { [messageId]: message };
212
+ }
213
+ }
214
+ /**
215
+ * Add references of messages to operations.
216
+ */
217
+ function applyMessageRefsToOperation(serializedMessages, newMessagePath, operation) {
218
+ if (Object.keys(serializedMessages !== null && serializedMessages !== void 0 ? serializedMessages : {}).length > 0) {
219
+ const newOperationMessages = [];
220
+ Object.entries(serializedMessages).forEach(([messageId, messageValue]) => {
221
+ if ((0, utils_1.isRefObject)(messageValue)) {
222
+ // shallow copy of JS reference
223
+ newOperationMessages.push(Object.assign({}, messageValue));
224
+ }
225
+ else {
226
+ const messagePath = [...newMessagePath, messageId];
227
+ newOperationMessages.push((0, utils_1.createRefObject)(...messagePath));
228
+ }
229
+ });
230
+ operation.messages = newOperationMessages;
231
+ }
232
+ }
233
+ /**
234
+ * Convert messages that use custom schema format into schema union.
235
+ */
236
+ function convertMessages(data) {
237
+ const messages = Object.assign({}, data.messages);
238
+ // Convert schema formats to union schemas
239
+ Object.entries(messages).forEach(([_, message]) => {
240
+ if (message.schemaFormat !== undefined) {
241
+ const payloadSchema = message.payload;
242
+ message.payload = {
243
+ schemaFormat: message.schemaFormat,
244
+ schema: payloadSchema
245
+ };
246
+ delete message.schemaFormat;
247
+ }
248
+ });
249
+ return messages;
250
+ }
251
+ /**
252
+ * Convert `channels`, `servers` and `securitySchemes` in components.
253
+ */
254
+ function convertComponents(asyncapi, options, context) {
255
+ const components = asyncapi.components;
256
+ if (!(0, utils_1.isPlainObject)(components)) {
257
+ return;
258
+ }
259
+ if (options.convertServerComponents && (0, utils_1.isPlainObject)(components.servers)) {
260
+ components.servers = convertServerObjects(components.servers, asyncapi);
261
+ }
262
+ if (options.convertChannelComponents && (0, utils_1.isPlainObject)(components.channels)) {
263
+ components.channels = convertChannelObjects(components.channels, asyncapi, options, context, true);
264
+ }
265
+ if ((0, utils_1.isPlainObject)(components.securitySchemes)) {
266
+ components.securitySchemes = convertSecuritySchemes(components.securitySchemes);
267
+ }
268
+ if ((0, utils_1.isPlainObject)(components.messages)) {
269
+ components.messages = convertMessages({
270
+ messages: components.messages
271
+ });
272
+ }
273
+ }
274
+ /**
275
+ * Convert `channels`, `servers` and `securitySchemes` in components.
276
+ */
277
+ function convertSecuritySchemes(securitySchemes) {
278
+ const newSecuritySchemes = {};
279
+ Object.entries(securitySchemes).forEach(([name, scheme]) => {
280
+ newSecuritySchemes[name] = convertSecuritySchemeObject(scheme);
281
+ });
282
+ return newSecuritySchemes;
283
+ }
284
+ /**
285
+ * Unify referencing mechanism in security field
286
+ */
287
+ function convertSecurityObject(security, asyncapi) {
288
+ const newSecurity = [];
289
+ security.forEach(securityItem => {
290
+ Object.entries(securityItem).forEach(([securityName, scopes]) => {
291
+ // without scopes - use ref
292
+ if (!scopes.length) {
293
+ newSecurity.push((0, utils_1.createRefObject)('components', 'securitySchemes', securityName));
294
+ return;
295
+ }
296
+ // create new security scheme in the components/securitySchemes with appropriate scopes
297
+ const securityScheme = (0, utils_1.getValueByPath)(asyncapi, ['components', 'securitySchemes', securityName]);
298
+ // handle logic only on `oauth2` and `openIdConnect` security mechanism
299
+ if (securityScheme.type === 'oauth2' || securityScheme.type === 'openIdConnect') {
300
+ const newSecurityScheme = convertSecuritySchemeObject(securityScheme);
301
+ newSecurity.push(Object.assign(Object.assign({}, newSecurityScheme), { scopes: [...scopes] }));
302
+ }
303
+ });
304
+ });
305
+ return newSecurity;
306
+ }
307
+ const flowKinds = ['implicit', 'password', 'clientCredentials', 'authorizationCode'];
308
+ /**
309
+ * Convert security scheme object to new from v3 version (flow.[x].scopes -> flow.[x].availableScopes).
310
+ */
311
+ function convertSecuritySchemeObject(original) {
312
+ const securityScheme = JSON.parse(JSON.stringify(original));
313
+ if (securityScheme.flows) {
314
+ flowKinds.forEach(flow => {
315
+ const flowScheme = securityScheme.flows[flow];
316
+ if (flowScheme === null || flowScheme === void 0 ? void 0 : flowScheme.scopes) {
317
+ flowScheme.availableScopes = flowScheme.scopes;
318
+ delete flowScheme.scopes;
319
+ }
320
+ });
321
+ }
322
+ return securityScheme;
323
+ }
324
+ /**
325
+ * Split `url` to the `host` and `pathname` (optional) fields.
326
+ *
327
+ * This function takes care of https://github.com/asyncapi/spec/pull/888
328
+ */
329
+ function resolveServerUrl(url) {
330
+ let [maybeProtocol, maybeHost] = url.split('://');
331
+ if (!maybeHost) {
332
+ maybeHost = maybeProtocol;
333
+ }
334
+ const [host, ...pathnames] = maybeHost.split('/');
335
+ if (pathnames.length) {
336
+ return { host, pathname: `/${pathnames.join('/')}`, protocol: maybeProtocol };
337
+ }
338
+ return { host, pathname: undefined, protocol: maybeProtocol };
339
+ }
340
+ /**
341
+ * Check if given channel (based on path) is used in the `channels` object.
342
+ */
343
+ function channelIsUsed(channels, path) {
344
+ for (const channel of Object.values(channels)) {
345
+ if ((0, utils_1.isRefObject)(channel) && (0, utils_1.createRefPath)(...path) === channel.$ref) {
346
+ return true;
347
+ }
348
+ }
349
+ return false;
350
+ }
351
+ /**
352
+ * Replace all deep local references with the new beginning of ref (when object is moved to another place).
353
+ */
354
+ function replaceDeepRefs(value, refs, key, parent) {
355
+ if (key === '$ref' && typeof value === 'string') {
356
+ const newRef = replaceRef(value, refs);
357
+ if (typeof newRef === 'string') {
358
+ parent[key] = newRef;
359
+ }
360
+ return;
361
+ }
362
+ if (Array.isArray(value)) {
363
+ return value.forEach((item, idx) => replaceDeepRefs(item, refs, idx, value));
364
+ }
365
+ if (value && typeof value === 'object') {
366
+ for (const objKey in value) {
367
+ replaceDeepRefs(value[objKey], refs, objKey, value);
368
+ }
369
+ }
370
+ }
371
+ function replaceRef(ref, refs) {
372
+ const allowed = [];
373
+ refs.forEach((_, key) => {
374
+ // few refs can be allowed
375
+ if (ref.startsWith(key)) {
376
+ allowed.push(key);
377
+ }
378
+ });
379
+ // find the longest one
380
+ allowed.sort((a, b) => a.length - b.length);
381
+ const from = allowed.pop();
382
+ if (!from) {
383
+ return;
384
+ }
385
+ const toReplace = refs.get(from);
386
+ if (toReplace) {
387
+ return ref.replace(from, toReplace);
388
+ }
389
+ }
390
+ /**
391
+ * Default function to generate ids for objects.
392
+ */
393
+ function idGeneratorFactory(options) {
394
+ const useChannelIdExtension = options.useChannelIdExtension;
395
+ return (data) => {
396
+ const { asyncapi, kind, object, key, parentId } = data;
397
+ switch (kind) {
398
+ case 'channel':
399
+ return generateIdForChannel(object, key, useChannelIdExtension);
400
+ case 'operation':
401
+ {
402
+ const oldOperationId = object.operationId;
403
+ const operationId = oldOperationId || (parentId ? `${parentId}.${key}` : kind);
404
+ return operationId;
405
+ }
406
+ ;
407
+ case 'message':
408
+ return generateIdForMessage(object, asyncapi, parentId, key);
409
+ default: return '';
410
+ }
411
+ };
412
+ }
413
+ function generateIdForChannel(object, key, useChannelIdExtension) {
414
+ if ((0, utils_1.isRefObject)(object)) {
415
+ const id = key;
416
+ return id;
417
+ }
418
+ const channel = object;
419
+ let channelId;
420
+ if (useChannelIdExtension) {
421
+ channelId = channel['x-channelId'] || key;
422
+ }
423
+ else {
424
+ channelId = key;
425
+ }
426
+ return channelId;
427
+ }
428
+ function generateIdForMessage(object, asyncapi, parentId, key) {
429
+ if ((0, utils_1.isRefObject)(object)) {
430
+ const possibleMessage = (0, utils_1.getValueByRef)(asyncapi, object.$ref);
431
+ if (possibleMessage === null || possibleMessage === void 0 ? void 0 : possibleMessage.messageId) {
432
+ const messageId = possibleMessage.messageId;
433
+ return messageId;
434
+ }
435
+ }
436
+ const messageId = object.messageId;
437
+ if (messageId) {
438
+ return messageId;
439
+ }
440
+ let operationKind;
441
+ const splitParentId = parentId.split('.');
442
+ if (splitParentId.length === 1) {
443
+ operationKind = parentId;
444
+ }
445
+ else {
446
+ operationKind = splitParentId.pop();
447
+ }
448
+ if (typeof key === 'number') {
449
+ return `${operationKind}.message.${key}`;
450
+ }
451
+ return `${operationKind}.message`;
452
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asyncapi/converter",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Convert AsyncAPI documents from older to newer versions.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",