diff --git a/packages/httpsnippet-client-api/__tests__/__fixtures__/output/issue-78-operationid.js b/packages/httpsnippet-client-api/__tests__/__fixtures__/output/issue-78-operationid.js new file mode 100644 index 00000000..19fbc9e1 --- /dev/null +++ b/packages/httpsnippet-client-api/__tests__/__fixtures__/output/issue-78-operationid.js @@ -0,0 +1,7 @@ +const sdk = require('api')('https://example.com/openapi.json'); + +sdk.getOrder({orderId: '1234', accept: 'application/xml'}) + .then(res => res.json()) + .then(res => { + console.log(res); + }); diff --git a/packages/httpsnippet-client-api/__tests__/__fixtures__/output/issue-78.js b/packages/httpsnippet-client-api/__tests__/__fixtures__/output/issue-78.js index 5775c5e0..94c7bbaf 100644 --- a/packages/httpsnippet-client-api/__tests__/__fixtures__/output/issue-78.js +++ b/packages/httpsnippet-client-api/__tests__/__fixtures__/output/issue-78.js @@ -1,6 +1,6 @@ const sdk = require('api')('https://example.com/openapi.json'); -sdk.get('/store/order/1234', {accept: 'application/xml'}) +sdk.get('/store/order/1234/tracking/{trackingId}', {accept: 'application/xml'}) .then(res => res.json()) .then(res => { console.log(res); diff --git a/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78-operationid/definition.json b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78-operationid/definition.json new file mode 100644 index 00000000..2e7aadec --- /dev/null +++ b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78-operationid/definition.json @@ -0,0 +1,37 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "issue-78" + }, + "servers": [ + { + "url": "http://petstore.swagger.io/v2" + } + ], + "paths": { + "/store/order/{orderId}": { + "get": { + "operationId": "getOrder", + "parameters": [ + { + "name": "orderId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 10 + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + } +} diff --git a/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78-operationid/har.json b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78-operationid/har.json new file mode 100644 index 00000000..b6c9af5a --- /dev/null +++ b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78-operationid/har.json @@ -0,0 +1,25 @@ +{ + "log": { + "entries": [ + { + "request": { + "headers": [ + { + "name": "Accept", + "value": "application/xml" + } + ], + "httpVersion": "HTTP/1.1", + "method": "GET", + "postData": { + "jsonObj": false, + "mimeType": "application/octet-stream", + "paramsObj": false, + "size": 0 + }, + "url": "http://petstore.swagger.io/v2/store/order/1234" + } + } + ] + } +} diff --git a/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78/definition.json b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78/definition.json index fd56fd68..d42345ae 100644 --- a/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78/definition.json +++ b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78/definition.json @@ -10,20 +10,29 @@ } ], "paths": { - "/store/order/{orderId}": { + "/store/order/{orderId}/tracking/{trackingId}": { "get": { "parameters": [ - { - "name": "orderId", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int64", - "minimum": 1, - "maximum": 10 - } - } + { + "name": "orderId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 10 + } + }, + { + "name": "trackingId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } ], "responses": { "200": { diff --git a/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78/har.json b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78/har.json index b6c9af5a..82320e25 100644 --- a/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78/har.json +++ b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/issue-78/har.json @@ -17,7 +17,7 @@ "paramsObj": false, "size": 0 }, - "url": "http://petstore.swagger.io/v2/store/order/1234" + "url": "http://petstore.swagger.io/v2/store/order/1234/tracking/{trackingId}" } } ] diff --git a/packages/httpsnippet-client-api/__tests__/index.test.js b/packages/httpsnippet-client-api/__tests__/index.test.js index dba1feb4..3c736e64 100644 --- a/packages/httpsnippet-client-api/__tests__/index.test.js +++ b/packages/httpsnippet-client-api/__tests__/index.test.js @@ -59,6 +59,7 @@ describe('snippets', () => { ['https'], ['issue-76'], ['issue-78'], + ['issue-78-operationid'], ['jsonObj-multiline'], ['jsonObj-null-value'], diff --git a/packages/httpsnippet-client-api/package.json b/packages/httpsnippet-client-api/package.json index b1caf844..279372ce 100644 --- a/packages/httpsnippet-client-api/package.json +++ b/packages/httpsnippet-client-api/package.json @@ -26,6 +26,7 @@ "@readme/oas-tooling": "^3.4.6", "content-type": "^1.0.4", "httpsnippet": "^1.20.0", + "path-to-regexp": "^6.1.0", "stringify-object": "^3.3.0" }, "peerDependencies": { diff --git a/packages/httpsnippet-client-api/src/index.js b/packages/httpsnippet-client-api/src/index.js index 5ee77d94..094a992f 100644 --- a/packages/httpsnippet-client-api/src/index.js +++ b/packages/httpsnippet-client-api/src/index.js @@ -1,3 +1,4 @@ +const { match } = require('path-to-regexp'); const stringifyObject = require('stringify-object'); const CodeBuilder = require('httpsnippet/src/helpers/code-builder'); const contentType = require('content-type'); @@ -44,6 +45,26 @@ function getAuthSources(operation) { return matchers; } +function getParamsInPath(operation, path) { + const cleanedPath = operation.path.replace(/{(.*?)}/g, ':$1'); + const matchStatement = match(cleanedPath, { decode: decodeURIComponent }); + const matchResult = matchStatement(path); + const slugs = {}; + + if (matchResult && Object.keys(matchResult.params).length) { + Object.keys(matchResult.params).forEach(param => { + // Only qualify a parameter as being valid if it's actually been filled out, and isn't just the same thing. + // For example in the case of an operation being `/store/order/{orderId}/tracking/{trackingId}`, and the incoming + // path as `/store/order/1234/tracking/{trackingId}`, we only want to promote `orderId`. + if (`{${param}}` !== matchResult.params[param]) { + slugs[`${param}`] = matchResult.params[param]; + } + }); + } + + return slugs; +} + module.exports = function (source, options) { const opts = { indent: ' ', ...options }; @@ -57,6 +78,10 @@ module.exports = function (source, options) { const oas = new OAS(opts.apiDefinition); const operation = oas.getOperation(source.url, method); + // For cases where a server URL in the OAS has a path attached to it, we don't want to include that path with the + // operation path. + const path = source.url.replace(oas.url(), ''); + const authData = []; const authSources = getAuthSources(operation); @@ -80,6 +105,15 @@ module.exports = function (source, options) { metadata = Object.assign(metadata, queryParams); } + // If we have path parameters present, we should only add them in if we have an operationId as we don't want metadata + // to duplicate what we'll be setting the path in the snippet to. + const pathParams = getParamsInPath(operation, path); + if (Object.keys(pathParams).length && typeof operation.operationId !== 'undefined') { + Object.keys(pathParams).forEach(param => { + metadata[param] = pathParams[param]; + }); + } + if (Object.keys(source.headersObj).length) { const headers = source.headersObj; @@ -174,10 +208,7 @@ module.exports = function (source, options) { if ('operationId' in operation && operation.operationId.length > 0) { accessor = operation.operationId; } else { - // For cases where a server URL in the OAS has a path attached to it, we don't want to include that path with the - // operation path. - const path = source.url.replace(oas.url(), ''); - args.push(`'${path}'`); + args.push(`'${decodeURIComponent(path)}'`); } if (typeof body !== 'undefined') {