From 3f86a19b3b1bca7816141cd02022a370c6c8c81d Mon Sep 17 00:00:00 2001 From: Jorge Date: Sun, 6 Nov 2022 13:02:26 +0100 Subject: [PATCH 1/3] feat: allow add operation id to the request --- docs/index.md | 17 +++ lib/index.js | 34 ++++- package-lock.json | 77 +++++++++--- package.json | 3 +- test/index.spec.js | 24 ++++ test/resources/input/v2/OperationIds.json | 88 +++++++++++++ test/resources/input/v21/OperationIds.json | 118 ++++++++++++++++++ test/resources/output/OperationIds.yml | 59 +++++++++ test/resources/output/OperationIdsAuto.yml | 62 +++++++++ .../resources/output/OperationIdsBrackets.yml | 61 +++++++++ types/index.d.ts | 4 +- 11 files changed, 526 insertions(+), 21 deletions(-) create mode 100644 test/resources/input/v2/OperationIds.json create mode 100644 test/resources/input/v21/OperationIds.json create mode 100644 test/resources/output/OperationIds.yml create mode 100644 test/resources/output/OperationIdsAuto.yml create mode 100644 test/resources/output/OperationIdsBrackets.yml diff --git a/docs/index.md b/docs/index.md index 8fd6456..2266a2c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -133,6 +133,7 @@ The third parameter used in the library method is an `options` object containing | [replaceVars](#replacevars-boolean) | Boolean value to indicate if postman variables should be replaced.| | [additionalVars](#additionalvars-object) | Object to provide additional values for variables replacement.| | [outputFormat](#outputformat-string) | Indicate the format of the output document. | +| [operationId](#operationid-string) | Indicate how to provide the value for `operationId` field. | ### info (Object) @@ -361,6 +362,22 @@ By default all parameters in the postman collection that has the field `"disable Please have a look to the [Parameters parsing](#parameters-parsing) section about duplicated parameters names in Headers and Query, this will apply also to the disabled parameters when using this feature. +### operationId (string) + +In OpenAPI the [operationId](https://swagger.io/specification/#operation-object) is a unique id that is used mainly for Tools and libraries to uniquely identify an operation, with this option you can indicate the strategy to provide this value for each request operation, the possible values are: + +| Option | Description | +|------------------|------------------------------------------------------------------------------------| +| `off` | Default. No `operationId` will be added. | +| `auto` | The field `name` of the request will transformed as [Camel case](https://es.wikipedia.org/wiki/Camel_ca) and used as `operationId`. | +| `brackets` | Will look for a name between brackets in the fields `name` of the request and use this as `operationId`. | + +As an example of option `auto` if you have in a postman collection a request with name `Create new User` the resulting operation id will be `createNewUser`. + +To use option `brackets` you should add the desired operation id between brackets in the name of the request, so for example if you use as request name `Create new User [newUser]`, the text `newUser` will be used as operation id, the library automatically will remove the literal `[newUser]` from the name and will no appear in the `summary` field in the OpenAPI yaml. + +> **Note about duplications:** As described in OpenAPI about the operationId, "The id MUST be unique among all operations described in the API." but the library does not ensure the uniqueness, so before do the conversion check that you are using unique operations ids for each request in your collection. +
diff --git a/lib/index.js b/lib/index.js index ec9f1d3..5a3d427 100644 --- a/lib/index.js +++ b/lib/index.js @@ -6,12 +6,13 @@ const { parseMdTable } = require('./md-utils') const { version } = require('../package.json') const replacePostmanVariables = require('./var-replacer') const jsonc = require('jsonc-parser') +const camelCase = require('lodash.camelcase') async function postmanToOpenApi (input, output, { info = {}, defaultTag = 'default', pathDepth = 0, auth: optsAuth, servers, externalDocs = {}, folders = {}, responseHeaders = true, replaceVars = false, additionalVars = {}, outputFormat = 'yaml', - disabledParams = { includeQuery: false, includeHeader: false } + disabledParams = { includeQuery: false, includeHeader: false }, operationId = 'off' } = {}) { // TODO validate? let collectionFile = await resolveInput(input) @@ -40,10 +41,12 @@ async function postmanToOpenApi (input, output, { if (element != null) { const { request: { url, method, body, description: rawDesc, header = [], auth }, - name: summary, tag = defaultTag, event: events, response + name, tag = defaultTag, event: events, response } = element const { path, query, protocol, host, port, valid, pathVars } = scrapeURL(url) if (valid) { + // Remove from name the possible operation id between brackets + const summary = name.replace(/ \[(.*?)\]/gi, '') domains.add(calculateDomains(protocol, host, port)) const joinedPath = calculatePath(path, pathDepth) if (!paths[joinedPath]) paths[joinedPath] = {} @@ -51,6 +54,7 @@ async function postmanToOpenApi (input, output, { paths[joinedPath][method.toLowerCase()] = { tags: [tag], summary, + ...(calculateOperationId(operationId, name, summary)), ...(description ? { description } : {}), ...parseBody(body, method), ...parseOperationAuth(auth, securitySchemes, optsAuth), @@ -610,6 +614,32 @@ function isRequired (text) { return /\[required\]/gi.test(text) } +/** + * calculate the operationId based on the user selected `mode` + * @param {*} mode - mode to calculate the operation id between `off`, `auto` or `brackets` + * @param {*} name - field name of the request/operation in the postman collection without modify. + * @param {*} summary - calculated summary of the operation that will be used in the OpenAPI spec. + * @returns an operation id + */ +function calculateOperationId (mode, name, summary) { + let operationId + switch (mode) { + case 'off': + break + case 'auto': + operationId = camelCase(summary) + break + case 'brackets': { + const matches = name.match(/\[([^()]*)\]/) + operationId = matches ? matches[1] : undefined + break + } + default: // Unknown value in the operationId option + break + } + return operationId ? { operationId } : {} +} + postmanToOpenApi.version = version module.exports = postmanToOpenApi diff --git a/package-lock.json b/package-lock.json index 7610e13..762d663 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "postman-to-openapi", - "version": "2.8.0", + "version": "2.9.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "postman-to-openapi", - "version": "2.8.0", + "version": "2.9.0", "license": "MIT", "dependencies": { "commander": "^8.3.0", "js-yaml": "^4.1.0", "jsonc-parser": "3.2.0", + "lodash.camelcase": "^4.3.0", "marked": "^4.2.2", "mustache": "^4.2.0" }, @@ -805,6 +806,15 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -1318,15 +1328,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/camelcase-keys": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", @@ -1344,6 +1345,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/camelcase-keys/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001370", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001370.tgz", @@ -3568,6 +3578,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -3990,6 +4005,15 @@ "node": ">=8.9" } }, + "node_modules/nyc/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/nyc/node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -6233,6 +6257,12 @@ "sprintf-js": "~1.0.2" } }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -6627,12 +6657,6 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "camelcase-keys": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", @@ -6642,6 +6666,14 @@ "camelcase": "^5.3.1", "map-obj": "^4.0.0", "quick-lru": "^4.0.1" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } } }, "caniuse-lite": { @@ -8247,6 +8279,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -8568,6 +8605,12 @@ "yargs": "^15.0.2" }, "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", diff --git a/package.json b/package.json index 882ea61..81ce973 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postman-to-openapi", - "version": "2.8.0", + "version": "2.9.0", "description": "Convert postman collection to OpenAPI spec", "main": "lib/index.js", "types": "types/index.d.ts", @@ -90,6 +90,7 @@ "commander": "^8.3.0", "js-yaml": "^4.1.0", "jsonc-parser": "3.2.0", + "lodash.camelcase": "^4.3.0", "marked": "^4.2.2", "mustache": "^4.2.0" }, diff --git a/test/index.spec.js b/test/index.spec.js index b117397..7da3674 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -66,6 +66,9 @@ const EXPECTED_DISABLED_PARAMS_DEFAULT = readFileSync('./test/resources/output/D const EXPECTED_DISABLED_PARAMS_ALL = readFileSync('./test/resources/output/DisabledParamsAll.yml', 'utf8') const EXPECTED_DISABLED_PARAMS_QUERY = readFileSync('./test/resources/output/DisabledParamsQuery.yml', 'utf8') const EXPECTED_DISABLED_PARAMS_HEADER = readFileSync('./test/resources/output/DisabledParamsHeader.yml', 'utf8') +const EXPECTED_OPERATIONS_IDS = readFileSync('./test/resources/output/OperationIds.yml', 'utf8') +const EXPECTED_OPERATIONS_IDS_AUTO = readFileSync('./test/resources/output/OperationIdsAuto.yml', 'utf8') +const EXPECTED_OPERATIONS_IDS_BRACKETS = readFileSync('./test/resources/output/OperationIdsBrackets.yml', 'utf8') const AUTH_DEFINITIONS = { myCustomAuth: { @@ -130,6 +133,7 @@ describe('Library specs', function () { const COLLECTION_RESPONSES_EMPTY = `./test/resources/input/${version}/ResponsesEmpty.json` const COLLECTION_JSON_COMMENTS = `./test/resources/input/${version}/JsonComments.json` const COLLECTION_DISABLED = `./test/resources/input/${version}/DisabledParams.json` + const COLLECTION_OPERATION_IDS = `./test/resources/input/${version}/OperationIds.json` it('should work with a basic transform', async function () { const result = await postmanToOpenApi(COLLECTION_BASIC, OUTPUT_PATH, {}) @@ -535,6 +539,26 @@ describe('Library specs', function () { }) equal(result, EXPECTED_DISABLED_PARAMS_HEADER) }) + + it('should not add `operationId` by default', async function () { + const result = await postmanToOpenApi(COLLECTION_OPERATION_IDS, OUTPUT_PATH) + equal(result, EXPECTED_OPERATIONS_IDS) + }) + + it('should include `operationId` when `auto` is selected', async function () { + const result = await postmanToOpenApi(COLLECTION_OPERATION_IDS, OUTPUT_PATH, { operationId: 'auto' }) + equal(result, EXPECTED_OPERATIONS_IDS_AUTO) + }) + + it('should include `operationId` when `brackets` is selected', async function () { + const result = await postmanToOpenApi(COLLECTION_OPERATION_IDS, OUTPUT_PATH, { operationId: 'brackets' }) + equal(result, EXPECTED_OPERATIONS_IDS_BRACKETS) + }) + + it('should not add `operationId` if option is unknown', async function () { + const result = await postmanToOpenApi(COLLECTION_OPERATION_IDS, OUTPUT_PATH, { operationId: 'banana' }) + equal(result, EXPECTED_OPERATIONS_IDS) + }) }) }) diff --git a/test/resources/input/v2/OperationIds.json b/test/resources/input/v2/OperationIds.json new file mode 100644 index 0000000..96d0024 --- /dev/null +++ b/test/resources/input/v2/OperationIds.json @@ -0,0 +1,88 @@ +{ + "info": { + "_postman_id": "030b673d-ddb2-4217-914c-46890cf112cc", + "name": "OperationIds", + "description": "Mi super test collection from postman", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "_exporter_id": "64956" + }, + "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}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "https://api.io/users", + "description": "Create a new user into your amazing API" + }, + "response": [] + }, + { + "name": "Create a post [createPost]", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "file", + "file": {} + }, + "url": "https://api.io/posts" + }, + "response": [] + }, + { + "name": "Create a note [createNote]", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "This is an example Note", + "options": { + "raw": { + "language": "text" + } + } + }, + "url": "https://api.io/note", + "description": "Just an example of text raw body" + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "version", + "value": "1.1.0" + } + ] +} \ No newline at end of file diff --git a/test/resources/input/v21/OperationIds.json b/test/resources/input/v21/OperationIds.json new file mode 100644 index 0000000..b7f06d1 --- /dev/null +++ b/test/resources/input/v21/OperationIds.json @@ -0,0 +1,118 @@ +{ + "info": { + "_postman_id": "030b673d-ddb2-4217-914c-46890cf112cc", + "name": "OperationIds", + "description": "Mi super test collection from postman", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "64956" + }, + "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}", + "options": { + "raw": { + "language": "json" + } + } + }, + "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 [createPost]", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "file", + "file": {} + }, + "url": { + "raw": "https://api.io/posts", + "protocol": "https", + "host": [ + "api", + "io" + ], + "path": [ + "posts" + ] + } + }, + "response": [] + }, + { + "name": "Create a note [createNote]", + "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": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "version", + "value": "1.1.0" + } + ] +} \ No newline at end of file diff --git a/test/resources/output/OperationIds.yml b/test/resources/output/OperationIds.yml new file mode 100644 index 0000000..d32a54a --- /dev/null +++ b/test/resources/output/OperationIds.yml @@ -0,0 +1,59 @@ +openapi: 3.0.0 +info: + title: OperationIds + description: Mi super test collection from postman + version: 1.1.0 +servers: + - url: https://api.io +paths: + /users: + post: + tags: + - default + summary: Create new User + description: Create a new user into your amazing API + requestBody: + content: + application/json: + schema: + type: object + example: + example: field + other: + data1: 'yes' + data2: 'no' + responses: + '200': + description: Successful response + content: + application/json: {} + /posts: + post: + tags: + - default + summary: Create a post + requestBody: + content: + text/plain: {} + responses: + '200': + description: Successful response + content: + application/json: {} + /note: + post: + tags: + - default + summary: Create a note + description: Just an example of text raw body + requestBody: + content: + text/plain: + schema: + type: string + example: This is an example Note + responses: + '200': + description: Successful response + content: + application/json: {} diff --git a/test/resources/output/OperationIdsAuto.yml b/test/resources/output/OperationIdsAuto.yml new file mode 100644 index 0000000..a22c76f --- /dev/null +++ b/test/resources/output/OperationIdsAuto.yml @@ -0,0 +1,62 @@ +openapi: 3.0.0 +info: + title: OperationIds + description: Mi super test collection from postman + version: 1.1.0 +servers: + - url: https://api.io +paths: + /users: + post: + tags: + - default + summary: Create new User + operationId: createNewUser + description: Create a new user into your amazing API + requestBody: + content: + application/json: + schema: + type: object + example: + example: field + other: + data1: 'yes' + data2: 'no' + responses: + '200': + description: Successful response + content: + application/json: {} + /posts: + post: + tags: + - default + summary: Create a post + operationId: createAPost + requestBody: + content: + text/plain: {} + responses: + '200': + description: Successful response + content: + application/json: {} + /note: + post: + tags: + - default + summary: Create a note + operationId: createANote + description: Just an example of text raw body + requestBody: + content: + text/plain: + schema: + type: string + example: This is an example Note + responses: + '200': + description: Successful response + content: + application/json: {} diff --git a/test/resources/output/OperationIdsBrackets.yml b/test/resources/output/OperationIdsBrackets.yml new file mode 100644 index 0000000..d24cb95 --- /dev/null +++ b/test/resources/output/OperationIdsBrackets.yml @@ -0,0 +1,61 @@ +openapi: 3.0.0 +info: + title: OperationIds + description: Mi super test collection from postman + version: 1.1.0 +servers: + - url: https://api.io +paths: + /users: + post: + tags: + - default + summary: Create new User + description: Create a new user into your amazing API + requestBody: + content: + application/json: + schema: + type: object + example: + example: field + other: + data1: 'yes' + data2: 'no' + responses: + '200': + description: Successful response + content: + application/json: {} + /posts: + post: + tags: + - default + summary: Create a post + operationId: createPost + requestBody: + content: + text/plain: {} + responses: + '200': + description: Successful response + content: + application/json: {} + /note: + post: + tags: + - default + summary: Create a note + operationId: createNote + description: Just an example of text raw body + requestBody: + content: + text/plain: + schema: + type: string + example: This is an example Note + responses: + '200': + description: Successful response + content: + application/json: {} diff --git a/types/index.d.ts b/types/index.d.ts index cb9d678..5a84482 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -92,7 +92,9 @@ export interface Options { additionalVars?: { [key: string]: string }, // Default value 'yaml' outputFormat?: 'json' | 'yaml', - disabledParams?: DisabledParamsOptions + disabledParams?: DisabledParamsOptions, + // Default value 'off' + operationId?: 'off' | 'auto' | 'brackets' } export default function postmanToOpenApi (input: string, output?: string, options?: Options) : Promise From b222ce464e71731adc79da201184af862eec4231 Mon Sep 17 00:00:00 2001 From: Jorge Date: Sun, 6 Nov 2022 13:03:03 +0100 Subject: [PATCH 2/3] docs: update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b28c47d..f56dc19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.9.0](https://github.com/joolfe/postman-to-openapi/compare/2.8.0...2.9.0) (2022-11-06) + + +### Features + +* allow add operation id to the request ([3f86a19](https://github.com/joolfe/postman-to-openapi/commit/3f86a19b3b1bca7816141cd02022a370c6c8c81d)) + ### [2.7.2](https://github.com/joolfe/postman-to-openapi/compare/2.7.1...2.7.2) (2022-11-05) From 8bda41f7e00a40fc58c1bdf07be8a5f1c7362d11 Mon Sep 17 00:00:00 2001 From: Jorge Date: Sun, 6 Nov 2022 13:24:44 +0100 Subject: [PATCH 3/3] fix: change regex to avoid redos attacks --- lib/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/index.js b/lib/index.js index 5a3d427..4b7b3cb 100644 --- a/lib/index.js +++ b/lib/index.js @@ -46,7 +46,8 @@ async function postmanToOpenApi (input, output, { const { path, query, protocol, host, port, valid, pathVars } = scrapeURL(url) if (valid) { // Remove from name the possible operation id between brackets - const summary = name.replace(/ \[(.*?)\]/gi, '') + // eslint-disable-next-line no-useless-escape + const summary = name.replace(/ \[([^\[\]]*)\]/gi, '') domains.add(calculateDomains(protocol, host, port)) const joinedPath = calculatePath(path, pathDepth) if (!paths[joinedPath]) paths[joinedPath] = {} @@ -630,7 +631,8 @@ function calculateOperationId (mode, name, summary) { operationId = camelCase(summary) break case 'brackets': { - const matches = name.match(/\[([^()]*)\]/) + // eslint-disable-next-line no-useless-escape + const matches = name.match(/\[([^\[\]]*)\]/) operationId = matches ? matches[1] : undefined break }