From aff566b27b3d87884dd66d2aac7aa030aff2da1c Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 25 Aug 2020 12:47:05 +0200 Subject: [PATCH 1/5] fix: should support root paths #63 Close #63 --- lib/index.js | 4 +- test/index.spec.js | 7 +++ test/resources/input/NoPath.json | 77 ++++++++++++++++++++++++++++++++ test/resources/output/NoPath.yml | 53 ++++++++++++++++++++++ 4 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 test/resources/input/NoPath.json create mode 100644 test/resources/output/NoPath.yml diff --git a/lib/index.js b/lib/index.js index ec36fbc..4de18bb 100644 --- a/lib/index.js +++ b/lib/index.js @@ -49,7 +49,7 @@ async function postmanToOpenApi (input, output, { info = {}, defaultTag = 'defau paths } - const openApiYml = safeDump(openApi) + const openApiYml = safeDump(openApi, { skipInvalid: true }) if (output != null) { await writeFile(output, openApiYml, 'utf8') } @@ -239,7 +239,7 @@ function parseOptsAuth (optAuth) { } /* From the path array compose the real path for OpenApi specs */ -function calculatePath (paths, pathDepth) { +function calculatePath (paths = [], pathDepth) { paths = paths.slice(pathDepth) // path depth // replace repeated '{' and '}' chars return '/' + paths.map(path => path.replace(/([{}])\1+/g, '$1')) diff --git a/test/index.spec.js b/test/index.spec.js index 5affc33..7288293 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -21,6 +21,7 @@ const COLLECTION_MULTIPLE_SERVERS = './test/resources/input/MultipleServers.json const COLLECTION_LICENSE_CONTACT = './test/resources/input/LicenseContact.json' const COLLECTION_DEPTH_PATH_PARAMS = './test/resources/input/DepthPathParams.json' const COLLECTION_PARSE_STATUS_CODE = './test/resources/input/ParseStatusCode.json' +const COLLECTION_NO_PATH = './test/resources/input/NoPath.json' const EXPECTED_BASIC = readFileSync('./test/resources/output/Basic.yml', 'utf8') const EXPECTED_INFO_OPTS = readFileSync('./test/resources/output/InfoOpts.yml', 'utf8') @@ -42,6 +43,7 @@ const EXPECTED_LICENSE_CONTACT_PARTIAL = readFileSync('./test/resources/output/L const EXPECTED_LICENSE_CONTACT_PARTIAL_2 = readFileSync('./test/resources/output/LicenseContactPartial2.yml', 'utf8') const EXPECTED_DEPTH_PATH_PARAMS = readFileSync('./test/resources/output/DepthPathParams.yml', 'utf8') const EXPECTED_PARSE_STATUS_CODE = readFileSync('./test/resources/output/ParseStatus.yml', 'utf8') +const EXPECTED_NO_PATH = readFileSync('./test/resources/output/NoPath.yml', 'utf8') describe('Library specs', function () { afterEach('remove file', function () { @@ -223,4 +225,9 @@ describe('Library specs', function () { const result = await postmanToOpenApi(COLLECTION_PARSE_STATUS_CODE) equal(result, EXPECTED_PARSE_STATUS_CODE) }) + + it.only('should parse operation when no path (only domain)', async function () { + const result = await postmanToOpenApi(COLLECTION_NO_PATH) + equal(result, EXPECTED_NO_PATH) + }) }) diff --git a/test/resources/input/NoPath.json b/test/resources/input/NoPath.json new file mode 100644 index 0000000..7ff8069 --- /dev/null +++ b/test/resources/input/NoPath.json @@ -0,0 +1,77 @@ +{ + "info": { + "_postman_id": "074ac938-087e-4762-8a33-feadabfa1145", + "name": "No Path", + "description": "API to manage GET methods", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Get list of users", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://api.io?age=45&name=Jhon&review=true&number=23.56&required=my value", + "protocol": "https", + "host": [ + "api", + "io" + ], + "query": [ + { + "key": "age", + "value": "45", + "description": "Filter by age [required]" + }, + { + "key": "name", + "value": "Jhon", + "description": "Filter by name [REQUIRED]" + }, + { + "key": "review", + "value": "true", + "description": "Indicate if should be reviewed or not" + }, + { + "key": "number", + "value": "23.56", + "description": "This is a number" + }, + { + "key": "required", + "value": "my value", + "description": "[required] mandatory paraemeter" + } + ] + }, + "description": "Obtain a list of users that fullfill the conditions of the filters" + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "e5a590b1-117c-4050-9541-926b7188ebe0", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "6fb7b705-a3fc-45e0-8a80-3f2b5ce4bf9f", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "protocolProfileBehavior": {} +} \ No newline at end of file diff --git a/test/resources/output/NoPath.yml b/test/resources/output/NoPath.yml new file mode 100644 index 0000000..38bf595 --- /dev/null +++ b/test/resources/output/NoPath.yml @@ -0,0 +1,53 @@ +openapi: 3.0.0 +info: + title: No Path + description: API to manage GET methods + version: 1.0.0 +servers: + - url: 'https://api.io' +paths: + /: + get: + tags: + - default + summary: Get list of users + description: Obtain a list of users that fullfill the conditions of the filters + parameters: + - name: age + in: query + schema: + type: integer + required: true + description: Filter by age + example: '45' + - name: name + in: query + schema: + type: string + required: true + description: Filter by name + example: Jhon + - name: review + in: query + schema: + type: boolean + description: Indicate if should be reviewed or not + example: 'true' + - name: number + in: query + schema: + type: number + description: This is a number + example: '23.56' + - name: required + in: query + schema: + type: string + required: true + description: mandatory paraemeter + example: my value + responses: + '200': + description: Successful response + content: + application/json: {} From a4e8af4e10b4bd568adaa2612693155aeda52f68 Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 25 Aug 2020 12:50:54 +0200 Subject: [PATCH 2/5] fix: error when no "options" in body #64 Close #64 --- lib/index.js | 2 +- test/index.spec.js | 8 +- test/resources/input/NoOptionsInBody.json | 121 ++++++++++++++++++++++ 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 test/resources/input/NoOptionsInBody.json diff --git a/lib/index.js b/lib/index.js index 4de18bb..0473e0b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -99,7 +99,7 @@ function parseContact (variables, optsContact = {}) { function parseBody (body = {}, method) { // Swagger validation return an error if GET has body if (['GET'].includes(method)) return {} - const { mode, raw, options } = body + const { mode, raw, options = { raw: { language: 'json' } } } = body let content = {} switch (mode) { case 'raw': { diff --git a/test/index.spec.js b/test/index.spec.js index 7288293..61e46b6 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -22,6 +22,7 @@ const COLLECTION_LICENSE_CONTACT = './test/resources/input/LicenseContact.json' const COLLECTION_DEPTH_PATH_PARAMS = './test/resources/input/DepthPathParams.json' const COLLECTION_PARSE_STATUS_CODE = './test/resources/input/ParseStatusCode.json' const COLLECTION_NO_PATH = './test/resources/input/NoPath.json' +const COLLECTION_NO_OPTIONS = './test/resources/input/NoOptionsInBody.json' const EXPECTED_BASIC = readFileSync('./test/resources/output/Basic.yml', 'utf8') const EXPECTED_INFO_OPTS = readFileSync('./test/resources/output/InfoOpts.yml', 'utf8') @@ -226,8 +227,13 @@ describe('Library specs', function () { equal(result, EXPECTED_PARSE_STATUS_CODE) }) - it.only('should parse operation when no path (only domain)', async function () { + it('should parse operation when no path (only domain)', async function () { const result = await postmanToOpenApi(COLLECTION_NO_PATH) equal(result, EXPECTED_NO_PATH) }) + + it('should work if no options in request body', async function () { + const result = await postmanToOpenApi(COLLECTION_NO_OPTIONS, OUTPUT_PATH, {}) + equal(result, EXPECTED_BASIC) + }) }) diff --git a/test/resources/input/NoOptionsInBody.json b/test/resources/input/NoOptionsInBody.json new file mode 100644 index 0000000..05d1102 --- /dev/null +++ b/test/resources/input/NoOptionsInBody.json @@ -0,0 +1,121 @@ +{ + "info": { + "_postman_id": "2a45baa5-352f-4831-8483-1a0fcbc7da37", + "name": "Postman to OpenAPI", + "description": "Mi super test collection from postman", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Create new User", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"example\": \"field\",\n \"other\": {\n \"data1\": \"yes\",\n \"data2\": \"no\"\n }\n}" + }, + "url": { + "raw": "https://api.io/users", + "protocol": "https", + "host": [ + "api", + "io" + ], + "path": [ + "users" + ] + }, + "description": "Create a new user into your amazing API" + }, + "response": [] + }, + { + "name": "Create a post", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "file", + "file": {}, + "options": { + "raw": { + "language": "text" + } + } + }, + "url": { + "raw": "https://api.io/posts", + "protocol": "https", + "host": [ + "api", + "io" + ], + "path": [ + "posts" + ] + } + }, + "response": [] + }, + { + "name": "Create a note", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "This is an example Note", + "options": { + "raw": { + "language": "text" + } + } + }, + "url": { + "raw": "https://api.io/note", + "protocol": "https", + "host": [ + "api", + "io" + ], + "path": [ + "note" + ] + }, + "description": "Just an example of text raw body" + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "ef248fcc-2944-4cba-837f-680b10a45b30", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "8e7453aa-5712-4a90-bc32-965c7d47906a", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "id": "d04439bd-c604-4758-bde2-3c873140f38c", + "key": "version", + "value": "1.1.0" + } + ], + "protocolProfileBehavior": {} +} \ No newline at end of file From ea1671e355e17816664455c4807c3b75c056017d Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 25 Aug 2020 13:20:22 +0200 Subject: [PATCH 3/5] feat: support hyphen character in path variables #65 Close #65 --- lib/index.js | 2 +- test/resources/input/PathParams.json | 27 ++++++++++++++++++++++++--- test/resources/output/PathParams.yml | 17 +++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/lib/index.js b/lib/index.js index 0473e0b..36be7ec 100644 --- a/lib/index.js +++ b/lib/index.js @@ -162,7 +162,7 @@ function mapParameters (type) { } function extractPathParameters (path, paramsMeta) { - const matched = path.match(/{\s*[\w]+\s*}/g) || [] + const matched = path.match(/{\s*[\w-]+\s*}/g) || [] return matched.map(match => { const name = match.slice(1, -1) const { type = 'string', description, example } = paramsMeta[name] || {} diff --git a/test/resources/input/PathParams.json b/test/resources/input/PathParams.json index 22e1b0d..1a66c35 100644 --- a/test/resources/input/PathParams.json +++ b/test/resources/input/PathParams.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "c2a620d9-979e-4f39-b6bc-22282ce4b21a", + "_postman_id": "da50fa53-6326-4c7c-bfd8-6041a2c625d9", "name": "Path Params", "description": "Collection to test path parameters", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" @@ -27,6 +27,27 @@ }, "response": [] }, + { + "name": "Get one customer", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://api.io/customer/{{customer-id}}", + "protocol": "https", + "host": [ + "api", + "io" + ], + "path": [ + "customer", + "{{customer-id}}" + ] + }, + "description": "Obtain one customer info" + }, + "response": [] + }, { "name": "Get one users with description", "request": { @@ -53,7 +74,7 @@ { "listen": "prerequest", "script": { - "id": "3d4c9432-87d0-46d1-8e0a-7bf78b95838f", + "id": "8d6ddd6c-f1a8-4a68-91e0-07ab38f881a8", "type": "text/javascript", "exec": [ "" @@ -63,7 +84,7 @@ { "listen": "test", "script": { - "id": "bfa95668-f3ba-423f-93d3-152fbba765af", + "id": "e9cb3ab3-f140-42b3-8c12-17667c92c907", "type": "text/javascript", "exec": [ "" diff --git a/test/resources/output/PathParams.yml b/test/resources/output/PathParams.yml index 232ed41..51a26a9 100644 --- a/test/resources/output/PathParams.yml +++ b/test/resources/output/PathParams.yml @@ -23,6 +23,23 @@ paths: description: Successful response content: application/json: {} + '/customer/{customer-id}': + get: + tags: + - default + summary: Get one customer + description: Obtain one customer info + parameters: + - name: customer-id + in: path + schema: + type: string + required: true + responses: + '200': + description: Successful response + content: + application/json: {} '/desc/{user_id}': get: tags: From 38eb3570a7afababd725b3dfe266ac98565d6e20 Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 25 Aug 2020 13:28:48 +0200 Subject: [PATCH 4/5] feat: support DELETE operations #66 Close #66 --- lib/index.js | 2 +- test/index.spec.js | 7 +++ test/resources/input/DeleteOperation.json | 60 +++++++++++++++++++++++ test/resources/output/DeleteOperation.yml | 19 +++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 test/resources/input/DeleteOperation.json create mode 100644 test/resources/output/DeleteOperation.yml diff --git a/lib/index.js b/lib/index.js index 36be7ec..960524b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -98,7 +98,7 @@ function parseContact (variables, optsContact = {}) { function parseBody (body = {}, method) { // Swagger validation return an error if GET has body - if (['GET'].includes(method)) return {} + if (['GET', 'DELETE'].includes(method)) return {} const { mode, raw, options = { raw: { language: 'json' } } } = body let content = {} switch (mode) { diff --git a/test/index.spec.js b/test/index.spec.js index 61e46b6..51b0283 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -23,6 +23,7 @@ const COLLECTION_DEPTH_PATH_PARAMS = './test/resources/input/DepthPathParams.jso const COLLECTION_PARSE_STATUS_CODE = './test/resources/input/ParseStatusCode.json' const COLLECTION_NO_PATH = './test/resources/input/NoPath.json' const COLLECTION_NO_OPTIONS = './test/resources/input/NoOptionsInBody.json' +const COLLECTION_DELETE = './test/resources/input/DeleteOperation.json' const EXPECTED_BASIC = readFileSync('./test/resources/output/Basic.yml', 'utf8') const EXPECTED_INFO_OPTS = readFileSync('./test/resources/output/InfoOpts.yml', 'utf8') @@ -45,6 +46,7 @@ const EXPECTED_LICENSE_CONTACT_PARTIAL_2 = readFileSync('./test/resources/output const EXPECTED_DEPTH_PATH_PARAMS = readFileSync('./test/resources/output/DepthPathParams.yml', 'utf8') const EXPECTED_PARSE_STATUS_CODE = readFileSync('./test/resources/output/ParseStatus.yml', 'utf8') const EXPECTED_NO_PATH = readFileSync('./test/resources/output/NoPath.yml', 'utf8') +const EXPECTED_DELETE = readFileSync('./test/resources/output/DeleteOperation.yml', 'utf8') describe('Library specs', function () { afterEach('remove file', function () { @@ -236,4 +238,9 @@ describe('Library specs', function () { const result = await postmanToOpenApi(COLLECTION_NO_OPTIONS, OUTPUT_PATH, {}) equal(result, EXPECTED_BASIC) }) + + it('should support "DELETE" operations', async function () { + const result = await postmanToOpenApi(COLLECTION_DELETE) + equal(result, EXPECTED_DELETE) + }) }) diff --git a/test/resources/input/DeleteOperation.json b/test/resources/input/DeleteOperation.json new file mode 100644 index 0000000..78fb537 --- /dev/null +++ b/test/resources/input/DeleteOperation.json @@ -0,0 +1,60 @@ +{ + "info": { + "_postman_id": "469344e4-4c85-45fe-b0a6-246575a018bc", + "name": "Delete Operation", + "description": "Mi super test collection from postman", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Delete all users", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "https://api.io/users", + "protocol": "https", + "host": [ + "api", + "io" + ], + "path": [ + "users" + ] + }, + "description": "Delete all the existing users" + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "9f8baaad-642e-48c6-b5d3-6ec59d2df6b1", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "61df081f-afb8-4e8a-8a32-66db8b22f887", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "id": "b8ff9254-81e3-432f-9e5a-aea92cf34d7c", + "key": "version", + "value": "1.1.0" + } + ], + "protocolProfileBehavior": {} +} \ No newline at end of file diff --git a/test/resources/output/DeleteOperation.yml b/test/resources/output/DeleteOperation.yml new file mode 100644 index 0000000..2954a7f --- /dev/null +++ b/test/resources/output/DeleteOperation.yml @@ -0,0 +1,19 @@ +openapi: 3.0.0 +info: + title: Delete Operation + description: Mi super test collection from postman + version: 1.1.0 +servers: + - url: 'https://api.io' +paths: + /users: + delete: + tags: + - default + summary: Delete all users + description: Delete all the existing users + responses: + '200': + description: Successful response + content: + application/json: {} From 9b9ed569b07c4e56a8b5a80c6cad614390cc4878 Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 25 Aug 2020 13:30:41 +0200 Subject: [PATCH 5/5] build: update version and docs --- CHANGELOG.md | 14 ++++++++++++++ package.json | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f68f0..2812400 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## [1.4.0](https://github.com/joolfe/postman-to-openapi/compare/1.3.0...1.4.0) (2020-08-25) + + +### Features + +* support DELETE operations [#66](https://github.com/joolfe/postman-to-openapi/issues/66) ([38eb357](https://github.com/joolfe/postman-to-openapi/commit/38eb3570a7afababd725b3dfe266ac98565d6e20)) +* support hyphen character in path variables [#65](https://github.com/joolfe/postman-to-openapi/issues/65) ([ea1671e](https://github.com/joolfe/postman-to-openapi/commit/ea1671e355e17816664455c4807c3b75c056017d)) + + +### Bug Fixes + +* error when no "options" in body [#64](https://github.com/joolfe/postman-to-openapi/issues/64) ([a4e8af4](https://github.com/joolfe/postman-to-openapi/commit/a4e8af4e10b4bd568adaa2612693155aeda52f68)) +* should support root paths [#63](https://github.com/joolfe/postman-to-openapi/issues/63) ([aff566b](https://github.com/joolfe/postman-to-openapi/commit/aff566b27b3d87884dd66d2aac7aa030aff2da1c)) + ## [1.3.0](https://github.com/joolfe/postman-to-openapi/compare/1.2.0...1.3.0) (2020-08-23) diff --git a/package.json b/package.json index 7306c90..05fd221 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postman-to-openapi", - "version": "1.3.0", + "version": "1.4.0", "description": "Convert postman collection to OpenAPI spec", "main": "lib/index.js", "scripts": {