Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: completed the conversion from postman to asyncapi #276

Merged
merged 12 commits into from
Sep 8, 2024
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,48 @@ The perspective option can be set to either 'server' (default) or 'client'.

- 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.

### Postman Collection to AsyncAPI conversion

The converter now also supports conversion from postman collection to AsyncAPI 3.0. This feature enables easy transition of existing postman collection to any AsyncAPI 3.0 documents.

To use this new conversion feature:

```js
const fs = require('fs');
const { convertPostman } = require('@asyncapi/converter')
try {
const postman = fs.readFileSync('postman-collection.yml', 'utf-8')
const asyncapi = convertPostman(postman, '3.0.0');
console.log(asyncapi);
} catch (e) {
console.error(e);
}
```

When converting from postman collection 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

```js
const { convertPostman } = require('@asyncapi/converter')
try {
const postman = fs.readFileSync('postman-collection.yml', 'utf-8')
const asyncapi = convertPostman(postman, '3.0.0', { perspective: 'client' });
console.log(asyncapi);
} catch (e) {
console.error(e);
}
```

The perspective option can be set to either 'server' (default) or 'client'.

- With `server` perspective: `action` becomes `receive`

- With `client` perspective: `action` becomes `send`

#### Limitations

- 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.


## Development

1. Setup project by installing dependencies `npm install`
Expand Down
39 changes: 38 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
"license": "Apache-2.0",
"dependencies": {
"@asyncapi/parser": "^3.1.0",
"js-yaml": "^3.14.1"
"js-yaml": "^3.14.1",
"path": "^0.12.7",
"postman2openapi": "^1.2.1"
},
"devDependencies": {
"@jest/types": "^27.5.1",
Expand Down
17 changes: 16 additions & 1 deletion src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { converters as firstConverters } from "./first-version";
import { converters as secondConverters } from "./second-version";
import { converters as thirdConverters } from "./third-version";
import { converters as openapiConverters } from "./openapi";
import { converters as postmanConverters } from "./postman-collection";

import { serializeInput } from "./utils";

import type { AsyncAPIDocument, AsyncAPIConvertVersion, OpenAPIConvertVersion, ConvertOptions, ConvertFunction, ConvertOpenAPIFunction, OpenAPIDocument, OpenAPIToAsyncAPIOptions } from './interfaces';
import type { AsyncAPIDocument, AsyncAPIConvertVersion, OpenAPIConvertVersion, ConvertOptions, ConvertFunction, ConvertOpenAPIFunction, OpenAPIDocument, OpenAPIToAsyncAPIOptions, PostmanToAsyncAPIOptions } from './interfaces';

/**
* Value for key (version) represents the function which converts specification from previous version to the given as key.
Expand Down Expand Up @@ -78,3 +79,17 @@ export function convertOpenAPI(input: string | OpenAPIDocument, version: OpenAPI
}
return convertedAsyncAPI;
}

export function convertPostman(input: string, version: OpenAPIConvertVersion, options?: PostmanToAsyncAPIOptions ): string;
export function convertPostman(input: Record<string,any>, version: OpenAPIConvertVersion, options?: PostmanToAsyncAPIOptions): AsyncAPIDocument;
export function convertPostman(input: string | Record<string,any>, version: OpenAPIConvertVersion, options: PostmanToAsyncAPIOptions={}): string | AsyncAPIDocument {
const { format, document } = serializeInput(input);
const postmantoAsyncapiConverter = postmanConverters[version];

const convertedAsyncAPI = postmantoAsyncapiConverter(document as any, options);

if (format === "yaml") {
return dump(convertedAsyncAPI, { skipInvalid: true });
}
return convertedAsyncAPI;
}
7 changes: 6 additions & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ export type OpenAPIToAsyncAPIOptions = {
export type ConvertOptions = {
v2tov3?: ConvertV2ToV3Options;
openAPIToAsyncAPI?: OpenAPIToAsyncAPIOptions;
postmanToAsyncAPI?: PostmanToAsyncAPIOptions;
}

export type PostmanToAsyncAPIOptions = {
perspective?: 'client' | 'server';
}

/**
* PRIVATE TYPES
*/
export type ConvertFunction = (asyncapi: AsyncAPIDocument, options: ConvertOptions) => AsyncAPIDocument;
export type ConvertOpenAPIFunction = (openapi: OpenAPIDocument, options: OpenAPIToAsyncAPIOptions) => AsyncAPIDocument;

export type ConvertPostmanFunction = (postman: any , options: PostmanToAsyncAPIOptions) => AsyncAPIDocument;
2 changes: 1 addition & 1 deletion src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const converters: Record<string, ConvertOpenAPIFunction > = {
* @param {ConvertOptions} options - Conversion options.
* @returns {AsyncAPIDocument} The converted AsyncAPI document.
*/
function from_openapi_to_asyncapi(openapi: OpenAPIDocument, options: OpenAPIToAsyncAPIOptions = {}): AsyncAPIDocument {
export function from_openapi_to_asyncapi(openapi: OpenAPIDocument, options: OpenAPIToAsyncAPIOptions = {}): AsyncAPIDocument {
const perspective = options.perspective || 'server';
const asyncapi: Partial<AsyncAPIDocument> = {
asyncapi: '3.0.0',
Expand Down
14 changes: 14 additions & 0 deletions src/postman-collection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { transpile } from 'postman2openapi';
import { from_openapi_to_asyncapi } from './openapi';
import { ConvertPostmanFunction, PostmanToAsyncAPIOptions } from 'interfaces';

export const converters: Record<string, ConvertPostmanFunction > = {
'3.0.0': from_postman_to_asyncapi
}

function from_postman_to_asyncapi(postman: any ,options:PostmanToAsyncAPIOptions ={}) {
const perspective = options.perspective;
const openapi = transpile(postman);
const asyncapi = from_openapi_to_asyncapi(openapi , {perspective:perspective});
return asyncapi;
}
2 changes: 1 addition & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { load } from 'js-yaml';

import type { AsyncAPIDocument, OpenAPIDocument } from "./interfaces";

export function serializeInput(document: string | AsyncAPIDocument | OpenAPIDocument): { format: 'json' | 'yaml', document: AsyncAPIDocument | OpenAPIDocument } | never {
export function serializeInput(document: string | AsyncAPIDocument | OpenAPIDocument | Record<string,any>): { format: 'json' | 'yaml', document: AsyncAPIDocument | OpenAPIDocument } | never {
let triedConvertToYaml = false;
try {
if (typeof document === 'object') {
Expand Down
38 changes: 38 additions & 0 deletions test/input/postman/basic-collection.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
info:
name: Sample Postman Collection
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
item:
- name: Sample Request
request:
method: GET
header: []
url:
raw: 'https://jsonplaceholder.typicode.com/posts/1'
protocol: https
host:
- jsonplaceholder
- typicode
- com
path:
- posts
- '1'
response: []
- name: Sample POST Request
request:
method: POST
header:
- key: Content-Type
value: application/json
body:
mode: raw
raw: '{ "title": "foo", "body": "bar", "userId": 1 }'
url:
raw: 'https://jsonplaceholder.typicode.com/posts'
protocol: https
host:
- jsonplaceholder
- typicode
- com
path:
- posts
response: []
22 changes: 22 additions & 0 deletions test/input/postman/header-authentication.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
info:
name: Headers and Authentication Test Collection
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
item:
- name: GET Request with Headers and Authentication
request:
method: GET
header:
- key: Authorization
value: Bearer your_token
- key: Accept
value: application/json
url:
raw: 'https://example.com/api/authenticated'
protocol: https
host:
- example
- com
path:
- api
- authenticated
response: []
72 changes: 72 additions & 0 deletions test/output/postman-to-asyncapi/basic-collection.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
asyncapi: 3.0.0
info:
title: Sample Postman Collection
version: 1.0.0
servers:
typicode_com:
host: jsonplaceholder.typicode.com
protocol: https
channels:
posts_1:
address: /posts/1
messages:
sampleRequestResponse200:
name: sampleRequestResponse200
title: GET response 200
summary: ''
posts:
address: /posts
messages:
samplePostRequestRequest:
name: samplePostRequestRequest
title: POST request
contentType: application/json
payload:
schemaFormat: application/vnd.oai.openapi;version=3.0.0
schema:
type: object
properties:
body:
type: string
example: bar
title:
type: string
example: foo
userId:
type: number
example: 1
samplePostRequestResponse200:
name: samplePostRequestResponse200
title: POST response 200
summary: ''
operations:
sampleRequest:
action: receive
channel:
$ref: '#/channels/posts_1'
summary: Sample Request
description: Sample Request
bindings:
http:
method: GET
reply:
channel:
$ref: '#/channels/posts_1'
messages:
- $ref: '#/channels/posts_1/messages/sampleRequestResponse200'
samplePostRequest:
action: receive
channel:
$ref: '#/channels/posts'
summary: Sample POST Request
description: Sample POST Request
bindings:
http:
method: POST
messages:
- $ref: '#/channels/posts/messages/samplePostRequestRequest'
reply:
channel:
$ref: '#/channels/posts'
messages:
- $ref: '#/channels/posts/messages/samplePostRequestResponse200'
32 changes: 32 additions & 0 deletions test/output/postman-to-asyncapi/header-authentication.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
asyncapi: 3.0.0
info:
title: Headers and Authentication Test Collection
version: 1.0.0
servers:
example_com:
host: example.com
protocol: https
channels:
api_authenticated:
address: /api/authenticated
messages:
getRequestWithHeadersAndAuthenticationResponse200:
name: getRequestWithHeadersAndAuthenticationResponse200
title: GET response 200
summary: ''
operations:
getRequestWithHeadersAndAuthentication:
action: receive
channel:
$ref: '#/channels/api_authenticated'
summary: GET Request with Headers and Authentication
description: GET Request with Headers and Authentication
bindings:
http:
method: GET
reply:
channel:
$ref: '#/channels/api_authenticated'
messages:
- $ref: >-
#/channels/api_authenticated/messages/getRequestWithHeadersAndAuthenticationResponse200
Loading
Loading