UNPKG

openapi-to-postmanv2

Version:

Convert a given OpenAPI specification to Postman Collection v2.0

500 lines (422 loc) 14.2 kB
let _ = require('lodash'), Graph = require('graphlib').Graph, { resolveRefFromSchema } = require('../../schemaUtils'), PATH_WEBHOOK = 'path~webhook', ALLOWED_HTTP_METHODS = { get: true, head: true, post: true, put: true, patch: true, delete: true, connect: true, options: true, trace: true }, _generateTreeFromPathsV2 = function (context, openapi, { includeDeprecated }) { /** * We will create a unidirectional graph */ let tree = new Graph(); tree.setNode('root:collection', { type: 'collection', data: {}, meta: {} }); /** * Get all the paths sorted in desc order. */ const paths = Object.keys(openapi.paths); if (_.isEmpty(paths)) { return tree; } _.forEach(paths, function (completePath) { let pathSplit = completePath === '/' ? [completePath] : _.compact(completePath.split('/')); /** * /user * /team * /hi * /bye * * In this scenario, always create a base folder for the path * and then add and link the request inside the created folder. */ if (pathSplit.length === 1) { let methods = openapi.paths[completePath]; if (methods && methods.$ref) { methods = resolveRefFromSchema(context, methods.$ref); } _.forEach(methods, function (data, method) { if (!ALLOWED_HTTP_METHODS[method]) { return; } /** * include deprecated handling. * If true, add in the postman collection. If false ignore the request. */ if (!includeDeprecated && data.deprecated) { return; } if (!tree.hasNode(`path:folder:${pathSplit[0]}`)) { tree.setNode(`path:folder:${pathSplit[0]}`, { type: 'folder', meta: { name: pathSplit[0], path: pathSplit[0], pathIdentifier: pathSplit[0] }, data: {} }); tree.setEdge('root:collection', `path:folder:${pathSplit[0]}`); } tree.setNode(`path:request:${pathSplit[0]}:${method}`, { type: 'request', data: {}, meta: { path: completePath, method: method, pathIdentifier: pathSplit[0] } }); tree.setEdge(`path:folder:${pathSplit[0]}`, `path:request:${pathSplit[0]}:${method}`); }); } else { _.forEach(pathSplit, function (path, index) { let previousPathIdentified = pathSplit.slice(0, index).join('/'), pathIdentifier = pathSplit.slice(0, index + 1).join('/'); if ((index + 1) === pathSplit.length) { let methods = openapi.paths[completePath]; if (methods && methods.$ref) { methods = resolveRefFromSchema(context, methods.$ref); } _.forEach(methods, function (data, method) { if (!ALLOWED_HTTP_METHODS[method]) { return; } /** * include deprecated handling. * If true, add in the postman collection. If false ignore the request. */ if (!includeDeprecated && data.deprecated) { return; } /** * If it is the last node, * it might happen that this exists as a folder. * * If yes add a request inside that folder else * add as a request on the previous path idendified which will be a folder. */ if (!tree.hasNode(`path:folder:${pathIdentifier}`)) { tree.setNode(`path:folder:${pathIdentifier}`, { type: 'folder', meta: { name: path, path: path, pathIdentifier: pathIdentifier }, data: {} }); tree.setEdge(index === 0 ? 'root:collection' : `path:folder:${previousPathIdentified}`, `path:folder:${pathIdentifier}`); } tree.setNode(`path:request:${pathIdentifier}:${method}`, { type: 'request', data: {}, meta: { path: completePath, method: method, pathIdentifier: pathIdentifier } }); tree.setEdge(`path:folder:${pathIdentifier}`, `path:request:${pathIdentifier}:${method}`); }); } else { let fromNode = index === 0 ? 'root:collection' : `path:folder:${previousPathIdentified}`, toNode = `path:folder:${pathIdentifier}`; if (!tree.hasNode(toNode)) { tree.setNode(toNode, { type: 'folder', meta: { name: path, path: path, pathIdentifier: pathIdentifier }, data: {} }); } if (!tree.hasEdge(fromNode, toNode)) { tree.setEdge(fromNode, toNode); } } }); } }); return tree; }, _generateTreeFromTags = function (context, openapi, { includeDeprecated }) { let tree = new Graph(), tagDescMap = _.reduce(openapi.tags, function (acc, data) { acc[data.name] = data.description; return acc; }, {}); tree.setNode('root:collection', { type: 'collection', data: {}, meta: {} }); /** * Create folders for all the tags present. */ _.forEach(tagDescMap, function (desc, tag) { if (tree.hasNode(`path:${tag}`)) { return; } /** * Generate a folder node and attach to root of collection. */ tree.setNode(`path:${tag}`, { type: 'folder', meta: { path: '', name: tag, description: tagDescMap[tag] }, data: {} }); tree.setEdge('root:collection', `path:${tag}`); }); _.forEach(openapi.paths, function (methods, path) { if (methods && methods.$ref) { methods = resolveRefFromSchema(context, methods.$ref); } _.forEach(methods, function (data, method) { if (!ALLOWED_HTTP_METHODS[method]) { return; } /** * include deprecated handling. * If true, add in the postman collection. If false ignore the request. */ if (!includeDeprecated && data.deprecated) { return; } /** * For all the tags present. Make that request to be * referenced in all the folder which are applicable. */ if (data.tags && data.tags.length > 0) { _.forEach(data.tags, function (tag) { tree.setNode(`path:${tag}:${path}:${method}`, { type: 'request', data: {}, meta: { tag: tag, path: path, method: method } }); // safeguard just in case there is no folder created for this tag. if (!tree.hasNode(`path:${tag}`)) { tree.setNode(`path:${tag}`, { type: 'folder', meta: { path: path, name: tag, description: tagDescMap[tag] }, data: {} }); tree.setEdge('root:collection', `path:${tag}`); } tree.setEdge(`path:${tag}`, `path:${tag}:${path}:${method}`); }); } else { tree.setNode(`path:${path}:${method}`, { type: 'request', data: {}, meta: { path: path, method: method } }); tree.setEdge('root:collection', `path:${path}:${method}`); } }); }); return tree; }, /** * Generates tree structure with nested folders based on tag order * @param {Object} context - Global context object * @param {Object} openapi - OpenAPI specification * @param {Object} options - Generation options * @param {boolean} options.includeDeprecated - Whether to include deprecated operations * @returns {Object} - Graph tree with nested folder structure */ _generateTreeFromNestedTags = function (context, openapi, { includeDeprecated }) { let tree = new Graph(), tagDescMap = _.reduce(openapi.tags, function (acc, data) { acc[data.name] = data.description; return acc; }, {}); tree.setNode('root:collection', { type: 'collection', data: {}, meta: {} }); /** * Helper function to create nested folder structure for tags * @param {Array} tags - Array of tags to create nested folders for * @returns {String} - Node ID of the deepest folder created */ const createNestedFolders = function (tags) { if (!tags || tags.length === 0) { return 'root:collection'; } let parentNodeId = 'root:collection'; // Create nested folder structure based on tag order for (let i = 0; i < tags.length; i++) { const tag = tags[i], folderPath = tags.slice(0, i + 1).join(':'), currentNodeId = `path:${folderPath}`; // Create folder node if it doesn't exist if (!tree.hasNode(currentNodeId)) { tree.setNode(currentNodeId, { type: 'folder', meta: { path: '', name: tag, description: tagDescMap[tag] || '' }, data: {} }); // Connect to parent (either root collection or previous folder) tree.setEdge(parentNodeId, currentNodeId); } parentNodeId = currentNodeId; } return parentNodeId; }; _.forEach(openapi.paths, function (methods, path) { if (methods && methods.$ref) { methods = resolveRefFromSchema(context, methods.$ref); } _.forEach(methods, function (data, method) { if (!ALLOWED_HTTP_METHODS[method]) { return; } /** * include deprecated handling. * If true, add in the postman collection. If false ignore the request. */ if (!includeDeprecated && data.deprecated) { return; } /** * Create nested folder structure based on tags order * and place the request in the deepest folder */ if (data.tags && data.tags.length > 0) { // Create nested folder structure and get the deepest folder node const deepestFolderNodeId = createNestedFolders(data.tags), // Create a unique request node ID (one per operation) requestNodeId = `request:${path}:${method}`; tree.setNode(requestNodeId, { type: 'request', data: {}, meta: { tags: data.tags, path: path, method: method } }); // Connect request to the deepest folder tree.setEdge(deepestFolderNodeId, requestNodeId); } else { // No tags - place request directly under root collection tree.setNode(`path:${path}:${method}`, { type: 'request', data: {}, meta: { path: path, method: method } }); tree.setEdge('root:collection', `path:${path}:${method}`); } }); }); return tree; }, _generateWebhookEndpoints = function (context, openapi, tree, { includeDeprecated }) { if (!_.isEmpty(openapi.webhooks)) { tree.setNode(`${PATH_WEBHOOK}:folder`, { type: 'webhook~folder', meta: { path: 'webhook~folder', name: 'webhook~folder', description: '' }, data: {} }); tree.setEdge('root:collection', `${PATH_WEBHOOK}:folder`); } _.forEach(openapi.webhooks, function (methodData, path) { if (methodData && methodData.$ref) { methodData = resolveRefFromSchema(context, methodData.$ref); } _.forEach(methodData, function (data, method) { /** * include deprecated handling. * If true, add in the postman collection. If false ignore the request. */ if (!includeDeprecated && data.deprecated) { return; } tree.setNode(`${PATH_WEBHOOK}:${path}:${method}`, { type: 'webhook~request', meta: { path: path, method: method }, data: {} }); tree.setEdge(`${PATH_WEBHOOK}:folder`, `${PATH_WEBHOOK}:${path}:${method}`); }); }); return tree; }; /** * Used to generate a tree skeleton for the openapi which will be a collection * * @param {Object} context - Global context object * @param {Object} openapi - openapi schema paths in question * @param {String} stratergy='PATHS' * * @returns {Object} - tree format */ module.exports = function (context, openapi, { folderStrategy, includeWebhooks, includeDeprecated, nestedFolderHierarchy }) { let skeletonTree; switch (folderStrategy) { case 'tags': if (nestedFolderHierarchy) { skeletonTree = _generateTreeFromNestedTags(context, openapi, { includeDeprecated }); } else { skeletonTree = _generateTreeFromTags(context, openapi, { includeDeprecated }); } break; case 'paths': skeletonTree = _generateTreeFromPathsV2(context, openapi, { includeDeprecated }); break; default: throw new Error('generateSkeletonTreeFromOpenAPI~folderStrategy not valid'); } if (includeWebhooks) { skeletonTree = _generateWebhookEndpoints(context, openapi, skeletonTree, { includeDeprecated }); } return skeletonTree; };