diff --git a/lib/convert.js b/lib/convert.js index ae69563..41295f4 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -1,145 +1,115 @@ var sdk = require('postman-collection'), - fs = require('fs'), - jsf = require('json-schema-faker'), - util = require('./util.js'), - _ = require('lodash'), - $RefParser = require('json-schema-ref-parser'); - async = require('async'); + util = require('./util.js'), + _ = require('lodash'), + $RefParser = require('json-schema-ref-parser'), + converter = { -// async function resolveOpenApiSpec(openapi){ -// var resolvedSchema = await $RefParser.dereference(openapi); -// return resolvedSchema; -// } + staticFolder: {}, + POSTMAN: {}, -var converter = { + generateCollection: function(status) { + var folderTree = status.tree, + openapi = status.spec, + child; - staticFolder: {}, - POSTMAN: {}, + for (child in folderTree.root.children) { + if (folderTree.root.children.hasOwnProperty(child)) { + this.POSTMAN.collection.items.add( + util.convertChildToItemGroup(openapi, folderTree.root.children[child]) + ); + } + } + }, - generateCollection: function(status){ - var folderTree = status.tree, - openapi = status.spec; + resolveOpenApiSpec: async function(openapi) { + var resolvedSchema = await $RefParser.dereference(openapi); + return resolvedSchema; + }, - for(var child in folderTree.root.children){ - this.POSTMAN.collection.items.add( - util.convertChildToItemGroup(openapi, folderTree.root.children[child]) - ); - } - }, - - resolveOpenApiSpec: async function(openapi){ - var resolvedSchema = await $RefParser.dereference(openapi); - return resolvedSchema; - }, - - convert: function (data, callback){ + convert: function (data, callback) { // console.log("tuhin",data); - var validation = util.parseSpec(data), + var validation = util.parseSpec(data), openapi = {}, - spec, - mycollection, description, - contact, - folderTree, - noVarUrl, - deRefObj = {}, - baseUri; + contact; - if(!validation.result){ - callback(validation); - } - - //TODO - Have to handle global level security scheme - - // changing the openapi spec for ease of access - openapi = validation.openapi; - openapi.securityDefs = openapi.components.securitySchemes || {}; - openapi.baseUrl = openapi.servers[0].url; - openapi.baseUrlVariables = openapi.servers[0].variables; - - // handling path templating in request url if any - openapi.baseUrl = openapi.baseUrl.replace(/{/g, ':').replace(/}/g, ''); - - - // creating a new instance of the collection - this.POSTMAN.collection = new sdk.Collection({ - info: { - name: openapi.info.title, - version: openapi.info.version, - }, - }); - - // adding the collection variables for all the necessary root level variables - - this.POSTMAN.collection = util.addCollectionVariables( - this.POSTMAN.collection, - openapi.baseUrlVariables, - 'base-url', - openapi.baseUrl - ); - - // if(openapi.baseUrlVariables){ - // _.forOwn(openapi.baseUrlVariables, (value, key) => { - // this.POSTMAN.collection.variables.add(new sdk.Variable({ - // id: key, - // value: value.default || '', - // description: value.description + (value.enum || ''), - // })); - // }); - // } else { - // this.POSTMAN.collection.variables.add(new sdk.Variable({ - // id: 'base-url', - // value: openapi.baseUrl, - // type: 'string', - // description: openapi.servers[0].description, - // })); - // } - - // adding the collection description - description = openapi.info.description; - - if(openapi.info.contact){ - contact = 'Name : ' + openapi.info.contact.name + '\n' + 'Email : ' + openapi.info.contact.email +'\n'; - description += '\nContact Support + \n{' + '\n' + contact + '\n}' - } - - this.POSTMAN.collection.describe(description); - - // the main callback exists in an asynchronous context, - this.resolveOpenApiSpec(openapi).then((fromResolve) => { - let status = {}; - let folderObj = util.generateTrieFromPaths(fromResolve); - - status = { - spec: fromResolve, - tree: folderObj.tree, + if (!validation.result) { + callback(validation); } - // adding path level variables which would be used - if(folderObj.variables){ - _.forEach(folderObj.variables, (collectionVariable) => { - this.POSTMAN.collection = util.addCollectionVariables( - this.POSTMAN.collection, - collectionVariable, - 'path-level-uri' - ); + + // TODO - Have to handle global level security scheme + + // changing the openapi spec for ease of access + openapi = validation.openapi; + openapi.securityDefs = openapi.components.securitySchemes || {}; + openapi.baseUrl = openapi.servers[0].url; + openapi.baseUrlVariables = openapi.servers[0].variables; + + // handling path templating in request url if any + openapi.baseUrl = openapi.baseUrl.replace(/{/g, ':').replace(/}/g, ''); + + + // creating a new instance of the collection + this.POSTMAN.collection = new sdk.Collection({ + info: { + name: openapi.info.title, + version: openapi.info.version + } + }); + + // adding the collection variables for all the necessary root level variables + + this.POSTMAN.collection = util.addCollectionVariables( + this.POSTMAN.collection, + openapi.baseUrlVariables, + 'base-url', + openapi.baseUrl + ); + // adding the collection description + description = openapi.info.description; + + if (openapi.info.contact) { + contact = 'Name : ' + openapi.info.contact.name + '\nEmail : ' + openapi.info.contact.email + '\n'; + description += '\nContact Support + \n{\n' + contact + '\n}'; + } + + this.POSTMAN.collection.describe(description); + + // the main callback exists in an asynchronous context, + this.resolveOpenApiSpec(openapi).then((fromResolve) => { + let status = {}; + folderObj = util.generateTrieFromPaths(fromResolve); + + status = { + spec: fromResolve, + tree: folderObj.tree + }; + // adding path level variables which would be used + if (folderObj.variables) { + _.forEach(folderObj.variables, (collectionVariable) => { + this.POSTMAN.collection = util.addCollectionVariables( + this.POSTMAN.collection, + collectionVariable, + 'path-level-uri' + ); + }); + } + + this.generateCollection(status); + callback({ + result: true, + collection: this.POSTMAN.collection + }); + }).catch((err) => { + callback({ + result: false, + reason: err }); - } - - this.generateCollection(status); - callback({ - result: true, - collection: this.POSTMAN.collection, }); - }).catch((err) => { - callback({ - result: false, - reason: err, - }); - }); - } -} + } + }; // module.exports = converter; -module.exports = function(json, callback){ +module.exports = function(json, callback) { converter.convert(json, callback); -} +}; diff --git a/lib/parse.js b/lib/parse.js index 8205c30..36c10e5 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1,70 +1,67 @@ var yaml = require('js-yaml'); module.exports = { - - asJson: function(spec){ - try{ + + asJson: function(spec) { + try { return JSON.parse(spec); } - catch (jsonException){ + catch (jsonException) { throw new SyntaxError(`Specification is not a valid JSON. ${jsonException}`); } }, - asYaml: function(spec){ - try{ + asYaml: function(spec) { + try { return yaml.safeLoad(spec); } - catch(yamlException){ + catch (yamlException) { throw new SyntaxError(`Specification is not a valid YAML. ${yamlException}`); } }, - validateRoot: function(spec){ + validateRoot: function(spec) { // Checking for the all the required properties in the specificatio - if(!spec.hasOwnProperty('openapi')){ + if (!spec.hasOwnProperty('openapi')) { return { result: false, - reason: 'Specification must contain a semantic version number of the OAS specification', - } + reason: 'Specification must contain a semantic version number of the OAS specification' + }; } - if(!spec.paths){ + if (!spec.paths) { return { result: false, - reason: 'Specification must contain Paths Object for the available operational paths', - } + reason: 'Specification must contain Paths Object for the available operational paths' + }; } - if(!spec.hasOwnProperty('info')){ + if (!spec.hasOwnProperty('info')) { return { result: false, - reason: 'Specification must contain an Info Object for the meta-data of the API', - } - } else { - if(!spec.info.hasOwnProperty('title')){ - return { - result: false, - reason: 'Specification must contain a title in order to generate a collection' - } - } - - if(!spec.info.hasOwnProperty('version')){ - return { - result: false, - reason: 'Specification must contain a semantic version number of the API in the Info Object' - } - } + reason: 'Specification must contain an Info Object for the meta-data of the API' + }; + } + if (!spec.info.hasOwnProperty('title')) { + return { + result: false, + reason: 'Specification must contain a title in order to generate a collection' + }; } - + if (!spec.info.hasOwnProperty('version')) { + return { + result: false, + reason: 'Specification must contain a semantic version number of the API in the Info Object' + }; + } // Valid specification return { result: true, - openapi: spec, - } - }, -} \ No newline at end of file + openapi: spec + }; + } +}; diff --git a/lib/util.js b/lib/util.js index 7ea4e7f..069ce46 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,39 +1,44 @@ -var sdk = require('postman-collection'), - schemaFaker = require('json-schema-faker'), - parse = require('./parse.js'), - _ = require('lodash'); +var sdk = require('postman-collection'), + schemaFaker = require('json-schema-faker'), + parse = require('./parse.js'), + _ = require('lodash'); const URLENCODED = 'application/x-www-form-urlencoded', - APP_JSON = 'application/json', - APP_JS = 'application/javascript', - APP_XML = 'application/xml', - TEXT_XML = 'text/xml', - TEXT_PLAIN = 'text/plain', - TEXT_HTML = 'text/html', - FORM_DATA = 'multipart/form-data'; + APP_JSON = 'application/json', + APP_JS = 'application/javascript', + APP_XML = 'application/xml', + TEXT_XML = 'text/xml', + TEXT_PLAIN = 'text/plain', + TEXT_HTML = 'text/html', + FORM_DATA = 'multipart/form-data'; schemaFaker.option({ requiredOnly: false }); - + +/** + * Class for the node of the tree containing the folders + * @param {object} options - Contains details about the folder node + * @returns {void} + */ function Node (options) { this.name = options ? options.name : '/'; this.requestCount = options ? options.requestCount : 0; - this.type = options ? options.type : 'item'; - this.children = {}; // object of objects - this.requests = options ? options.requests : []; // request will be an array of objects + this.type = options ? options.type : 'item'; + this.children = {}; // object of objects + this.requests = options ? options.requests : []; // request will be an array of objects - this.addChildren = function (child, value){ + this.addChildren = function (child, value) { this.children[child] = value; - } - - this.addMethod = function (method){ + }; + + this.addMethod = function (method) { this.requests.push(method); - } + }; } -class Trie{ - constructor(node){ +class Trie { + constructor(node) { this.root = node; } } @@ -42,22 +47,29 @@ module.exports = { /** * Adds the neccessary server variables to the collection + * @param {object} collection - POSTMAN Collection JSON object + * @param {object} serverVariables - Object containing the server variables at the root/path-item level + * @param {string} level - root / path-item level + * @param {string} serverUrl - URL from the server object + * + * @returns {object} modified collection after the addition of the server variables */ - addCollectionVariables: function(collection, serverVariables,level, serverUrl = ''){ + addCollectionVariables: function(collection, serverVariables, level, serverUrl = '') { var modifiedCollection = collection; - if(serverVariables){ + if (serverVariables) { _.forOwn(serverVariables, (value, key) => { - modifiedCollection.variables.add(new sdk.Variable({ - id: key, - value: value.default || '', - description: value.description + (value.enum || ''), - })); + modifiedCollection.variables.add(new sdk.Variable({ + id: key, + value: value.default || '', + description: value.description + (value.enum || '') + })); }); - } else { + } + else { modifiedCollection.variables.add(new sdk.Variable({ id: level, value: serverUrl, - type: 'string', + type: 'string' })); } @@ -66,26 +78,28 @@ module.exports = { /** * Parses an open api string as a YAML or JSON - */ - parseSpec: function (openApiSpec){ + * @param {YAML/JSON} openApiSpec - The swagger 3.0.0 specification specified in either YAML or JSON + * @returns {Object} - Contains the folder trie and the array of collection variables to be created + */ + parseSpec: function (openApiSpec) { var openApiObj = openApiSpec, - rootValidation; + rootValidation; // If the open api specification is a string could be YAML or JSON - if(typeof openApiSpec == 'string'){ - try{ + if (typeof openApiSpec === 'string') { + try { openApiObj = parse.asYaml(openApiSpec); } - catch(yamlException) { - // Could be a JSON as well - try{ + catch (yamlException) { + // Could be a JSON as well + try { openApiObj = parse.asJson(openApiSpec); } - catch(jsonException) { + catch (jsonException) { // Its neither JSON nor YAML return { result: false, reason: 'Invalid format. Input must be in YAML or JSON format' - } + }; } } } @@ -93,148 +107,148 @@ module.exports = { // spec is a valid JSON // Validate the root level object rootValidation = parse.validateRoot(openApiObj); - - if(!rootValidation.result){ + + if (!rootValidation.result) { return { result: false, - reason: rootValidation.reason, - } + reason: rootValidation.reason + }; } // Valid openapi root object return { result: true, - openapi: rootValidation.openapi, - } + openapi: rootValidation.openapi + }; }, - generateTrieFromPaths: function (spec){ + /** + * Generates a Trie like folder structure from the path object of the OPENAPI specification + * @param {Object} spec - specification in json format + * @returns {Object} - The final object consists of the tree structure and collection variables + */ + generateTrieFromPaths: function (spec) { var paths = spec.paths, - pathStrings = Object.keys(paths), - currentPath, - currentPathObject, - commonParams, - methodParams, - collectionVariables = [], - validMethodNames = ['get', 'post', 'put', 'delete', 'patch', 'option', 'head', 'trace'], - root = new Node({ - name: '/', - }), - //creating a root node for the trie (serves as the root dir) - trie = new Trie(root); - - for(path in paths){ - - // decalring a variable to be in this loops context only - let pathLevelServers; + currentPath = '', + currentPathObject = '', + commonParams = '', + collectionVariables = [], + root = new Node({ + name: '/' + }), + method, + operationItem, + pathLevelServers = '', + pathLength, + currentPathRequestCount, + currentNode, + i, + summary, + path, + // creating a root node for the trie (serves as the root dir) + trie = new Trie(root); - currentPathObject = paths[path]; - // console.log("path object = ", currentPathObject); + for (path in paths) { + if (paths.hasOwnProperty(path)) { + // decalring a variable to be in this loops context only + currentPathObject = paths[path]; + // console.log("path object = ", currentPathObject); + // the key should not be empty + if (path[0] === '/') { + path = path.substring(1); + } - // the key should not be empty - if(path[0] == '/'){ - path = path.substring(1); - } + currentPath = path.split('/'); + pathLength = currentPath.length; + currentPathRequestCount = Object.keys(currentPathObject).length; - currentPath = path.split('/'); - pathLength = currentPath.length; - currentPathRequestCount = Object.keys(currentPathObject).length; - - currentNode = trie.root; - - // adding children for the nodes in the trie - for(var i = 0; i < pathLength;i++){ - if(!currentNode.children[currentPath[i]]){ - currentNode.addChildren(currentPath[i], new Node({ + currentNode = trie.root; + + // adding children for the nodes in the trie + for (i = 0; i < pathLength; i++) { + if (!currentNode.children[currentPath[i]]) { + currentNode.addChildren(currentPath[i], new Node({ name: currentPath[i], requestCount: 0, requests: [], children: {}, - type: 'item-group', - })); - } - currentNode.children[currentPath[i]].requestCount += currentPathRequestCount; - currentNode = currentNode.children[currentPath[i]]; - } - - //handling common parameters for all the methods in the current path item - if(currentPathObject.hasOwnProperty('parameters')){ - commonParams = currentPathObject.parameters; - // console.log(' ' ,commonParams); - delete currentPathObject.parameters; - } - - //handling the server object at the path item level - if(currentPathObject.hasOwnProperty('servers')){ - pathLevelServers = currentPathObject.servers; - if(pathLevelServers[0].hasOwnProperty('variables')){ - collectionVariables.push(pathLevelServers[0].variables); - } - } - - - for(method in currentPathObject){ - // console.log(method); - var summary = ''; - - if(currentPathObject[method].hasOwnProperty('summary')){ - summary = currentPathObject[method].summary; - } else { - summary = currentPathObject[method].description; - } - - // extending the parameters array for each method with the common ones - // for ease of accessing - if(commonParams && commonParams.length > 0){ - if(!currentPathObject[method].hasOwnProperty('parameters')) - currentPathObject[method].parameters = commonParams; - // if does should I extend ? (don't know yet) + type: 'item-group' + })); + } + currentNode.children[currentPath[i]].requestCount += currentPathRequestCount; + currentNode = currentNode.children[currentPath[i]]; } - if(validMethodNames.includes(method)){ - currentNode.addMethod({ - name: summary, - id: currentPathObject[method].operationId, - method: method, - path: path, - properties: currentPathObject[method], - type: 'item', - servers: pathLevelServers || undefined, - }); - } + // handling common parameters for all the methods in the current path item + if (currentPathObject.hasOwnProperty('parameters')) { + commonParams = currentPathObject.parameters; + // console.log(' ' ,commonParams); + delete currentPathObject.parameters; + } + + // handling the server object at the path item level + if (currentPathObject.hasOwnProperty('servers')) { + pathLevelServers = currentPathObject.servers; + delete currentPathObject.servers; + if (pathLevelServers[0].hasOwnProperty('variables')) { + collectionVariables.push(pathLevelServers[0].variables); + } + } + + + for (method in currentPathObject) { + if (currentPathObject.hasOwnProperty(method)) { + operationItem = currentPathObject[method]; + operationItem.parameters = operationItem.parameters || commonParams; + summary = operationItem.summary || operationItem.description; + // extending the parameters array for each method with the common ones + // for ease of accessing + + currentNode.addMethod({ + name: summary, + id: operationItem.operationId, + method: method, + path: path, + properties: operationItem, + type: 'item', + servers: pathLevelServers || undefined + }); + } + } } } return { tree: trie, - variables: collectionVariables, + variables: collectionVariables }; }, - // takes in a string url or a server item object - convertPathVariables: function(type, pathVarArray, pathVariables){ - - // console.log("here i am",pathVariables); - + /** + * Converts the path variable in a more accessible format + * @param {string} type - Level at the tree root/path level + * @param {Array} pathVarArray - Array of path variables + * @param {object} pathVariables - Object of path variables taken from the specification + * @returns {Array} returns array of variables + */ + convertPathVariables: function(type, pathVarArray, pathVariables) { var variables = pathVarArray; - - //converting the base uri path variables if any - if(type == 'root'){ - // console.log("base variables", pathVariables); + // converting the base uri path variables if any + if (type === 'root') { _.forOwn(pathVariables, (value, key) => { variables.push({ key: key, value: '{{' + key + '}}', - description: value.enum ? value.description + 'can be only one of' + value.enum.toString() : value.description, - }) + description: value.enum ? value.description + 'can be only one of' + value.enum.toString() : value.description + }); }); - } else { - // console.log("") + } + else { _.forEach(pathVariables, (variable) => { variables.push({ key: variable.name, value: schemaFaker(variable.schema || {}), - description: variable.description || '', + description: variable.description || '' }); }); } @@ -242,30 +256,50 @@ module.exports = { return variables; }, - convertChildToItemGroup: function(openapi, child){ + /** + * Helper function in order to handle query string with delimiters + * @param {String} paramValue - name of the query parameter + * @param {String} delimiter - the delimiter which is to be used + * @returns {String} returns the query string with the delimiter at appropriate points + */ + getQueryStringWithStyle: function(paramValue, delimiter) { + var queryString, + paramNameArray = Object.keys(paramValue), + queryParams = paramNameArray.map((value, index) => { + return value + delimiter + Object.values(paramValue)[index]; + }); + queryString = queryParams.join(delimiter); + return queryString; + }, + + convertChildToItemGroup: function(openapi, child) { var resource = child, - itemGroup, - subChild, - item, - singleRequest; - // resourceUri = new sdk.Url(parentUri + '/' + resource.path) - - if(resource.type == 'item-group'){ + itemGroup, + subChild, + item, + i, + requestCount, + singleRequest; + // resourceUri = new sdk.Url(parentUri + '/' + resource.path) + + if (resource.type === 'item-group') { // the resource should be a folder - if(resource.requestCount > 1){ + if (resource.requestCount > 1) { // definetive conversion itemGroup = new sdk.ItemGroup({ - name: resource.name, + name: resource.name // have to add auth here (first auth to be put in the tree) }); - - for(subChild in resource.children) { - itemGroup.items.add( - this.convertChildToItemGroup(openapi, resource.children[subChild]) - ); + + for (subChild in resource.children) { + if (resource.children.hasOwnProperty(subChild)) { + itemGroup.items.add( + this.convertChildToItemGroup(openapi, resource.children[subChild]) + ); + } } - - for(var i = 0, requestCount = resource.requests.length; i < requestCount;i++){ + + for (i = 0, requestCount = resource.requests.length; i < requestCount; i++) { itemGroup.items.add( this.convertChildToItemGroup(openapi, resource.requests[i]) ); @@ -273,23 +307,23 @@ module.exports = { return itemGroup; } - else { - // single request exists in the folder - // pull it out from there - singleRequest = resource.requests[0]; - return this.convertRequestToItem(openapi, singleRequest); - } + + // single request exists in the folder + // pull it out from there + singleRequest = resource.requests[0]; + return this.convertRequestToItem(openapi, singleRequest); + } item = this.convertRequestToItem(openapi, resource); return item; }, - getAuthHelper: function(openapi, securitySet){ + getAuthHelper: function(openapi, securitySet) { var securityDef, - helper; + helper; - if(!securitySet){ + if (!securitySet) { return { type: 'noauth' }; @@ -297,62 +331,64 @@ module.exports = { _.forEach(securitySet, (security) => { securityDef = openapi.securityDefs[Object.keys(security)[0]]; - if(securityDef.type == 'http'){ - helper = { - type: securityDef.scheme, + if (securityDef.type === 'http') { + helper = { + type: securityDef.scheme }; - return false; - } else if(securityDef.type == 'oauth2') { - helper = { - type: 'oauth2', - }; - return false; - } else if(securityDef.type == 'apiKey'){ - helper = { - type: 'api-key', - properties: securityDef, - }; - return false; } + else if (securityDef.type === 'oauth2') { + helper = { + type: 'oauth2' + }; + } + else if (securityDef.type === 'apiKey') { + helper = { + type: 'api-key', + properties: securityDef + }; + } + return false; }); return helper; }, - getResponseHeaders: function(contentTypes, responseHeaders){ + getResponseHeaders: function(contentTypes, responseHeaders) { var headerArray = [{ - key: 'Content-Type', - value: contentTypes ? (contentTypes[0]) : TEXT_PLAIN, - }], - header; + key: 'Content-Type', + value: contentTypes ? (contentTypes[0]) : TEXT_PLAIN + }], + header; + + if (!responseHeaders) { return headerArray; } - if(!responseHeaders) - return headerArray; - _.forOwn(responseHeaders, (value, key) => { header = { key: key, value: schemaFaker(value.schema || {}), - description: value.description || '', - } + description: value.description || '' + }; headerArray.push(header); }); return headerArray; }, - getResponseBody: function(openapi, contentObj){ + getResponseBody: function(openapi, contentObj) { var responseBody; - if(!contentObj){ - return ""; + if (!contentObj) { + return ''; } - if(contentObj[APP_JSON]){ + if (contentObj[APP_JSON]) { responseBody = this.getBodyData(openapi, contentObj[APP_JSON]); - } else if(contentObj[APP_XML]){ + } + else if (contentObj[APP_XML]) { responseBody = this.getBodyData(openapi, contentObj[APP_XML]); - } else if(contentObj[APP_JS]){ + } + else if (contentObj[APP_JS]) { responseBody = this.getBodyData(openapi, contentObj[APP_XML]); - } else if(contentObj[TEXT_PLAIN]){ + } + else if (contentObj[TEXT_PLAIN]) { responseBody = this.getBodyData(openapi, contentObj[TEXT_PLAIN]); } @@ -360,35 +396,36 @@ module.exports = { }, // map for creating parameters specific for a request - getParametersForPathItem: function(openapi, localParams){ + getParametersForPathItem: function(openapi, localParams) { var tempParam, - params = { - query: [], - header: [], - path: [], - }; - - _.forEach(localParams, (param) => { - tempParam = param; + params = { + query: [], + header: [], + path: [] + }; - if(tempParam.in == 'query'){ - params.query.push(tempParam); - } else if(tempParam.in == 'header'){ - params.header.push(tempParam); - } else if(tempParam.in == 'path'){ - params.path.push(tempParam); - } + _.forEach(localParams, (param) => { + tempParam = param; + + if (tempParam.in === 'query') { + params.query.push(tempParam); + } + else if (tempParam.in === 'header') { + params.header.push(tempParam); + } + else if (tempParam.in === 'path') { + params.path.push(tempParam); + } }); return params; }, - getExampleData: function(openapi, exampleObj){ - var exampleData, - example, - exampleKey; + getExampleData: function(openapi, exampleObj) { + var example, + exampleKey; - if(exampleObj){ + if (exampleObj) { return {}; } @@ -398,197 +435,184 @@ module.exports = { return example; }, - getBodyData: function(openapi, bodyObj){ + getBodyData: function(openapi, bodyObj) { var bodyData = ''; - if(bodyObj.hasOwnProperty('schema')) { + if (bodyObj.hasOwnProperty('schema')) { bodyData = schemaFaker(bodyObj.schema || {}); - } else if(bodyObj.hasOwnProperty('examples')) { + } + else if (bodyObj.hasOwnProperty('examples')) { bodyData = this.getExampleData(openapi, bodyObj.examples); // take one of the examples as the body and not all - } else if(bodyObj.hasOwnProperty('example')){ + } + else if (bodyObj.hasOwnProperty('example')) { bodyData = bodyObj.example; } return bodyData; }, - addQueryParameters: function(requestObj, queryParameters){ + addQueryParameters: function(requestObj, queryParameters) { var modifiedRequestObj = requestObj, - modifiedUrl = requestObj.url.toString(), - queryParam, - paramValue; + modifiedUrl = requestObj.url.toString(), + queryParams, + paramNameArray, + handleExplode = (explodeFlag, paramValue, paramName) => { + if (explodeFlag) { + paramNameArray = Array.from(Array(paramValue.length), () => { return paramName; }); + queryParams = paramNameArray.map((value, index) => { return (value + '=' + paramValue[index]); }); + modifiedUrl += queryParams.join('&'); + } + else { + modifiedUrl += paramName + '=' + paramValue.join(','); + } + return modifiedUrl; + }, + paramValue; - if(queryParameters == []){ + if (queryParameters === []) { return requestObj; } - // console.log("modified url => ", modifiedUrl); - - modifiedUrl += '?' + modifiedUrl += '?'; _.forEach(queryParameters, (param) => { - // check for existence of schema - if(param.hasOwnProperty('schema')){ - paramValue = schemaFaker(param.schema); // fake data generated - paramType = param.schema.type; - // checking the type of the query parameter - if(paramType == 'array'){ - // check for the existence of the style property - if(param.hasOwnProperty('style')){ - // which style is there ? - if(param.style == 'form'){ - // check for the existence of explode property - if(param.hasOwnProperty('explode')){ - // default is true for form so must be false here - modifiedUrl += param.name + '='; - _.forEach(paramValue, (value) => { - modifiedUrl += value + ','; - }); - modifiedUrl = modifiedUrl.slice(0,-1); - } else { - _.forEach(paramValue, (value) => { - modifiedUrl += param.name + '=' + value + '&'; - }); - modifiedUrl = modifiedUrl.slice(0, -1); - } - } else if(param.style == 'spaceDelimited'){ - // explode parameter doesn't really affect anything here - modifiedUrl += param.name + '='; - _.forEach(paramValue, (value) => { - modifiedUrl += value + '%20'; - }); - modifiedUrl = modifiedUrl.slice(0, -3); - } else if(param.style == 'pipeDelimited'){ - modifiedUrl += param.name + '='; - _.forEach(paramValue, (value) => { - modifiedUrl += value + '|'; - }); - modifiedUrl = modifiedUrl.slice(0, -1); - } - } else { - // if there is not style parameter we assume that it will be form by default; - queryParam = new sdk.QueryParam({ - key: param.name, - value: schemaFaker(param.schema), - }); - queryParam.description = param.description; - modifiedRequestObj.addQueryParams(queryParam); + // check for existence of schema + if (param.hasOwnProperty('schema')) { + paramValue = schemaFaker(param.schema); // fake data generated + paramType = param.schema.type; + // checking the type of the query parameter + if (paramType === 'array') { + // check for the existence of the style property + if (param.hasOwnProperty('style')) { + // which style is there ? + if (param.style === 'form') { + // check for the truthness of explode + modifiedUrl = handleExplode(param.explode, paramValue, param.name); + + // if (param.explode) { + // paramNameArray = Array.from(Array(paramValue.length), () => { return param.name; }); + // queryParams = paramNameArray.map((value, index) => { return (value + '=' + paramValue[index]); }); + // modifiedUrl += queryParams.join('&'); + // } + // else { + // modifiedUrl += param.name + '=' + paramValue.join(','); + // } } - } else if(paramType == 'object'){ - // following similar checks as above - if(param.hasOwnProperty('style')){ - if(param.style == 'form'){ - if(param.hasOwnProperty('explode')){ - // false if present - modifiedUrl += param.name + '='; - _.forOwn(paramValue, (value, key) => { - modifiedUrl += key + ',' + value + ','; - }); - modifiedUrl = modifiedUrl.slice(0, -1); - } else { - // true by default - _.forOwn(paramValue, (value, key) => { - modifiedUrl += key + '=' + value + '&'; - }); - modifiedUrl = modifiedUrl.slice(0, -1); - } - } else if (param.style == 'spaceDelimited'){ - _.forOwn(paramValue, (value, key) => { - modifiedUrl += key + '%20' + value + '%20'; - }); - modifiedUrl = modifiedUrl.slice(0, -3); - - } else if (param.style == 'pipeDelimited'){ - _.forOwn(paramValue, (value, key) => { - modifiedUrl += key + '|' + value + '|'; - }); - modifiedUrl = modifiedUrl.slice(0, -1); - - } else if (param.style == 'deepObject'){ - _.forOwn(paramValue, (value, key) => { - modifiedUrl += param.name + '[' + key + ']' + '=' + value + '&'; - }); - modifiedUrl = modifiedUrl.slice(0, -1); - } - } else { - modifiedUrl += param.name + '=' + paramValue; + else if (param.style === 'spaceDelimited') { + // explode parameter doesn't really affect anything here + modifiedUrl += param.name + '=' + paramValue.join('%20'); + } + else if (param.style === 'pipeDelimited') { + modifiedUrl += param.name + '=' + paramValue.join('|'); } - } else { - // for all the primitive types - modifiedUrl += param.name + '=' + paramValue; } - } else { - // since schema was not present (it wasnt a required property) - // using the max info I can from the spec - - modifiedUrl += param.name + '='; + else { + // if there is not style parameter we assume that it will be form by default; + modifiedUrl += param.name + '=' + paramValue.join(','); + } } - - modifiedUrl += '&' + else if (paramType === 'object') { + // following similar checks as above + if (param.hasOwnProperty('style')) { + if (param.style === 'form') { + if (param.explode) { + // if explode is true + paramNameArray = Object.keys(paramValue); + queryParams = paramNameArray.map((value, index) => { + return (value + '=' + Object.values(paramValue)[index]); + }); + modifiedUrl += queryParams.join('&'); + } + else { + modifiedUrl += this.getQueryStringWithStyle(paramValue, ','); + } + } + else if (param.style === 'spaceDelimited') { + modifiedUrl += this.getQueryStringWithStyle(paramValue, '%20'); + } + else if (param.style === 'pipeDelimited') { + modifiedUrl += this.getQueryStringWithStyle(paramValue, '|'); + } + else if (param.style === 'deepObject') { + _.forOwn(paramValue, (value, key) => { + modifiedUrl += param.name + '[' + key + ']=' + value + '&'; + }); + modifiedUrl = modifiedUrl.slice(0, -1); + } + } + else { + modifiedUrl += param.name + '=' + (paramValue); + } + } + else { + modifiedUrl += param.name + '=' + (paramValue); + } + } + else { + // since no schema present add the parameter with no value + modifiedUrl += param.name + '='; + } + modifiedUrl += '&'; }); - modifiedUrl = modifiedUrl.slice(0, -1); - // updating the request url modifiedRequestObj.url = new sdk.Url(modifiedUrl); - return modifiedRequestObj; }, - addHeaders: function(postmanItemObj, headers){ + addHeaders: function(postmanItemObj, headers) { var modifiedPostmanItemObj = postmanItemObj, - fakeData, - reqHeader, - description; - if(headers == []){ + fakeData, + reqHeader; + if (headers === []) { return modifedPostmanItemObj; } _.forEach(headers, (header) => { - if(header.hasOwnProperty('schema')){ - fakeData = schemaFaker(header.schema || {}); - } else { - fakeData = ''; - } + if (header.hasOwnProperty('schema')) { + fakeData = schemaFaker(header.schema || {}); + } + else { + fakeData = ''; + } - reqHeader = new sdk.Header({ - key: header.name, - value: fakeData, - }); - reqHeader.description = header.description; - modifiedPostmanItemObj.request.addHeader(reqHeader); + reqHeader = new sdk.Header({ + key: header.name, + value: fakeData + }); + reqHeader.description = header.description; + modifiedPostmanItemObj.request.addHeader(reqHeader); }); return modifiedPostmanItemObj; }, - addRequestBody: function(postmanItemObj, requestBody, openapi){ - var modifiedPostmanItemObj = postmanItemObj, - contentObj, // content is required - bodyData, - param, - paramArray = [], - updateOptions = {}, - reqBody = new sdk.RequestBody(), - rDataMode; - - if(!requestBody){ + addRequestBody: function(postmanItemObj, requestBody, openapi) { + var modifiedPostmanItemObj = postmanItemObj, + contentObj, // content is required + bodyData, + param, + paramArray = [], + updateOptions = {}, + reqBody = new sdk.RequestBody(), + rDataMode; + + if (!requestBody) { return postmanItemObj; } - // how do I support multiple content types - contentObj = requestBody.content + // how do I support multiple content types + contentObj = requestBody.content; // handling for the urlencoded media type - if(contentObj.hasOwnProperty(URLENCODED)){ + if (contentObj.hasOwnProperty(URLENCODED)) { rDataMode = 'urlencoded'; bodyData = this.getBodyData(openapi, contentObj[URLENCODED]); // create query parameters and add it to the request body object _.forOwn(bodyData, (value, key) => { - if(typeof value == 'object') - value = JSON.stringify(value); + if (typeof value === 'object') { value = JSON.stringify(value); } param = new sdk.QueryParam({ key: key, - value: value, + value: value }); paramArray.push(param); }); @@ -600,62 +624,58 @@ module.exports = { // add a content type header for each media type for the request body modifiedPostmanItemObj.request.addHeader(new sdk.Header({ key: 'Content-Type', - value: URLENCODED, + value: URLENCODED })); // update the request body with the options reqBody.update(updateOptions); - } else if (contentObj.hasOwnProperty(FORM_DATA)){ - rDataMode = 'formdata'; - bodyData = this.getBodyData(openapi, contentObj[FORM_DATA]); - // create the form parameters and add it to the request body object - _.forOwn(bodyData, (value, key) => { - if(typeof value == 'object') - value = JSON.stringify(value); + } + else if (contentObj.hasOwnProperty(FORM_DATA)) { + rDataMode = 'formdata'; + bodyData = this.getBodyData(openapi, contentObj[FORM_DATA]); + // create the form parameters and add it to the request body object + _.forOwn(bodyData, (value, key) => { + if (typeof value === 'object') { value = JSON.stringify(value); } - param = new sdk.FormParam({ - key: key, - value: value, - }); - paramArray.push(param); + param = new sdk.FormParam({ + key: key, + value: value }); - updateOptions = { - mode: rDataMode, - formdata: paramArray - }; - // add a content type header for the pertaining media type - modifiedPostmanItemObj.request.addHeader(new sdk.Header({ - key: 'Content-Type', - value: FORM_DATA, - })); - // update the request body - reqBody.update(updateOptions); - } else { + paramArray.push(param); + }); + updateOptions = { + mode: rDataMode, + formdata: paramArray + }; + // add a content type header for the pertaining media type + modifiedPostmanItemObj.request.addHeader(new sdk.Header({ + key: 'Content-Type', + value: FORM_DATA + })); + // update the request body + reqBody.update(updateOptions); + } + else { rDataMode = 'raw'; let bodyType; - // checking for all possible raw types - if(contentObj.hasOwnProperty(APP_JS)) - bodyType = APP_JS; - else if(contentObj.hasOwnProperty(APP_JSON)) - bodyType = APP_JSON; - else if(contentObj.hasOwnProperty(TEXT_HTML)) - bodyType = TEXT_HTML; - else if(contentObj.hasOwnProperty(TEXT_PLAIN)) - bodyType = TEXT_PLAIN; - else if(contentObj.hasOwnProperty(TEXT_XML)) - bodyType = TEXT_XML; + // checking for all possible raw types + if (contentObj.hasOwnProperty(APP_JS)) { bodyType = APP_JS; } + else if (contentObj.hasOwnProperty(APP_JSON)) { bodyType = APP_JSON; } + else if (contentObj.hasOwnProperty(TEXT_HTML)) { bodyType = TEXT_HTML; } + else if (contentObj.hasOwnProperty(TEXT_PLAIN)) { bodyType = TEXT_PLAIN; } + else if (contentObj.hasOwnProperty(TEXT_XML)) { bodyType = TEXT_XML; } bodyData = this.getBodyData(openapi, contentObj[bodyType]); updateOptions = { mode: rDataMode, - raw: JSON.stringify(bodyData, null, 4), - } + raw: JSON.stringify(bodyData, null, 4) + }; modifiedPostmanItemObj.request.addHeader(new sdk.Header({ key: 'Content-Type', - value: bodyType, + value: bodyType })); reqBody.update(updateOptions); @@ -665,20 +685,20 @@ module.exports = { return modifiedPostmanItemObj; }, - addResponse: function(postmanItemObj, responses, openapi){ + addResponse: function(postmanItemObj, responses, openapi) { var modifedPostmanItemObj = postmanItemObj, - response; - - if(!responses){ - return responseDefArray; + response; + + if (!responses) { + return postmanItemObj; } _.forOwn(responses, (value, code) => { response = new sdk.Response({ name: code.toString(), - code: code == 'default' ? 500 : Number(code), - header: this.getResponseHeaders(Object.keys(value.content || {}),value.headers), - body: this.getResponseBody(openapi, value.content), + code: code === 'default' ? 500 : Number(code), + header: this.getResponseHeaders(Object.keys(value.content || {}), value.headers), + body: this.getResponseBody(openapi, value.content) }); modifedPostmanItemObj.responses.add(response); }); @@ -687,41 +707,36 @@ module.exports = { }, // function to convert an openapi path item to postman item - convertRequestToItem: function(openapi, operationItem){ + convertRequestToItem: function(openapi, operationItem) { // console.log(openapi.components) + var reqName, + itemName = operationItem.id, + pathVariables = openapi.baseUrlVariables, + reqBody = operationItem.properties.requestBody, + itemParams = operationItem.properties.parameters, + reqParams = this.getParametersForPathItem(openapi, itemParams), + baseUrl = openapi.baseUrl, + pathVarArray, + authHelper, + item, + serverObj, + displayUrl, + reqUrl = '/' + operationItem.path; - - var reqUrl, - reqName, - itemName = operationItem.id, - authHelpers, - pathVariables = openapi.baseUrlVariables, - baseParams = openapi.components.parameters, - reqBody = operationItem.properties.requestBody, - itemParams = operationItem.properties.parameters, - reqParams = this.getParametersForPathItem(openapi, itemParams), - baseUrl = openapi.baseUrl, - pathVarArray, - serverObj, - displayUrl, - - - - reqUrl = '/' + operationItem.path; - // handling path templating in request url if any reqUrl = reqUrl.replace(/{/g, ':').replace(/}/g, ''); - + // accounting for the overriding of the root level servers object if present at the path level - if(operationItem.hasOwnProperty('servers') && operationItem.servers){ + if (operationItem.hasOwnProperty('servers') && operationItem.servers) { serverObj = operationItem.servers[0]; - baseUrl = serverObj.url.replace(/{/g, ':').replace(/}/g, '');; + baseUrl = serverObj.url.replace(/{/g, ':').replace(/}/g, ''); pathVariables = serverObj.variables; - } else { - baseUrl = baseUrl + reqUrl; + } + else { + baseUrl += reqUrl; displayUrl = '{{base-url}}' + reqUrl; } - + pathVarArray = this.convertPathVariables('root', [], pathVariables); reqName = operationItem.id; @@ -734,35 +749,37 @@ module.exports = { request: { url: displayUrl || baseUrl, name: reqName, - method: operationItem.method.toUpperCase(), + method: operationItem.method.toUpperCase() } }); // using the auth helper - if(authHelper.type == 'api-key'){ - if(authHelper.properties.in == 'header'){ + if (authHelper.type === 'api-key') { + if (authHelper.properties.in === 'header') { item = this.addHeaders(item, [authHelper.properties]); item.request.auth = { type: 'noauth' - } - } else if(authHelper.properties.in == 'query'){ + }; + } + else if (authHelper.properties.in === 'query') { item.request = this.addQueryParameters(item.request, [authHelper.properties]); item.request.auth = { type: 'noauth' - } + }; } - } else { + } + else { item.request.auth = authHelper; } // console.log("path params = ", reqParams.path, pathVarArray) - item.request = this.addQueryParameters(item.request, reqParams.query); + item.request = this.addQueryParameters(item.request, reqParams.query); // item.request.url.variable = this.convertPathVariables('method', pathVarArray, reqParams.path); item.request.url.variables = this.convertPathVariables('method', pathVarArray, reqParams.path); - item = this.addHeaders(item, reqParams.header); + item = this.addHeaders(item, reqParams.header); item = this.addRequestBody(item, reqBody, openapi); @@ -770,5 +787,5 @@ module.exports = { return item; } -} +}; diff --git a/lib/validate.js b/lib/validate.js index 5e08804..945777f 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -1,16 +1,16 @@ var util = require('./util.js'); -module.exports = function(jsonOrString){ +module.exports = function(jsonOrString) { var parseResult = util.parseSpec(jsonOrString); - if(!parseResult.result){ + if (!parseResult.result) { return { result: false, - reason: parseResult.reason, + reason: parseResult.reason }; } return { - result: true, + result: true }; -} \ No newline at end of file +};