@asyncapi/converter 1.2.1 → 1.3.1
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 +70 -0
- package/lib/convert.js +2 -1
- package/lib/interfaces.d.ts +18 -2
- package/lib/third-version.d.ts +2 -0
- package/lib/third-version.js +497 -0
- package/package.json +1 -1
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);
|
package/lib/interfaces.d.ts
CHANGED
|
@@ -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
|
|
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,497 @@
|
|
|
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
|
+
//Change parameter formats
|
|
113
|
+
if ((0, utils_1.isPlainObject)(channel.parameters)) {
|
|
114
|
+
channel.parameters = convertParameters(channel.parameters);
|
|
115
|
+
}
|
|
116
|
+
const operations = {};
|
|
117
|
+
// serialize publish and subscribe Operation Objects to standalone object
|
|
118
|
+
const publishMessages = toStandaloneOperation({ kind: 'publish', channel, asyncapi, operations, context, inComponents, channelId, channelAddress, options, oldPath });
|
|
119
|
+
const subscribeMessages = toStandaloneOperation({ kind: 'subscribe', channel, asyncapi, operations, context, inComponents, channelId, channelAddress, options, oldPath });
|
|
120
|
+
if (publishMessages || subscribeMessages) {
|
|
121
|
+
const allOperationMessages = Object.assign(Object.assign({}, publishMessages), subscribeMessages);
|
|
122
|
+
channel.messages = convertMessages({
|
|
123
|
+
messages: allOperationMessages
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
setOperationsOnRoot({ operations, inComponents, asyncapi, oldPath });
|
|
127
|
+
newChannels[channelId] = (0, utils_1.sortObjectKeys)(channel, ['address', 'messages', 'title', 'summary', 'description', 'servers', 'parameters', 'tags', 'externalDocs', 'bindings']);
|
|
128
|
+
});
|
|
129
|
+
return newChannels;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Assign the operations to the root AsyncAPI object.
|
|
133
|
+
*/
|
|
134
|
+
function setOperationsOnRoot(data) {
|
|
135
|
+
var _a, _b, _c, _d, _e;
|
|
136
|
+
const { operations, inComponents, asyncapi, oldPath } = data;
|
|
137
|
+
if (Object.keys(operations)) {
|
|
138
|
+
if (inComponents) {
|
|
139
|
+
const components = asyncapi.components = (_a = asyncapi.components) !== null && _a !== void 0 ? _a : {};
|
|
140
|
+
components.operations = Object.assign(Object.assign({}, (_b = components.operations) !== null && _b !== void 0 ? _b : {}), operations);
|
|
141
|
+
// if given component is used in the `channels` object then create references for operations in the `operations` object
|
|
142
|
+
if (channelIsUsed((_c = asyncapi.channels) !== null && _c !== void 0 ? _c : {}, oldPath)) {
|
|
143
|
+
const referencedOperations = Object.keys(operations).reduce((acc, current) => {
|
|
144
|
+
acc[current] = (0, utils_1.createRefObject)('components', 'operations', current);
|
|
145
|
+
return acc;
|
|
146
|
+
}, {});
|
|
147
|
+
asyncapi.operations = Object.assign(Object.assign({}, (_d = asyncapi.operations) !== null && _d !== void 0 ? _d : {}), referencedOperations);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
asyncapi.operations = Object.assign(Object.assign({}, (_e = asyncapi.operations) !== null && _e !== void 0 ? _e : {}), operations);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Points to the connected channel and split messages for channel
|
|
157
|
+
*/
|
|
158
|
+
function convertOperationObject(data, options, context) {
|
|
159
|
+
const { asyncapi, channelId, oldChannelId, kind, inComponents } = data;
|
|
160
|
+
const operation = Object.assign({}, data.operation);
|
|
161
|
+
const oldChannelPath = ['channels', oldChannelId];
|
|
162
|
+
if (inComponents) {
|
|
163
|
+
oldChannelPath.unshift('components');
|
|
164
|
+
}
|
|
165
|
+
const newChannelPath = ['channels', channelId];
|
|
166
|
+
if (inComponents) {
|
|
167
|
+
newChannelPath.unshift('components');
|
|
168
|
+
}
|
|
169
|
+
const operationId = options.idGenerator({ asyncapi, kind: 'operation', key: kind, path: oldChannelPath, object: data.operation, parentId: channelId });
|
|
170
|
+
operation.channel = (0, utils_1.createRefObject)(...newChannelPath);
|
|
171
|
+
try {
|
|
172
|
+
delete operation.operationId;
|
|
173
|
+
}
|
|
174
|
+
catch (err) { }
|
|
175
|
+
const isPublish = kind === 'publish';
|
|
176
|
+
if (options.pointOfView === 'application') {
|
|
177
|
+
operation.action = isPublish ? 'receive' : 'send';
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
operation.action = isPublish ? 'send' : 'receive';
|
|
181
|
+
}
|
|
182
|
+
const message = operation.message;
|
|
183
|
+
let serializedMessages = {};
|
|
184
|
+
if (message) {
|
|
185
|
+
delete operation.message;
|
|
186
|
+
const oldMessagePath = ['channels', oldChannelId, kind, 'message'];
|
|
187
|
+
const newMessagePath = ['channels', channelId, 'messages'];
|
|
188
|
+
if (inComponents) {
|
|
189
|
+
oldMessagePath.unshift('components');
|
|
190
|
+
newMessagePath.unshift('components');
|
|
191
|
+
}
|
|
192
|
+
serializedMessages = moveMessagesFromOperation(message, newMessagePath, oldMessagePath, asyncapi, options, context, operationId);
|
|
193
|
+
applyMessageRefsToOperation(serializedMessages, newMessagePath, operation);
|
|
194
|
+
}
|
|
195
|
+
const sortedOperation = (0, utils_1.sortObjectKeys)(operation, ['action', 'channel', 'title', 'summary', 'description', 'security', 'tags', 'externalDocs', 'bindings', 'traits']);
|
|
196
|
+
return { operationId, operation: sortedOperation, messages: serializedMessages };
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Remove all messages under operations and return an object of them.
|
|
200
|
+
*/
|
|
201
|
+
function moveMessagesFromOperation(message, newMessagePath, oldMessagePath, asyncapi, options, context, operationId) {
|
|
202
|
+
if (Array.isArray(message.oneOf)) {
|
|
203
|
+
//Message oneOf no longer exists, it's implicit by having multiple entires in the message object.
|
|
204
|
+
return message.oneOf.reduce((acc, current, index) => {
|
|
205
|
+
const messagePath = [...oldMessagePath, 'oneOf', index];
|
|
206
|
+
const messageId = options.idGenerator({ asyncapi, kind: 'message', key: index, path: messagePath, object: current, parentId: operationId });
|
|
207
|
+
context.refs.set((0, utils_1.createRefPath)(...messagePath), (0, utils_1.createRefPath)(...newMessagePath, messageId));
|
|
208
|
+
acc[messageId] = current;
|
|
209
|
+
return acc;
|
|
210
|
+
}, {});
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
const messageId = options.idGenerator({ asyncapi, kind: 'message', key: 'message', path: oldMessagePath, object: message, parentId: operationId });
|
|
214
|
+
context.refs.set((0, utils_1.createRefPath)(...oldMessagePath), (0, utils_1.createRefPath)(...newMessagePath, messageId));
|
|
215
|
+
return { [messageId]: message };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Add references of messages to operations.
|
|
220
|
+
*/
|
|
221
|
+
function applyMessageRefsToOperation(serializedMessages, newMessagePath, operation) {
|
|
222
|
+
if (Object.keys(serializedMessages !== null && serializedMessages !== void 0 ? serializedMessages : {}).length > 0) {
|
|
223
|
+
const newOperationMessages = [];
|
|
224
|
+
Object.entries(serializedMessages).forEach(([messageId, messageValue]) => {
|
|
225
|
+
if ((0, utils_1.isRefObject)(messageValue)) {
|
|
226
|
+
// shallow copy of JS reference
|
|
227
|
+
newOperationMessages.push(Object.assign({}, messageValue));
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
const messagePath = [...newMessagePath, messageId];
|
|
231
|
+
newOperationMessages.push((0, utils_1.createRefObject)(...messagePath));
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
operation.messages = newOperationMessages;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Convert messages that use custom schema format into schema union.
|
|
239
|
+
*/
|
|
240
|
+
function convertMessages(data) {
|
|
241
|
+
const messages = Object.assign({}, data.messages);
|
|
242
|
+
// Convert schema formats to union schemas
|
|
243
|
+
Object.entries(messages).forEach(([_, message]) => {
|
|
244
|
+
if (message.schemaFormat !== undefined) {
|
|
245
|
+
const payloadSchema = message.payload;
|
|
246
|
+
message.payload = {
|
|
247
|
+
schemaFormat: message.schemaFormat,
|
|
248
|
+
schema: payloadSchema
|
|
249
|
+
};
|
|
250
|
+
delete message.schemaFormat;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
return messages;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Convert `channels`, `servers` and `securitySchemes` in components.
|
|
257
|
+
*/
|
|
258
|
+
function convertComponents(asyncapi, options, context) {
|
|
259
|
+
const components = asyncapi.components;
|
|
260
|
+
if (!(0, utils_1.isPlainObject)(components)) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (options.convertServerComponents && (0, utils_1.isPlainObject)(components.servers)) {
|
|
264
|
+
components.servers = convertServerObjects(components.servers, asyncapi);
|
|
265
|
+
}
|
|
266
|
+
if (options.convertChannelComponents && (0, utils_1.isPlainObject)(components.channels)) {
|
|
267
|
+
components.channels = convertChannelObjects(components.channels, asyncapi, options, context, true);
|
|
268
|
+
}
|
|
269
|
+
if ((0, utils_1.isPlainObject)(components.securitySchemes)) {
|
|
270
|
+
components.securitySchemes = convertSecuritySchemes(components.securitySchemes);
|
|
271
|
+
}
|
|
272
|
+
if ((0, utils_1.isPlainObject)(components.messages)) {
|
|
273
|
+
components.messages = convertMessages({
|
|
274
|
+
messages: components.messages
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
if ((0, utils_1.isPlainObject)(components.parameters)) {
|
|
278
|
+
components.parameters = convertParameters(components.parameters);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Convert all parameters to the new v3 format
|
|
283
|
+
*/
|
|
284
|
+
function convertParameters(parameters) {
|
|
285
|
+
const newParameters = {};
|
|
286
|
+
Object.entries(parameters).forEach(([name, parameter]) => {
|
|
287
|
+
newParameters[name] = convertParameter(parameter);
|
|
288
|
+
});
|
|
289
|
+
return newParameters;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Convert the old v2 parameter object to v3.
|
|
293
|
+
*
|
|
294
|
+
* Ensure that extensions and references are all kept as is.
|
|
295
|
+
*
|
|
296
|
+
* Does not include extensions from schema.
|
|
297
|
+
*/
|
|
298
|
+
function convertParameter(parameter) {
|
|
299
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
300
|
+
const ref = (_a = parameter['$ref']) !== null && _a !== void 0 ? _a : null;
|
|
301
|
+
if (ref !== null) {
|
|
302
|
+
return {
|
|
303
|
+
$ref: ref
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const enumValues = (_c = (_b = parameter.schema) === null || _b === void 0 ? void 0 : _b.enum) !== null && _c !== void 0 ? _c : null;
|
|
307
|
+
const defaultValues = (_e = (_d = parameter.schema) === null || _d === void 0 ? void 0 : _d.default) !== null && _e !== void 0 ? _e : null;
|
|
308
|
+
const description = (_h = (_f = parameter.description) !== null && _f !== void 0 ? _f : (_g = parameter.schema) === null || _g === void 0 ? void 0 : _g.description) !== null && _h !== void 0 ? _h : null;
|
|
309
|
+
const examples = (_k = (_j = parameter.schema) === null || _j === void 0 ? void 0 : _j.examples) !== null && _k !== void 0 ? _k : null;
|
|
310
|
+
const location = (_l = parameter.location) !== null && _l !== void 0 ? _l : null;
|
|
311
|
+
//Make sure we keep parameter extensions
|
|
312
|
+
const v2ParameterObjectProperties = ["location", "schema", "description"];
|
|
313
|
+
const v2ParameterObjectExtensions = Object.entries(parameter).filter(([key,]) => {
|
|
314
|
+
return !v2ParameterObjectProperties.includes(key);
|
|
315
|
+
});
|
|
316
|
+
//Return the new v3 parameter object
|
|
317
|
+
return Object.assign(Object.assign({}, v2ParameterObjectExtensions), enumValues === null ? null : { enum: enumValues }, defaultValues === null ? null : { default: defaultValues }, description === null ? null : { description }, examples === null ? null : { examples }, location === null ? null : { location });
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Convert `channels`, `servers` and `securitySchemes` in components.
|
|
321
|
+
*/
|
|
322
|
+
function convertSecuritySchemes(securitySchemes) {
|
|
323
|
+
const newSecuritySchemes = {};
|
|
324
|
+
Object.entries(securitySchemes).forEach(([name, scheme]) => {
|
|
325
|
+
newSecuritySchemes[name] = convertSecuritySchemeObject(scheme);
|
|
326
|
+
});
|
|
327
|
+
return newSecuritySchemes;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Unify referencing mechanism in security field
|
|
331
|
+
*/
|
|
332
|
+
function convertSecurityObject(security, asyncapi) {
|
|
333
|
+
const newSecurity = [];
|
|
334
|
+
security.forEach(securityItem => {
|
|
335
|
+
Object.entries(securityItem).forEach(([securityName, scopes]) => {
|
|
336
|
+
// without scopes - use ref
|
|
337
|
+
if (!scopes.length) {
|
|
338
|
+
newSecurity.push((0, utils_1.createRefObject)('components', 'securitySchemes', securityName));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// create new security scheme in the components/securitySchemes with appropriate scopes
|
|
342
|
+
const securityScheme = (0, utils_1.getValueByPath)(asyncapi, ['components', 'securitySchemes', securityName]);
|
|
343
|
+
// handle logic only on `oauth2` and `openIdConnect` security mechanism
|
|
344
|
+
if (securityScheme.type === 'oauth2' || securityScheme.type === 'openIdConnect') {
|
|
345
|
+
const newSecurityScheme = convertSecuritySchemeObject(securityScheme);
|
|
346
|
+
newSecurity.push(Object.assign(Object.assign({}, newSecurityScheme), { scopes: [...scopes] }));
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
return newSecurity;
|
|
351
|
+
}
|
|
352
|
+
const flowKinds = ['implicit', 'password', 'clientCredentials', 'authorizationCode'];
|
|
353
|
+
/**
|
|
354
|
+
* Convert security scheme object to new from v3 version (flow.[x].scopes -> flow.[x].availableScopes).
|
|
355
|
+
*/
|
|
356
|
+
function convertSecuritySchemeObject(original) {
|
|
357
|
+
const securityScheme = JSON.parse(JSON.stringify(original));
|
|
358
|
+
if (securityScheme.flows) {
|
|
359
|
+
flowKinds.forEach(flow => {
|
|
360
|
+
const flowScheme = securityScheme.flows[flow];
|
|
361
|
+
if (flowScheme === null || flowScheme === void 0 ? void 0 : flowScheme.scopes) {
|
|
362
|
+
flowScheme.availableScopes = flowScheme.scopes;
|
|
363
|
+
delete flowScheme.scopes;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
return securityScheme;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Split `url` to the `host` and `pathname` (optional) fields.
|
|
371
|
+
*
|
|
372
|
+
* This function takes care of https://github.com/asyncapi/spec/pull/888
|
|
373
|
+
*/
|
|
374
|
+
function resolveServerUrl(url) {
|
|
375
|
+
let [maybeProtocol, maybeHost] = url.split('://');
|
|
376
|
+
if (!maybeHost) {
|
|
377
|
+
maybeHost = maybeProtocol;
|
|
378
|
+
}
|
|
379
|
+
const [host, ...pathnames] = maybeHost.split('/');
|
|
380
|
+
if (pathnames.length) {
|
|
381
|
+
return { host, pathname: `/${pathnames.join('/')}`, protocol: maybeProtocol };
|
|
382
|
+
}
|
|
383
|
+
return { host, pathname: undefined, protocol: maybeProtocol };
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Check if given channel (based on path) is used in the `channels` object.
|
|
387
|
+
*/
|
|
388
|
+
function channelIsUsed(channels, path) {
|
|
389
|
+
for (const channel of Object.values(channels)) {
|
|
390
|
+
if ((0, utils_1.isRefObject)(channel) && (0, utils_1.createRefPath)(...path) === channel.$ref) {
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Replace all deep local references with the new beginning of ref (when object is moved to another place).
|
|
398
|
+
*/
|
|
399
|
+
function replaceDeepRefs(value, refs, key, parent) {
|
|
400
|
+
if (key === '$ref' && typeof value === 'string') {
|
|
401
|
+
const newRef = replaceRef(value, refs);
|
|
402
|
+
if (typeof newRef === 'string') {
|
|
403
|
+
parent[key] = newRef;
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (Array.isArray(value)) {
|
|
408
|
+
return value.forEach((item, idx) => replaceDeepRefs(item, refs, idx, value));
|
|
409
|
+
}
|
|
410
|
+
if (value && typeof value === 'object') {
|
|
411
|
+
for (const objKey in value) {
|
|
412
|
+
replaceDeepRefs(value[objKey], refs, objKey, value);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function replaceRef(ref, refs) {
|
|
417
|
+
const allowed = [];
|
|
418
|
+
refs.forEach((_, key) => {
|
|
419
|
+
// few refs can be allowed
|
|
420
|
+
if (ref.startsWith(key)) {
|
|
421
|
+
allowed.push(key);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
// find the longest one
|
|
425
|
+
allowed.sort((a, b) => a.length - b.length);
|
|
426
|
+
const from = allowed.pop();
|
|
427
|
+
if (!from) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const toReplace = refs.get(from);
|
|
431
|
+
if (toReplace) {
|
|
432
|
+
return ref.replace(from, toReplace);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Default function to generate ids for objects.
|
|
437
|
+
*/
|
|
438
|
+
function idGeneratorFactory(options) {
|
|
439
|
+
const useChannelIdExtension = options.useChannelIdExtension;
|
|
440
|
+
return (data) => {
|
|
441
|
+
const { asyncapi, kind, object, key, parentId } = data;
|
|
442
|
+
switch (kind) {
|
|
443
|
+
case 'channel':
|
|
444
|
+
return generateIdForChannel(object, key, useChannelIdExtension);
|
|
445
|
+
case 'operation':
|
|
446
|
+
{
|
|
447
|
+
const oldOperationId = object.operationId;
|
|
448
|
+
const operationId = oldOperationId || (parentId ? `${parentId}.${key}` : kind);
|
|
449
|
+
return operationId;
|
|
450
|
+
}
|
|
451
|
+
;
|
|
452
|
+
case 'message':
|
|
453
|
+
return generateIdForMessage(object, asyncapi, parentId, key);
|
|
454
|
+
default: return '';
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function generateIdForChannel(object, key, useChannelIdExtension) {
|
|
459
|
+
if ((0, utils_1.isRefObject)(object)) {
|
|
460
|
+
const id = key;
|
|
461
|
+
return id;
|
|
462
|
+
}
|
|
463
|
+
const channel = object;
|
|
464
|
+
let channelId;
|
|
465
|
+
if (useChannelIdExtension) {
|
|
466
|
+
channelId = channel['x-channelId'] || key;
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
channelId = key;
|
|
470
|
+
}
|
|
471
|
+
return channelId;
|
|
472
|
+
}
|
|
473
|
+
function generateIdForMessage(object, asyncapi, parentId, key) {
|
|
474
|
+
if ((0, utils_1.isRefObject)(object)) {
|
|
475
|
+
const possibleMessage = (0, utils_1.getValueByRef)(asyncapi, object.$ref);
|
|
476
|
+
if (possibleMessage === null || possibleMessage === void 0 ? void 0 : possibleMessage.messageId) {
|
|
477
|
+
const messageId = possibleMessage.messageId;
|
|
478
|
+
return messageId;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const messageId = object.messageId;
|
|
482
|
+
if (messageId) {
|
|
483
|
+
return messageId;
|
|
484
|
+
}
|
|
485
|
+
let operationKind;
|
|
486
|
+
const splitParentId = parentId.split('.');
|
|
487
|
+
if (splitParentId.length === 1) {
|
|
488
|
+
operationKind = parentId;
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
operationKind = splitParentId.pop();
|
|
492
|
+
}
|
|
493
|
+
if (typeof key === 'number') {
|
|
494
|
+
return `${operationKind}.message.${key}`;
|
|
495
|
+
}
|
|
496
|
+
return `${operationKind}.message`;
|
|
497
|
+
}
|