Skip to content

Commit ec996f4

Browse files
Gmin2asyncapi-bot
andauthored
feat: support for postman-collection conversion to asyncapi (#1527)
Co-authored-by: Ashish Padhy <[email protected]>%0ACo-authored-by: asyncapi-bot <[email protected]>
1 parent 8f7b019 commit ec996f4

File tree

5 files changed

+185
-43
lines changed

5 files changed

+185
-43
lines changed

docs/usage.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ _See code: [src/commands/config/versions.ts](https://github.com/asyncapi/cli/blo
308308

309309
## `asyncapi convert [SPEC-FILE]`
310310

311-
Convert asyncapi documents older to newer versions or OpenAPI documents to AsyncAPI
311+
Convert asyncapi documents older to newer versions or OpenAPI | postman-collection documents to AsyncAPI
312312

313313
```
314314
USAGE
@@ -318,12 +318,12 @@ ARGUMENTS
318318
SPEC-FILE spec path, url, or context-name
319319
320320
FLAGS
321-
-f, --format=<option> (required) [default: asyncapi] Specify the format to convert from (openapi or asyncapi)
322-
<options: openapi|asyncapi>
321+
-f, --format=<option> (required) [default: asyncapi] Specify the format to convert from (openapi or asyncapi or postman-collection)
322+
<options: openapi|asyncapi|postman-collection>
323323
-h, --help Show CLI help.
324324
-o, --output=<value> path to the file where the result is saved
325325
-p, --perspective=<option> [default: server] Perspective to use when converting OpenAPI to AsyncAPI (client or
326-
server). Note: This option is only applicable for OpenAPI to AsyncAPI conversions.
326+
server). Note: This option is only applicable for OpenAPI | postman-collection to AsyncAPI conversions.
327327
<options: client|server>
328328
-t, --target-version=<value> [default: 3.0.0] asyncapi version to convert to
329329

src/commands/convert.ts

+79-38
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Command from '../core/base';
55
import { ValidationError } from '../core/errors/validation-error';
66
import { load } from '../core/models/SpecificationFile';
77
import { SpecificationFileNotFound } from '../core/errors/specification-file';
8-
import { convert, convertOpenAPI } from '@asyncapi/converter';
8+
import { convert, convertOpenAPI, convertPostman } from '@asyncapi/converter';
99
import type { AsyncAPIConvertVersion, OpenAPIConvertVersion } from '@asyncapi/converter';
1010
import { cyan, green } from 'picocolors';
1111

@@ -16,7 +16,9 @@ import { convertFlags } from '../core/flags/convert.flags';
1616
const latestVersion = Object.keys(specs.schemas).pop() as string;
1717

1818
export default class Convert extends Command {
19-
static description = 'Convert asyncapi documents older to newer versions or OpenAPI documents to AsyncAPI';
19+
static specFile: any;
20+
static metricsMetadata: any = {};
21+
static description = 'Convert asyncapi documents older to newer versions or OpenAPI/postman-collection documents to AsyncAPI';
2022

2123
static flags = convertFlags(latestVersion);
2224

@@ -37,47 +39,86 @@ export default class Convert extends Command {
3739
this.metricsMetadata.to_version = flags['target-version'];
3840

3941
// Determine if the input is OpenAPI or AsyncAPI
40-
const specJson = this.specFile.toJson();
4142
const isOpenAPI = flags['format'] === 'openapi';
4243
const isAsyncAPI = flags['format'] === 'asyncapi';
4344

4445
// CONVERSION
45-
if (isOpenAPI) {
46-
convertedFile = convertOpenAPI(this.specFile.text(), specJson.openapi as OpenAPIConvertVersion, {
47-
perspective: flags['perspective'] as 'client' | 'server'
48-
});
49-
this.log(`🎉 The OpenAPI document has been successfully converted to AsyncAPI version ${green(flags['target-version'])}!`);
50-
} else if (isAsyncAPI) {
51-
convertedFile = convert(this.specFile.text(), flags['target-version'] as AsyncAPIConvertVersion);
52-
if (this.specFile.getFilePath()) {
53-
this.log(`🎉 The ${cyan(this.specFile.getFilePath())} file has been successfully converted to version ${green(flags['target-version'])}!!`);
54-
} else if (this.specFile.getFileURL()) {
55-
this.log(`🎉 The URL ${cyan(this.specFile.getFileURL())} has been successfully converted to version ${green(flags['target-version'])}!!`);
56-
}
57-
}
58-
59-
if (typeof convertedFile === 'object') {
60-
convertedFileFormatted = JSON.stringify(convertedFile, null, 4);
61-
} else {
62-
convertedFileFormatted = convertedFile;
63-
}
64-
65-
if (flags.output) {
66-
await fPromises.writeFile(`${flags.output}`, convertedFileFormatted, { encoding: 'utf8' });
67-
} else {
68-
this.log(convertedFileFormatted);
69-
}
46+
convertedFile = this.handleConversion(isOpenAPI, isAsyncAPI, flags);
47+
48+
// Handle file output or log the result
49+
convertedFileFormatted = this.formatConvertedFile(convertedFile);
50+
await this.handleOutput(flags.output, convertedFileFormatted);
7051
} catch (err) {
71-
if (err instanceof SpecificationFileNotFound) {
72-
this.error(new ValidationError({
73-
type: 'invalid-file',
74-
filepath: filePath
75-
}));
76-
} else if (this.specFile?.toJson().asyncapi > flags['target-version']) {
77-
this.error(`The ${cyan(filePath)} file cannot be converted to an older version. Downgrading is not supported.`);
78-
} else {
79-
this.error(err as Error);
80-
}
52+
this.handleError(err, filePath ?? 'unknown', flags);
53+
}
54+
}
55+
56+
// Helper function to handle conversion logic
57+
private handleConversion(isOpenAPI: boolean, isAsyncAPI: boolean, flags: any) {
58+
const specJson = this.specFile?.toJson();
59+
if (isOpenAPI) {
60+
return this.convertOpenAPI(specJson, flags);
61+
} else if (isAsyncAPI) {
62+
return this.convertAsyncAPI(flags);
63+
}
64+
return this.convertPostman(flags);
65+
}
66+
67+
private convertOpenAPI(specJson: any, flags: any) {
68+
const convertedFile = convertOpenAPI(this.specFile?.text() ?? '', specJson.openapi as OpenAPIConvertVersion, {
69+
perspective: flags['perspective'] as 'client' | 'server'
70+
});
71+
this.log(`🎉 The OpenAPI document has been successfully converted to AsyncAPI version ${green(flags['target-version'])}!`);
72+
return convertedFile;
73+
}
74+
75+
private convertAsyncAPI(flags: any) {
76+
const convertedFile = convert(this.specFile?.text() ?? '', flags['target-version'] as AsyncAPIConvertVersion);
77+
if (this.specFile?.getFilePath()) {
78+
this.log(`🎉 The ${cyan(this.specFile?.getFilePath())} file has been successfully converted to version ${green(flags['target-version'])}!!`);
79+
} else if (this.specFile?.getFileURL()) {
80+
this.log(`🎉 The URL ${cyan(this.specFile?.getFileURL())} has been successfully converted to version ${green(flags['target-version'])}!!`);
81+
}
82+
return convertedFile;
83+
}
84+
85+
private convertPostman(flags: any) {
86+
const convertedFile = convertPostman(this.specFile?.text() ?? '', '3.0.0', {
87+
perspective: flags['perspective'] as 'client' | 'server'
88+
});
89+
if (this.specFile?.getFilePath()) {
90+
this.log(`🎉 The ${cyan(this.specFile?.getFilePath())} file has been successfully converted to asyncapi of version ${green(flags['target-version'])}!!`);
91+
} else if (this.specFile?.getFileURL()) {
92+
this.log(`🎉 The URL ${cyan(this.specFile?.getFileURL())} has been successfully converted to asyncapi of version ${green(flags['target-version'])}!!`);
93+
}
94+
return convertedFile;
95+
}
96+
97+
// Helper function to format the converted file
98+
private formatConvertedFile(convertedFile: any) {
99+
return typeof convertedFile === 'object' ? JSON.stringify(convertedFile, null, 4) : convertedFile;
100+
}
101+
102+
// Helper function to handle output
103+
private async handleOutput(outputPath: string | undefined, convertedFileFormatted: string) {
104+
if (outputPath) {
105+
await fPromises.writeFile(`${outputPath}`, convertedFileFormatted, { encoding: 'utf8' });
106+
} else {
107+
this.log(convertedFileFormatted);
108+
}
109+
}
110+
111+
// Helper function to handle errors
112+
private handleError(err: any, filePath: string, flags: any) {
113+
if (err instanceof SpecificationFileNotFound) {
114+
this.error(new ValidationError({
115+
type: 'invalid-file',
116+
filepath: filePath
117+
}));
118+
} else if (this.specFile?.toJson().asyncapi > flags['target-version']) {
119+
this.error(`The ${cyan(filePath)} file cannot be converted to an older version. Downgrading is not supported.`);
120+
} else {
121+
this.error(err as Error);
81122
}
82123
}
83124
}

src/core/flags/convert.flags.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const convertFlags = (latestVersion: string) => {
77
format: Flags.string({
88
char: 'f',
99
description: 'Specify the format to convert from (openapi or asyncapi)',
10-
options: ['openapi', 'asyncapi'],
10+
options: ['openapi', 'asyncapi', 'postman-collection'],
1111
required: true,
1212
default: 'asyncapi',
1313
}),

test/fixtures/postman-collection.yml

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
info:
2+
name: Sample Postman Collection
3+
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
4+
item:
5+
- name: Sample Request
6+
request:
7+
method: GET
8+
header: []
9+
url:
10+
raw: 'https://jsonplaceholder.typicode.com/posts/1'
11+
protocol: https
12+
host:
13+
- jsonplaceholder
14+
- typicode
15+
- com
16+
path:
17+
- posts
18+
- '1'
19+
response: []
20+
- name: Sample POST Request
21+
request:
22+
method: POST
23+
header:
24+
- key: Content-Type
25+
value: application/json
26+
body:
27+
mode: raw
28+
raw: '{ "title": "foo", "body": "bar", "userId": 1 }'
29+
url:
30+
raw: 'https://jsonplaceholder.typicode.com/posts'
31+
protocol: https
32+
host:
33+
- jsonplaceholder
34+
- typicode
35+
- com
36+
path:
37+
- posts
38+
response: []

test/integration/convert.test.ts

+63
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const testHelper = new TestHelper();
99
const filePath = './test/fixtures/specification.yml';
1010
const JSONFilePath = './test/fixtures/specification.json';
1111
const openAPIFilePath = './test/fixtures/openapi.yml';
12+
const postmanFilePath = './test/fixtures/postman-collection.yml';
1213

1314
describe('convert', () => {
1415
describe('with file paths', () => {
@@ -241,4 +242,66 @@ describe('convert', () => {
241242
done();
242243
});
243244
});
245+
246+
describe('with Postman input', () => {
247+
beforeEach(() => {
248+
testHelper.createDummyContextFile();
249+
});
250+
251+
afterEach(() => {
252+
testHelper.deleteDummyContextFile();
253+
});
254+
255+
test
256+
.stderr()
257+
.stdout()
258+
.command(['convert', postmanFilePath, '-f', 'postman-collection'])
259+
.it('works when Postman file path is passed', (ctx, done) => {
260+
expect(ctx.stdout).to.contain(`🎉 The ${postmanFilePath} file has been successfully converted to asyncapi of version 3.0.0!!`);
261+
expect(ctx.stderr).to.equal('');
262+
done();
263+
});
264+
265+
test
266+
.stderr()
267+
.stdout()
268+
.command(['convert', postmanFilePath, '-f', 'postman-collection', '-p=client'])
269+
.it('works when Postman file path is passed with client perspective', (ctx, done) => {
270+
expect(ctx.stdout).to.contain(`🎉 The ${postmanFilePath} file has been successfully converted to asyncapi of version 3.0.0!!`);
271+
expect(ctx.stderr).to.equal('');
272+
done();
273+
});
274+
275+
test
276+
.stderr()
277+
.stdout()
278+
.command(['convert', postmanFilePath, '-f', 'postman-collection', '-p=server'])
279+
.it('works when Postman file path is passed with server perspective', (ctx, done) => {
280+
expect(ctx.stdout).to.contain(`🎉 The ${postmanFilePath} file has been successfully converted to asyncapi of version 3.0.0!!`);
281+
expect(ctx.stderr).to.equal('');
282+
done();
283+
});
284+
285+
test
286+
.stderr()
287+
.stdout()
288+
.command(['convert', postmanFilePath, '-f', 'postman-collection', '-p=invalid'])
289+
.it('should throw error if invalid perspective is passed', (ctx, done) => {
290+
expect(ctx.stdout).to.equal('');
291+
expect(ctx.stderr).to.contain('Error: Expected --perspective=invalid to be one of: client, server');
292+
done();
293+
});
294+
295+
test
296+
.stderr()
297+
.stdout()
298+
.command(['convert', postmanFilePath, '-f', 'postman-collection', '-o=./test/fixtures/postman_converted_output.yml'])
299+
.it('works when Postman file is converted and output is saved', (ctx, done) => {
300+
expect(ctx.stdout).to.contain(`🎉 The ${postmanFilePath} file has been successfully converted to asyncapi of version 3.0.0!!`);
301+
expect(fs.existsSync('./test/fixtures/postman_converted_output.yml')).to.equal(true);
302+
expect(ctx.stderr).to.equal('');
303+
fs.unlinkSync('./test/fixtures/postman_converted_output.yml');
304+
done();
305+
});
306+
});
244307
});

0 commit comments

Comments
 (0)