@asyncapi/converter 1.4.21 → 1.5.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 +45 -1
- package/lib/convert.d.ts +5 -3
- package/lib/convert.js +25 -6
- package/lib/index.d.ts +1 -1
- package/lib/interfaces.d.ts +10 -1
- package/lib/openapi.d.ts +2 -0
- package/lib/openapi.js +527 -0
- package/lib/utils.d.ts +4 -3
- package/lib/utils.js +28 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AsyncAPI Converter
|
|
2
2
|
|
|
3
|
-
Convert [AsyncAPI](https://asyncapi.com) documents older to newer versions.
|
|
3
|
+
Convert [AsyncAPI](https://asyncapi.com) documents older to newer versions and you can also convert OpenAPI documents to AsyncAPI documents.
|
|
4
4
|
|
|
5
5
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
6
6
|
[](#contributors-)
|
|
@@ -17,6 +17,7 @@ Convert [AsyncAPI](https://asyncapi.com) documents older to newer versions.
|
|
|
17
17
|
* [In TS](#in-ts)
|
|
18
18
|
- [Conversion 2.x.x to 3.x.x](#conversion-2xx-to-3xx)
|
|
19
19
|
- [Known missing features](#known-missing-features)
|
|
20
|
+
- [OpenAPI 3.0 to AsyncAPI 3.0 Conversion](#openapi-30-to-asyncapi-30-conversion)
|
|
20
21
|
- [Development](#development)
|
|
21
22
|
- [Contribution](#contribution)
|
|
22
23
|
- [Contributors ✨](#contributors-%E2%9C%A8)
|
|
@@ -194,6 +195,49 @@ Conversion to version `3.x.x` from `2.x.x` has several assumptions that should b
|
|
|
194
195
|
examples: ["test"]
|
|
195
196
|
```
|
|
196
197
|
|
|
198
|
+
### OpenAPI 3.0 to AsyncAPI 3.0 Conversion
|
|
199
|
+
|
|
200
|
+
The converter now supports transformation from OpenAPI 3.0 to AsyncAPI 3.0. This feature enables easy transition of existing OpenAPI 3.0 documents to AsyncAPI 3.0.
|
|
201
|
+
|
|
202
|
+
To use this new conversion feature:
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
const fs = require('fs');
|
|
206
|
+
const { convert } = require('@asyncapi/converter')
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const openapi = fs.readFileSync('openapi.yml', 'utf-8')
|
|
210
|
+
const asyncapi = convert(openapi, '3.0.0', { from: 'openapi' });
|
|
211
|
+
console.log(asyncapi);
|
|
212
|
+
} catch (e) {
|
|
213
|
+
console.error(e);
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
When converting from OpenAPI to AsyncAPI you can now specify the perspective of the conversion using the `perspective` option. This allows you to choose whether the conversion should be from an application or client point of view
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
const { convert } = require('@asyncapi/converter')
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const asyncapi2 = fs.readFileSync('asyncapi2.yml', 'utf-8')
|
|
224
|
+
const asyncapi3 = convert(asyncapi2, '3.0.0', { openAPIToAsyncAPI: { perspective: 'client' } });
|
|
225
|
+
console.log(asyncapi3);
|
|
226
|
+
} catch (e) {
|
|
227
|
+
console.error(e);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The perspective option can be set to either 'server' (default) or 'client'.
|
|
232
|
+
|
|
233
|
+
- With `server` perspective: `action` becomes `receive`
|
|
234
|
+
|
|
235
|
+
- With `client` perspective: `action` becomes `send`
|
|
236
|
+
|
|
237
|
+
#### Limitations
|
|
238
|
+
|
|
239
|
+
- External to internal references: The converter does not support scenarios where an external schema file references internal components of the OpenAPI document. In such cases, manual adjustment of the converted document may be necessary.
|
|
240
|
+
|
|
197
241
|
## Development
|
|
198
242
|
|
|
199
243
|
1. Setup project by installing dependencies `npm install`
|
package/lib/convert.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import type { AsyncAPIDocument,
|
|
2
|
-
export declare function convert(
|
|
3
|
-
export declare function convert(
|
|
1
|
+
import type { AsyncAPIDocument, AsyncAPIConvertVersion, OpenAPIConvertVersion, ConvertOptions, OpenAPIDocument, OpenAPIToAsyncAPIOptions } from './interfaces';
|
|
2
|
+
export declare function convert(input: string, version: AsyncAPIConvertVersion, options?: ConvertOptions): string;
|
|
3
|
+
export declare function convert(input: AsyncAPIDocument, version: AsyncAPIConvertVersion, options?: ConvertOptions): AsyncAPIDocument;
|
|
4
|
+
export declare function convertOpenAPI(input: string, version: OpenAPIConvertVersion, options?: OpenAPIToAsyncAPIOptions): string;
|
|
5
|
+
export declare function convertOpenAPI(input: OpenAPIDocument, version: OpenAPIConvertVersion, options?: OpenAPIToAsyncAPIOptions): AsyncAPIDocument;
|
package/lib/convert.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.convert = void 0;
|
|
3
|
+
exports.convertOpenAPI = 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
7
|
const third_version_1 = require("./third-version");
|
|
8
|
+
const openapi_1 = require("./openapi");
|
|
8
9
|
const utils_1 = require("./utils");
|
|
9
10
|
/**
|
|
10
11
|
* Value for key (version) represents the function which converts specification from previous version to the given as key.
|
|
11
12
|
*/
|
|
12
|
-
const
|
|
13
|
-
const conversionVersions = Object.keys(
|
|
14
|
-
function convert(
|
|
15
|
-
const { format, document } = (0, utils_1.serializeInput)(
|
|
13
|
+
const asyncAPIconverters = Object.assign(Object.assign(Object.assign({}, first_version_1.converters), second_version_1.converters), third_version_1.converters);
|
|
14
|
+
const conversionVersions = Object.keys(asyncAPIconverters);
|
|
15
|
+
function convert(input, version, options = {}) {
|
|
16
|
+
const { format, document } = (0, utils_1.serializeInput)(input);
|
|
17
|
+
if ('openapi' in document) {
|
|
18
|
+
throw new Error('Cannot convert OpenAPI document. Use convertOpenAPI function instead.');
|
|
19
|
+
}
|
|
16
20
|
const asyncapiVersion = document.asyncapi;
|
|
17
21
|
let fromVersion = conversionVersions.indexOf(asyncapiVersion);
|
|
18
22
|
const toVersion = conversionVersions.indexOf(version);
|
|
@@ -30,7 +34,7 @@ function convert(asyncapi, version = '2.6.0', options = {}) {
|
|
|
30
34
|
let converted = document;
|
|
31
35
|
for (let i = fromVersion; i <= toVersion; i++) {
|
|
32
36
|
const v = conversionVersions[i];
|
|
33
|
-
converted =
|
|
37
|
+
converted = asyncAPIconverters[v](converted, options);
|
|
34
38
|
}
|
|
35
39
|
if (format === 'yaml') {
|
|
36
40
|
return (0, js_yaml_1.dump)(converted, { skipInvalid: true });
|
|
@@ -38,3 +42,18 @@ function convert(asyncapi, version = '2.6.0', options = {}) {
|
|
|
38
42
|
return converted;
|
|
39
43
|
}
|
|
40
44
|
exports.convert = convert;
|
|
45
|
+
function convertOpenAPI(input, version, options = {}) {
|
|
46
|
+
const { format, document } = (0, utils_1.serializeInput)(input);
|
|
47
|
+
const openApiVersion = document.openapi;
|
|
48
|
+
const converterVersion = openApiVersion;
|
|
49
|
+
const openapiToAsyncapiConverter = openapi_1.converters[converterVersion];
|
|
50
|
+
if (!openapiToAsyncapiConverter) {
|
|
51
|
+
throw new Error(`We are not able to convert OpenAPI ${converterVersion} to AsyncAPI, please raise a feature request.`);
|
|
52
|
+
}
|
|
53
|
+
const convertedAsyncAPI = openapiToAsyncapiConverter(document, options);
|
|
54
|
+
if (format === "yaml") {
|
|
55
|
+
return (0, js_yaml_1.dump)(convertedAsyncAPI, { skipInvalid: true });
|
|
56
|
+
}
|
|
57
|
+
return convertedAsyncAPI;
|
|
58
|
+
}
|
|
59
|
+
exports.convertOpenAPI = convertOpenAPI;
|
package/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { convert } from './convert';
|
|
2
|
-
export type { AsyncAPIDocument,
|
|
2
|
+
export type { AsyncAPIDocument, AsyncAPIConvertVersion, OpenAPIConvertVersion, ConvertOptions } from './interfaces';
|
package/lib/interfaces.d.ts
CHANGED
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
export declare type AsyncAPIDocument = {
|
|
5
5
|
asyncapi: string;
|
|
6
6
|
} & Record<string, any>;
|
|
7
|
-
export declare type
|
|
7
|
+
export declare type OpenAPIDocument = {
|
|
8
|
+
openapi: string;
|
|
9
|
+
} & Record<string, any>;
|
|
10
|
+
export declare type AsyncAPIConvertVersion = '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';
|
|
11
|
+
export declare type OpenAPIConvertVersion = '3.0.0';
|
|
8
12
|
export declare type ConvertV2ToV3Options = {
|
|
9
13
|
idGenerator?: (data: {
|
|
10
14
|
asyncapi: AsyncAPIDocument;
|
|
@@ -19,10 +23,15 @@ export declare type ConvertV2ToV3Options = {
|
|
|
19
23
|
convertServerComponents?: boolean;
|
|
20
24
|
convertChannelComponents?: boolean;
|
|
21
25
|
};
|
|
26
|
+
export declare type OpenAPIToAsyncAPIOptions = {
|
|
27
|
+
perspective?: 'client' | 'server';
|
|
28
|
+
};
|
|
22
29
|
export declare type ConvertOptions = {
|
|
23
30
|
v2tov3?: ConvertV2ToV3Options;
|
|
31
|
+
openAPIToAsyncAPI?: OpenAPIToAsyncAPIOptions;
|
|
24
32
|
};
|
|
25
33
|
/**
|
|
26
34
|
* PRIVATE TYPES
|
|
27
35
|
*/
|
|
28
36
|
export declare type ConvertFunction = (asyncapi: AsyncAPIDocument, options: ConvertOptions) => AsyncAPIDocument;
|
|
37
|
+
export declare type ConvertOpenAPIFunction = (openapi: OpenAPIDocument, options: OpenAPIToAsyncAPIOptions) => AsyncAPIDocument;
|
package/lib/openapi.d.ts
ADDED
package/lib/openapi.js
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
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_openapi_to_asyncapi,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Converts an OpenAPI document to an AsyncAPI document.
|
|
10
|
+
* @param {OpenAPIDocument} openapi - The OpenAPI document to convert.
|
|
11
|
+
* @param {ConvertOptions} options - Conversion options.
|
|
12
|
+
* @returns {AsyncAPIDocument} The converted AsyncAPI document.
|
|
13
|
+
*/
|
|
14
|
+
function from_openapi_to_asyncapi(openapi, options = {}) {
|
|
15
|
+
const perspective = options.perspective || 'server';
|
|
16
|
+
const asyncapi = {
|
|
17
|
+
asyncapi: '3.0.0',
|
|
18
|
+
info: convertInfoObject(openapi.info, openapi),
|
|
19
|
+
servers: openapi.servers ? convertServerObjects(openapi.servers, openapi) : undefined,
|
|
20
|
+
channels: {},
|
|
21
|
+
operations: {},
|
|
22
|
+
components: convertComponents(openapi)
|
|
23
|
+
};
|
|
24
|
+
const { channels, operations } = convertPaths(openapi.paths, perspective);
|
|
25
|
+
asyncapi.channels = channels;
|
|
26
|
+
asyncapi.operations = operations;
|
|
27
|
+
(0, utils_1.removeEmptyObjects)(asyncapi);
|
|
28
|
+
return (0, utils_1.sortObjectKeys)(asyncapi, ['asyncapi', 'info', 'defaultContentType', 'servers', 'channels', 'operations', 'components']);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Converts openAPI info objects to asyncAPI info objects.
|
|
32
|
+
* @param info - The openAPI info object to convert.
|
|
33
|
+
* @param openapi - The complete openAPI document.
|
|
34
|
+
* @returns openAPI info object
|
|
35
|
+
*/
|
|
36
|
+
function convertInfoObject(info, openapi) {
|
|
37
|
+
return (0, utils_1.sortObjectKeys)(Object.assign(Object.assign({}, info), { tags: [openapi.tags], externalDocs: openapi.externalDocs }), [
|
|
38
|
+
"title",
|
|
39
|
+
"version",
|
|
40
|
+
"description",
|
|
41
|
+
"termsOfService",
|
|
42
|
+
"contact",
|
|
43
|
+
"license",
|
|
44
|
+
"tags",
|
|
45
|
+
"externalDocs",
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Converts OpenAPI server objects to AsyncAPI server objects.
|
|
50
|
+
* @param {ServerObject[]} servers - The OpenAPI server objects to convert.
|
|
51
|
+
* @param {OpenAPIDocument} openapi - The complete OpenAPI document.
|
|
52
|
+
* @returns {AsyncAPIDocument['servers']} The converted AsyncAPI server objects.
|
|
53
|
+
*/
|
|
54
|
+
function convertServerObjects(servers, openapi) {
|
|
55
|
+
const newServers = {};
|
|
56
|
+
const security = openapi.security;
|
|
57
|
+
servers.forEach((server) => {
|
|
58
|
+
const serverName = generateServerName(server.url);
|
|
59
|
+
if ((0, utils_1.isRefObject)(server)) {
|
|
60
|
+
newServers[serverName] = server;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const { host, pathname, protocol } = resolveServerUrl(server.url);
|
|
64
|
+
server.host = host;
|
|
65
|
+
if (pathname !== undefined) {
|
|
66
|
+
server.pathname = pathname;
|
|
67
|
+
}
|
|
68
|
+
if (protocol !== undefined && server.protocol === undefined) {
|
|
69
|
+
server.protocol = protocol;
|
|
70
|
+
}
|
|
71
|
+
delete server.url;
|
|
72
|
+
if (security) {
|
|
73
|
+
server.security = security.map((securityRequirement) => {
|
|
74
|
+
// pass through the security requirement, conversion will happen in components
|
|
75
|
+
return securityRequirement;
|
|
76
|
+
});
|
|
77
|
+
delete openapi.security;
|
|
78
|
+
}
|
|
79
|
+
newServers[serverName] = (0, utils_1.sortObjectKeys)(server, ['host', 'pathname', 'protocol', 'protocolVersion', 'title', 'summary', 'description', 'variables', 'security', 'tags', 'externalDocs', 'bindings']);
|
|
80
|
+
});
|
|
81
|
+
return newServers;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Generates a server name based on the server URL.
|
|
85
|
+
* @param {string} url - The server URL.
|
|
86
|
+
* @returns {string} The generated server name.
|
|
87
|
+
*/
|
|
88
|
+
function generateServerName(url) {
|
|
89
|
+
const { host, pathname } = resolveServerUrl(url);
|
|
90
|
+
const baseName = host.split('.').slice(-2).join('.');
|
|
91
|
+
const pathSegment = pathname ? pathname.split('/')[1] : '';
|
|
92
|
+
return `${baseName}${pathSegment ? `_${pathSegment}` : ''}`.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
93
|
+
}
|
|
94
|
+
function resolveServerUrl(url) {
|
|
95
|
+
let [maybeProtocol, maybeHost] = url.split("://");
|
|
96
|
+
if (!maybeHost) {
|
|
97
|
+
maybeHost = maybeProtocol;
|
|
98
|
+
}
|
|
99
|
+
const [host, ...pathnames] = maybeHost.split("/");
|
|
100
|
+
if (pathnames.length) {
|
|
101
|
+
return {
|
|
102
|
+
host,
|
|
103
|
+
pathname: `/${pathnames.join("/")}`,
|
|
104
|
+
protocol: maybeProtocol,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return { host, pathname: undefined, protocol: maybeProtocol };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Converts OpenAPI paths to AsyncAPI channels and operations.
|
|
111
|
+
* @param {Record<string, any>} paths - The OpenAPI paths object.
|
|
112
|
+
* @param {'client' | 'server'} perspective - The perspective of the conversion (client or server).
|
|
113
|
+
* @returns {{ channels: AsyncAPIDocument['channels'], operations: AsyncAPIDocument['operations'] }}
|
|
114
|
+
*/
|
|
115
|
+
function convertPaths(paths, perspective) {
|
|
116
|
+
var _a;
|
|
117
|
+
const channels = {};
|
|
118
|
+
const operations = {};
|
|
119
|
+
if (paths) {
|
|
120
|
+
for (const [path, pathItemOrRef] of Object.entries(paths)) {
|
|
121
|
+
if (!(0, utils_1.isPlainObject)(pathItemOrRef))
|
|
122
|
+
continue;
|
|
123
|
+
const pathItem = (0, utils_1.isRefObject)(pathItemOrRef) ? pathItemOrRef : pathItemOrRef;
|
|
124
|
+
const channelName = path.replace(/^\//, '').replace(/\//g, '_') || 'root';
|
|
125
|
+
channels[channelName] = {
|
|
126
|
+
address: path,
|
|
127
|
+
messages: {},
|
|
128
|
+
parameters: convertPathParameters(path, pathItem.parameters)
|
|
129
|
+
};
|
|
130
|
+
for (const [method, operation] of Object.entries(pathItem)) {
|
|
131
|
+
if (['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace'].includes(method) && (0, utils_1.isPlainObject)(operation)) {
|
|
132
|
+
const operationObject = operation;
|
|
133
|
+
const operationId = operationObject.operationId || `${method}${channelName}`;
|
|
134
|
+
// Create operation
|
|
135
|
+
operations[operationId] = {
|
|
136
|
+
action: perspective === 'client' ? 'send' : 'receive',
|
|
137
|
+
channel: (0, utils_1.createRefObject)('channels', channelName),
|
|
138
|
+
summary: operationObject.summary,
|
|
139
|
+
description: operationObject.description,
|
|
140
|
+
tags: (_a = operationObject.tags) === null || _a === void 0 ? void 0 : _a.map((tag) => ({ name: tag })),
|
|
141
|
+
bindings: {
|
|
142
|
+
http: {
|
|
143
|
+
method: method.toUpperCase(),
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
messages: []
|
|
147
|
+
};
|
|
148
|
+
// Convert request body to message
|
|
149
|
+
if (operationObject.requestBody) {
|
|
150
|
+
const requestMessages = convertRequestBodyToMessages(operationObject.requestBody, operationId, method);
|
|
151
|
+
Object.assign(channels[channelName].messages, requestMessages);
|
|
152
|
+
operations[operationId].messages.push(...Object.keys(requestMessages).map(msgName => (0, utils_1.createRefObject)('channels', channelName, 'messages', msgName)));
|
|
153
|
+
}
|
|
154
|
+
// Convert responses to messages
|
|
155
|
+
if (operationObject.responses) {
|
|
156
|
+
const responseMessages = convertResponsesToMessages(operationObject.responses, operationId, method);
|
|
157
|
+
Object.assign(channels[channelName].messages, responseMessages);
|
|
158
|
+
operations[operationId].reply = {
|
|
159
|
+
channel: (0, utils_1.createRefObject)('channels', channelName),
|
|
160
|
+
messages: Object.keys(responseMessages).map(msgName => (0, utils_1.createRefObject)('channels', channelName, 'messages', msgName))
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// Add reply section if there are responses
|
|
164
|
+
if (operationObject.responses && Object.keys(operationObject.responses).length > 0) {
|
|
165
|
+
operations[operationId].reply = {
|
|
166
|
+
channel: (0, utils_1.createRefObject)('channels', channelName),
|
|
167
|
+
messages: Object.entries(operationObject.responses).map(([statusCode, response]) => (0, utils_1.createRefObject)('channels', channelName, 'messages', `${operationId}Response${statusCode}`))
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// Convert parameters
|
|
171
|
+
if (operationObject.parameters) {
|
|
172
|
+
const params = convertOperationParameters(operationObject.parameters);
|
|
173
|
+
if (Object.keys(params).length > 0) {
|
|
174
|
+
channels[channelName].parameters = Object.assign(Object.assign({}, channels[channelName].parameters), params);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
(0, utils_1.removeEmptyObjects)(channels[channelName]);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { channels, operations };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Converts OpenAPI path parameters to AsyncAPI channel parameters.
|
|
186
|
+
* @param {any[]} parameters - The OpenAPI path parameters.
|
|
187
|
+
* @returns {Record<string, any>} The converted AsyncAPI channel parameters.
|
|
188
|
+
*/
|
|
189
|
+
function convertPathParameters(path, parameters = []) {
|
|
190
|
+
var _a;
|
|
191
|
+
const convertedParams = {};
|
|
192
|
+
const paramNames = ((_a = path.match(/\{([^}]+)\}/g)) === null || _a === void 0 ? void 0 : _a.map(param => param.slice(1, -1))) || [];
|
|
193
|
+
paramNames.forEach(paramName => {
|
|
194
|
+
const param = parameters.find(p => p.name === paramName && p.in === 'path');
|
|
195
|
+
if (param) {
|
|
196
|
+
convertedParams[paramName] = convertParameter(param);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// If the parameter is not defined in the OpenAPI spec, create a default one
|
|
200
|
+
convertedParams[paramName] = {
|
|
201
|
+
description: `Path parameter ${paramName}`,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
return convertedParams;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Converts OpenAPI operatiion parameters to AsyncAPI operation parameters.
|
|
209
|
+
* @param {any[]} parameters - The OpenAPI operation parameters.
|
|
210
|
+
* @returns {Record<string, any>} The converted AsyncAPI operation parameters.
|
|
211
|
+
*/
|
|
212
|
+
function convertOperationParameters(parameters) {
|
|
213
|
+
const convertedParams = {};
|
|
214
|
+
parameters.forEach(param => {
|
|
215
|
+
if (!(0, utils_1.isRefObject)(param) && param.in === 'query') {
|
|
216
|
+
convertedParams[param.name] = convertParameter(param);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
return convertedParams;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Converts an OpenAPI Parameter Object to an AsyncAPI Parameter Object.
|
|
223
|
+
* @param {ParameterObject} param - The OpenAPI Parameter Object.
|
|
224
|
+
* @returns {any} The converted AsyncAPI Parameter Object.
|
|
225
|
+
*/
|
|
226
|
+
function convertParameter(param) {
|
|
227
|
+
const convertedParam = {
|
|
228
|
+
description: param.description,
|
|
229
|
+
};
|
|
230
|
+
if (param.required) {
|
|
231
|
+
convertedParam.required = param.required;
|
|
232
|
+
}
|
|
233
|
+
if (param.schema && !(0, utils_1.isRefObject)(param.schema)) {
|
|
234
|
+
if (param.schema.enum) {
|
|
235
|
+
convertedParam.enum = param.schema.enum;
|
|
236
|
+
}
|
|
237
|
+
if (param.schema.default !== undefined) {
|
|
238
|
+
convertedParam.default = param.schema.default;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (param.examples) {
|
|
242
|
+
convertedParam.examples = Object.values(param.examples).map((example) => (0, utils_1.isRefObject)(example) ? example : example.value);
|
|
243
|
+
}
|
|
244
|
+
// the location based on the parameter's 'in' property
|
|
245
|
+
switch (param.in) {
|
|
246
|
+
case 'query':
|
|
247
|
+
case 'header':
|
|
248
|
+
case 'cookie':
|
|
249
|
+
convertedParam.location = `$message.header#/${param.name}`;
|
|
250
|
+
break;
|
|
251
|
+
case 'path':
|
|
252
|
+
// Path parameters are part of the channel address
|
|
253
|
+
break;
|
|
254
|
+
default:
|
|
255
|
+
// If 'in' is not recognized, default to payload
|
|
256
|
+
convertedParam.location = `$message.payload#/${param.name}`;
|
|
257
|
+
}
|
|
258
|
+
return convertedParam;
|
|
259
|
+
}
|
|
260
|
+
function convertRequestBodyToMessages(requestBody, operationId, method) {
|
|
261
|
+
const messages = {};
|
|
262
|
+
if ((0, utils_1.isPlainObject)(requestBody.content)) {
|
|
263
|
+
Object.entries(requestBody.content).forEach(([contentType, mediaType]) => {
|
|
264
|
+
const messageName = `${operationId}Request`;
|
|
265
|
+
messages[messageName] = {
|
|
266
|
+
name: messageName,
|
|
267
|
+
title: `${method.toUpperCase()} request`,
|
|
268
|
+
contentType: contentType,
|
|
269
|
+
payload: convertSchema(mediaType.schema),
|
|
270
|
+
summary: requestBody.description,
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return messages;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Converts OpenAPI Response Objects to AsyncAPI Message Objects.
|
|
278
|
+
* @param {ResponsesObject} responses - The OpenAPI Response Objects to convert.
|
|
279
|
+
* @param {string} operationId - The ID of the operation these responses belong to.
|
|
280
|
+
* @param {string} method - The HTTP method of the operation.
|
|
281
|
+
* @returns {Record<string, any>} A record of converted AsyncAPI Message Objects.
|
|
282
|
+
*/
|
|
283
|
+
function convertResponsesToMessages(responses, operationId, method) {
|
|
284
|
+
const messages = {};
|
|
285
|
+
Object.entries(responses).forEach(([statusCode, response]) => {
|
|
286
|
+
if ((0, utils_1.isPlainObject)(response.content)) {
|
|
287
|
+
Object.entries(response.content).forEach(([contentType, mediaType]) => {
|
|
288
|
+
const messageName = `${operationId}Response${statusCode}`;
|
|
289
|
+
messages[messageName] = {
|
|
290
|
+
name: messageName,
|
|
291
|
+
title: `${method.toUpperCase()} response ${statusCode}`,
|
|
292
|
+
contentType: contentType,
|
|
293
|
+
payload: convertSchema(mediaType.schema),
|
|
294
|
+
summary: response.description,
|
|
295
|
+
headers: response.headers ? convertHeadersToSchema(response.headers) : undefined,
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
const messageName = `${operationId}Response${statusCode}`;
|
|
301
|
+
messages[messageName] = {
|
|
302
|
+
name: messageName,
|
|
303
|
+
title: `${method.toUpperCase()} response ${statusCode}`,
|
|
304
|
+
summary: response.description,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
return messages;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Converts OpenAPI Components Object to AsyncAPI Components Object.
|
|
312
|
+
* @param {OpenAPIDocument} openapi - The complete OpenAPI document.
|
|
313
|
+
* @returns {AsyncAPIDocument['components']} The converted AsyncAPI Components Object.
|
|
314
|
+
*/
|
|
315
|
+
function convertComponents(openapi) {
|
|
316
|
+
const asyncComponents = {};
|
|
317
|
+
if (openapi.components) {
|
|
318
|
+
if (openapi.components.schemas) {
|
|
319
|
+
asyncComponents.schemas = convertSchemas(openapi.components.schemas);
|
|
320
|
+
}
|
|
321
|
+
if (openapi.components.securitySchemes) {
|
|
322
|
+
asyncComponents.securitySchemes = convertSecuritySchemes(openapi.components.securitySchemes);
|
|
323
|
+
}
|
|
324
|
+
if (openapi.components.parameters) {
|
|
325
|
+
asyncComponents.parameters = {};
|
|
326
|
+
for (const [name, param] of Object.entries(openapi.components.parameters)) {
|
|
327
|
+
if (!(0, utils_1.isRefObject)(param)) {
|
|
328
|
+
asyncComponents.parameters[name] = convertParameter(param);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
asyncComponents.parameters[name] = param;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (openapi.components.responses) {
|
|
336
|
+
asyncComponents.messages = convertComponentResponsesToMessages(openapi.components.responses);
|
|
337
|
+
}
|
|
338
|
+
if (openapi.components.requestBodies) {
|
|
339
|
+
asyncComponents.messageTraits = convertRequestBodiesToMessageTraits(openapi.components.requestBodies);
|
|
340
|
+
}
|
|
341
|
+
if (openapi.components.headers) {
|
|
342
|
+
asyncComponents.messageTraits = Object.assign(Object.assign({}, (asyncComponents.messageTraits || {})), convertHeadersToMessageTraits(openapi.components.headers));
|
|
343
|
+
}
|
|
344
|
+
if (openapi.components.examples) {
|
|
345
|
+
asyncComponents.examples = openapi.components.examples;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return (0, utils_1.removeEmptyObjects)(asyncComponents);
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* converts openAPI schema object to multiformat/schema object
|
|
352
|
+
* @param schema openAPI schema object
|
|
353
|
+
* @returns multiformat/schema object
|
|
354
|
+
*/
|
|
355
|
+
function convertSchema(schema) {
|
|
356
|
+
if ((0, utils_1.isRefObject)(schema)) {
|
|
357
|
+
// Check if it's an external reference
|
|
358
|
+
if (schema.$ref.startsWith('./') || schema.$ref.startsWith('http')) {
|
|
359
|
+
// Convert external references to multi-format schema objects
|
|
360
|
+
return {
|
|
361
|
+
schemaFormat: 'application/vnd.oai.openapi;version=3.0.0',
|
|
362
|
+
schema: schema
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return schema;
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
schemaFormat: 'application/vnd.oai.openapi;version=3.0.0',
|
|
369
|
+
schema: schema
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Converts OpenAPI Schema Objects to AsyncAPI Schema Objects.
|
|
374
|
+
* @param {Record<string, any>} schemas - The OpenAPI Schema Objects to convert.
|
|
375
|
+
* @returns {Record<string, any>} The converted AsyncAPI Schema Objects.
|
|
376
|
+
*/
|
|
377
|
+
function convertSchemas(schemas) {
|
|
378
|
+
const convertedSchemas = {};
|
|
379
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
380
|
+
convertedSchemas[name] = convertSchema(schema);
|
|
381
|
+
}
|
|
382
|
+
return convertedSchemas;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Converts a single OpenAPI Security Scheme Object to an AsyncAPI Security Scheme Object.
|
|
386
|
+
* @param {Record<string, any>} scheme - The OpenAPI Security Scheme Object to convert.
|
|
387
|
+
* @returns {Record<string, any>} The converted AsyncAPI Security Scheme Object.
|
|
388
|
+
*/
|
|
389
|
+
function convertSecuritySchemes(securitySchemes) {
|
|
390
|
+
const convertedSchemes = {};
|
|
391
|
+
for (const [name, scheme] of Object.entries(securitySchemes)) {
|
|
392
|
+
convertedSchemes[name] = convertSecurityScheme(scheme);
|
|
393
|
+
}
|
|
394
|
+
return convertedSchemes;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Converts a single OpenAPI Security Scheme Object to an AsyncAPI Security Scheme Object.
|
|
398
|
+
* @param {any} scheme - The OpenAPI Security Scheme Object to convert.
|
|
399
|
+
* @returns {Record<string, any>} The converted AsyncAPI Security Scheme Object.
|
|
400
|
+
*/
|
|
401
|
+
function convertSecurityScheme(scheme) {
|
|
402
|
+
const convertedScheme = {
|
|
403
|
+
type: scheme.type,
|
|
404
|
+
description: scheme.description
|
|
405
|
+
};
|
|
406
|
+
if (scheme.type === 'oauth2' && scheme.flows) {
|
|
407
|
+
const newFlows = JSON.parse(JSON.stringify(scheme.flows));
|
|
408
|
+
function convertScopesToAvailableScopes(obj) {
|
|
409
|
+
for (const key in obj) {
|
|
410
|
+
if (obj.hasOwnProperty(key)) {
|
|
411
|
+
if (key === 'scopes') {
|
|
412
|
+
obj['availableScopes'] = obj[key];
|
|
413
|
+
delete obj[key];
|
|
414
|
+
}
|
|
415
|
+
else if (typeof obj[key] === 'object') {
|
|
416
|
+
convertScopesToAvailableScopes(obj[key]);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
convertScopesToAvailableScopes(newFlows);
|
|
422
|
+
convertedScheme.flows = newFlows;
|
|
423
|
+
if (scheme.scopes) {
|
|
424
|
+
convertedScheme.scopes = Object.keys(scheme.scopes);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
else if (scheme.type === 'http') {
|
|
428
|
+
convertedScheme.scheme = scheme.scheme;
|
|
429
|
+
if (scheme.scheme === 'bearer') {
|
|
430
|
+
convertedScheme.bearerFormat = scheme.bearerFormat;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
else if (scheme.type === 'apiKey') {
|
|
434
|
+
convertedScheme.in = scheme.in;
|
|
435
|
+
convertedScheme.name = scheme.name;
|
|
436
|
+
}
|
|
437
|
+
return convertedScheme;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Converts OpenAPI Response Objects from the components section to AsyncAPI Message Objects.
|
|
441
|
+
* @param {Record<string, any>} responses - The OpenAPI Response Objects to convert.
|
|
442
|
+
* @returns {Record<string, any>} A record of converted AsyncAPI Message Objects.
|
|
443
|
+
*/
|
|
444
|
+
function convertComponentResponsesToMessages(responses) {
|
|
445
|
+
const messages = {};
|
|
446
|
+
for (const [name, response] of Object.entries(responses)) {
|
|
447
|
+
if ((0, utils_1.isPlainObject)(response.content)) {
|
|
448
|
+
Object.entries(response.content).forEach(([contentType, mediaType]) => {
|
|
449
|
+
messages[name] = {
|
|
450
|
+
name: name,
|
|
451
|
+
contentType: contentType,
|
|
452
|
+
payload: convertSchema(mediaType.schema),
|
|
453
|
+
summary: response.description,
|
|
454
|
+
headers: response.headers ? convertHeadersToSchema(response.headers) : undefined,
|
|
455
|
+
};
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
messages[name] = {
|
|
460
|
+
name: name,
|
|
461
|
+
summary: response.description,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return messages;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Converts OpenAPI Request Body Objects from the components section to AsyncAPI Message Trait Objects.
|
|
469
|
+
* @param {Record<string, any>} requestBodies - The OpenAPI Request Body Objects to convert.
|
|
470
|
+
* @returns {Record<string, any>} A record of converted AsyncAPI Message Trait Objects.
|
|
471
|
+
*/
|
|
472
|
+
function convertRequestBodiesToMessageTraits(requestBodies) {
|
|
473
|
+
const messageTraits = {};
|
|
474
|
+
for (const [name, requestBodyOrRef] of Object.entries(requestBodies)) {
|
|
475
|
+
if (!(0, utils_1.isRefObject)(requestBodyOrRef) && requestBodyOrRef.content) {
|
|
476
|
+
const contentType = Object.keys(requestBodyOrRef.content)[0];
|
|
477
|
+
messageTraits[name] = {
|
|
478
|
+
name: name,
|
|
479
|
+
contentType: contentType,
|
|
480
|
+
description: requestBodyOrRef.description,
|
|
481
|
+
};
|
|
482
|
+
if (requestBodyOrRef.content[contentType].schema &&
|
|
483
|
+
requestBodyOrRef.content[contentType].schema.properties &&
|
|
484
|
+
requestBodyOrRef.content[contentType].schema.properties.headers) {
|
|
485
|
+
messageTraits[name].headers = requestBodyOrRef.content[contentType].schema.properties.headers;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return messageTraits;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Converts OpenAPI Header Objects from the components section to AsyncAPI Message Trait Objects.
|
|
493
|
+
* @param {Record<string, any>} headers - The OpenAPI Header Objects to convert.
|
|
494
|
+
* @returns {Record<string, any>} A record of converted AsyncAPI Message Trait Objects.
|
|
495
|
+
*/
|
|
496
|
+
function convertHeadersToMessageTraits(headers) {
|
|
497
|
+
const messageTraits = {};
|
|
498
|
+
for (const [name, header] of Object.entries(headers)) {
|
|
499
|
+
messageTraits[`Header${name}`] = {
|
|
500
|
+
headers: {
|
|
501
|
+
type: 'object',
|
|
502
|
+
properties: {
|
|
503
|
+
[name]: header.schema,
|
|
504
|
+
},
|
|
505
|
+
required: [name],
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
return messageTraits;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Converts OpenAPI Header Objects to an AsyncAPI Schema Object representing the headers.
|
|
513
|
+
* @param {Record<string, any>} headers - The OpenAPI Header Objects to convert.
|
|
514
|
+
* @returns {SchemaObject} An AsyncAPI Schema Object representing the headers.
|
|
515
|
+
*/
|
|
516
|
+
function convertHeadersToSchema(headers) {
|
|
517
|
+
const properties = {};
|
|
518
|
+
for (const [name, headerOrRef] of Object.entries(headers)) {
|
|
519
|
+
if (!(0, utils_1.isRefObject)(headerOrRef)) {
|
|
520
|
+
properties[name] = headerOrRef.schema || {};
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
type: 'object',
|
|
525
|
+
properties,
|
|
526
|
+
};
|
|
527
|
+
}
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { AsyncAPIDocument } from "./interfaces";
|
|
2
|
-
export declare function serializeInput(document: string | AsyncAPIDocument): {
|
|
1
|
+
import type { AsyncAPIDocument, OpenAPIDocument } from "./interfaces";
|
|
2
|
+
export declare function serializeInput(document: string | AsyncAPIDocument | OpenAPIDocument): {
|
|
3
3
|
format: 'json' | 'yaml';
|
|
4
|
-
document: AsyncAPIDocument;
|
|
4
|
+
document: AsyncAPIDocument | OpenAPIDocument;
|
|
5
5
|
} | never;
|
|
6
6
|
export declare function objectToSchema(obj: Record<string, unknown>): {
|
|
7
7
|
type: string;
|
|
@@ -20,3 +20,4 @@ export declare function isRemoteRef(value: any): boolean;
|
|
|
20
20
|
export declare function getValueByRef(root: any, ref: string): any;
|
|
21
21
|
export declare function getValueByPath(value: any, path: string[]): any;
|
|
22
22
|
export declare function sortObjectKeys(obj: any, keys: string[]): any;
|
|
23
|
+
export declare function removeEmptyObjects(obj: Record<string, any>): Record<string, any>;
|
package/lib/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sortObjectKeys = exports.getValueByPath = exports.getValueByRef = exports.isRemoteRef = exports.isRefObject = exports.createRefPath = exports.createRefObject = exports.isPlainObject = exports.dotsToSlashes = exports.objectToSchema = exports.serializeInput = void 0;
|
|
3
|
+
exports.removeEmptyObjects = exports.sortObjectKeys = exports.getValueByPath = exports.getValueByRef = exports.isRemoteRef = exports.isRefObject = exports.createRefPath = exports.createRefObject = exports.isPlainObject = exports.dotsToSlashes = exports.objectToSchema = exports.serializeInput = void 0;
|
|
4
4
|
const js_yaml_1 = require("js-yaml");
|
|
5
5
|
function serializeInput(document) {
|
|
6
6
|
let triedConvertToYaml = false;
|
|
@@ -13,10 +13,18 @@ function serializeInput(document) {
|
|
|
13
13
|
}
|
|
14
14
|
const maybeJSON = JSON.parse(document);
|
|
15
15
|
if (typeof maybeJSON === 'object') {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
if ('openapi' in maybeJSON) {
|
|
17
|
+
return {
|
|
18
|
+
format: 'json',
|
|
19
|
+
document: maybeJSON,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
return {
|
|
24
|
+
format: 'json',
|
|
25
|
+
document: maybeJSON,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
20
28
|
}
|
|
21
29
|
triedConvertToYaml = true; // NOSONAR
|
|
22
30
|
// if `maybeJSON` is object, then we have 100% sure that we operate on JSON,
|
|
@@ -128,3 +136,18 @@ function untilde(str) {
|
|
|
128
136
|
return sub;
|
|
129
137
|
});
|
|
130
138
|
}
|
|
139
|
+
function removeEmptyObjects(obj) {
|
|
140
|
+
Object.keys(obj).forEach(key => {
|
|
141
|
+
if (obj[key] && typeof obj[key] === 'object') {
|
|
142
|
+
removeEmptyObjects(obj[key]);
|
|
143
|
+
if (Object.keys(obj[key]).length === 0) {
|
|
144
|
+
delete obj[key];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (obj[key] === undefined) {
|
|
148
|
+
delete obj[key];
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return obj;
|
|
152
|
+
}
|
|
153
|
+
exports.removeEmptyObjects = removeEmptyObjects;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asyncapi/converter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.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",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"author": "Fran Mendez <fmvilas@gmail.com> (fmvilas.com)",
|
|
39
39
|
"license": "Apache-2.0",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@asyncapi/parser": "^3.0
|
|
41
|
+
"@asyncapi/parser": "^3.1.0",
|
|
42
42
|
"js-yaml": "^3.14.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|