Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Early start to supporting sourcemaps. #224

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions bin/openapi2postmanv2.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ var program = require('commander'),
outputFile,
prettyPrintFlag,
testFlag,
sourceMapFile,
swaggerInput,
swaggerData;
swaggerData,
sourceMapData;

program
.version(require('../package.json').version, '-v, --version')
.option('-s, --spec <spec>', 'Convert given OPENAPI 3.0.0 spec to Postman Collection v2.0')
.option('-o, --output <output>', 'Write the collection to an output file')
.option('-m, --sourceMap <source-map>', 'Source map to use for operation to request mapping')
.option('-t, --test', 'Test the OPENAPI converter')
.option('-p, --pretty', 'Pretty print the JSON file');

Expand Down Expand Up @@ -41,6 +44,7 @@ inputFile = program.spec;
outputFile = program.output || false;
testFlag = program.test || false;
prettyPrintFlag = program.pretty || false;
sourceMapFile = program.sourceMap;
swaggerInput;
swaggerData;

Expand All @@ -52,7 +56,7 @@ swaggerData;
* @param {Object} collection - POSTMAN collection object
* @returns {void}
*/
function writetoFile(prettyPrintFlag, file, collection) {
function writetoFile(prettyPrintFlag, file, collection, sourceMapFile, sourceMap) {
if (prettyPrintFlag) {
fs.writeFile(file, JSON.stringify(collection, null, 4), (err) => {
if (err) { console.log('Could not write to file', err); }
Expand All @@ -65,17 +69,25 @@ function writetoFile(prettyPrintFlag, file, collection) {
console.log('Conversion successful', 'Collection written to file');
});
}

if (sourceMapFile) {
fs.writeFile(sourceMapFile, JSON.stringify(sourceMap), (err) => {
if (err) { console.log('Could not write to source map file', err); }
console.log('Source Map written to file');
});
}
}

/**
* Helper function for the CLI to convert swagger data input
* @param {String} swaggerData - swagger data used for conversion input
* @returns {void}
*/
function convert(swaggerData) {
function convert(swaggerData, sourceMapData) {
Converter.convert({
type: 'string',
data: swaggerData
data: swaggerData,
sourceMap: sourceMapData ? JSON.parse(sourceMapData) : ''
}, {}, (err, status) => {
if (err) {
return console.error(err);
Expand All @@ -87,7 +99,7 @@ function convert(swaggerData) {
else if (outputFile) {
let file = path.resolve(outputFile);
console.log('Writing to file: ', prettyPrintFlag, file, status); // eslint-disable-line no-console
writetoFile(prettyPrintFlag, file, status.output[0].data);
writetoFile(prettyPrintFlag, file, status.output[0].data, sourceMapFile, status.sourceMap);
}
else {
console.log(status.output[0].data); // eslint-disable-line no-console
Expand All @@ -107,7 +119,14 @@ else if (inputFile) {
// this will fix https://github.com/postmanlabs/openapi-to-postman/issues/4
// inputFile should be read from the cwd, not the path of the executable
swaggerData = fs.readFileSync(inputFile, 'utf8');
convert(swaggerData);
if (sourceMapFile) {
try {
sourceMapData = fs.readFileSync(sourceMapFile, 'utf8');
} catch (_e) {
sourceMapData = '{}';
}
}
convert(swaggerData, sourceMapData);
}
else {
program.emit('--help');
Expand Down
81 changes: 58 additions & 23 deletions lib/schemaUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ schemaFaker.option({
ignoreMissingRefs: true
});

sdk.Request.prototype._postman_propertyRequiresId = true;

/**
*
* @param {*} input - input string that needs to be hashed
Expand Down Expand Up @@ -535,9 +537,10 @@ module.exports = {
* resolve references while generating params.
* @param {object} options - a standard list of options that's globally passed around. Check options.js for more.
* @param {object} schemaCache - object storing schemaFaker and schmeResolution caches
* @param {object} sourceMap - object storing a map between OpenAPI elements and Postman collection elements.
* @returns {void} - generatedStore is modified in-place
*/
addCollectionItemsUsingPaths: function (spec, generatedStore, components, options, schemaCache) {
addCollectionItemsUsingPaths: function (spec, generatedStore, components, options, schemaCache, sourceMap) {
var folderTree,
folderObj,
child,
Expand Down Expand Up @@ -579,7 +582,7 @@ module.exports = {
if (folderTree.root.children.hasOwnProperty(child) && folderTree.root.children[child].requestCount > 0) {
generatedStore.collection.items.add(
this.convertChildToItemGroup(spec, folderTree.root.children[child],
components, options, schemaCache, variableStore)
components, options, schemaCache, variableStore, sourceMap)
);
}
}
Expand All @@ -604,9 +607,10 @@ module.exports = {
* resolve references while generating params.
* @param {object} options - a standard list of options that's globally passed around. Check options.js for more.
* @param {object} schemaCache - object storing schemaFaker and schmeResolution caches
* @param {object} sourceMap - object storing a map between OpenAPI elements and Postman collection elements.
* @returns {object} returns an object containing objects of tags and their requests
*/
addCollectionItemsUsingTags: function(spec, generatedStore, components, options, schemaCache) {
addCollectionItemsUsingTags: function(spec, generatedStore, components, options, schemaCache, sourceMap) {
var globalTags = spec.tags || [],
paths = spec.paths || {},
pathMethods,
Expand Down Expand Up @@ -683,7 +687,7 @@ module.exports = {
};

generatedStore.collection.items.add(this.convertRequestToItem(
spec, tempRequest, components, options, schemaCache, variableStore));
spec, tempRequest, components, options, schemaCache, variableStore, sourceMap));
}
else {
_.forEach(localTags, (localTag) => {
Expand Down Expand Up @@ -711,13 +715,17 @@ module.exports = {
// Add all folders created from tags and corresponding operations
// Iterate from bottom to top order to maintain tag order in spec
_.forEachRight(tagFolders, (tagFolder, tagName) => {
const sourceMapKey = `#/folders/${encodeURIComponent(resource.name)}`;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be tagName

var itemGroup = new sdk.ItemGroup({
id: sourceMap[sourceMapKey] || undefined,
name: tagName,
description: tagFolder.description
});
sourceMap[sourceMapKey] = itemGroup.id;

_.forEach(tagFolder.requests, (request) => {
itemGroup.items.add(this.convertRequestToItem(spec, request, components, options, schemaCache, variableStore));
itemGroup.items.add(this.convertRequestToItem(spec, request, components, options,
schemaCache, variableStore, sourceMap));
});

// Add folders first (before requests) in generated collection
Expand Down Expand Up @@ -810,10 +818,11 @@ module.exports = {
* @param {object} options - a standard list of options that's globally passed around. Check options.js for more.
* @param {object} schemaCache - object storing schemaFaker and schmeResolution caches
* @param {object} variableStore - array for storing collection variables
* @param {object} sourceMap - object storing a map between OpenAPI elements and Postman collection elements.
* @returns {*} Postman itemGroup or request
* @no-unit-test
*/
convertChildToItemGroup: function (openapi, child, components, options, schemaCache, variableStore) {
convertChildToItemGroup: function (openapi, child, components, options, schemaCache, variableStore, sourceMap) {
options = _.merge({}, defaultOptions, options);

var resource = child,
Expand All @@ -827,12 +836,15 @@ module.exports = {
// 1. folder with more than one request in its subtree
// (immediate children or otherwise)
if (resource.requestCount > 1) {
const sourceMapKey = `#/folders/${encodeURIComponent(resource.name)}`;
// only return a Postman folder if this folder has>1 children in its subtree
// otherwise we can end up with 10 levels of folders with 1 request in the end
itemGroup = new sdk.ItemGroup({
id: sourceMap[sourceMapKey] || undefined,
name: utils.insertSpacesInName(resource.name)
// TODO: have to add auth here (but first, auth to be put into the openapi tree)
});
sourceMap[sourceMapKey] = itemGroup.id;
// If a folder has only one child which is a folder then we collapsed the child folder
// with parent folder.
/* eslint-disable max-depth */
Expand All @@ -841,14 +853,16 @@ module.exports = {
resourceSubChild = resource.children[subChild];

resourceSubChild.name = resource.name + '/' + resourceSubChild.name;
return this.convertChildToItemGroup(openapi, resourceSubChild, components, options, schemaCache, variableStore);
return this.convertChildToItemGroup(openapi, resourceSubChild, components, options,
schemaCache, variableStore, sourceMap);
}
/* eslint-enable */
// recurse over child leaf nodes
// and add as children to this folder
for (i = 0, requestCount = resource.requests.length; i < requestCount; i++) {
itemGroup.items.add(
this.convertRequestToItem(openapi, resource.requests[i], components, options, schemaCache, variableStore)
this.convertRequestToItem(openapi, resource.requests[i], components, options,
schemaCache, variableStore, sourceMap)
);
}

Expand All @@ -859,7 +873,7 @@ module.exports = {
if (resource.children.hasOwnProperty(subChild) && resource.children[subChild].requestCount > 0) {
itemGroup.items.add(
this.convertChildToItemGroup(openapi, resource.children[subChild], components, options, schemaCache,
variableStore)
variableStore, sourceMap)
);
}
}
Expand All @@ -870,15 +884,16 @@ module.exports = {

// 2. it has only 1 direct request of its own
if (resource.requests.length === 1) {
return this.convertRequestToItem(openapi, resource.requests[0], components, options, schemaCache, variableStore);
return this.convertRequestToItem(openapi, resource.requests[0], components, options,
schemaCache, variableStore, sourceMap);
}

// 3. it's a folder that has no child request
// but one request somewhere in its child folders
for (subChild in resource.children) {
if (resource.children.hasOwnProperty(subChild) && resource.children[subChild].requestCount === 1) {
return this.convertChildToItemGroup(openapi, resource.children[subChild], components, options, schemaCache,
variableStore);
variableStore, sourceMap);
}
}
},
Expand Down Expand Up @@ -1605,11 +1620,14 @@ module.exports = {
* @param {*} originalRequest - the request for the example
* @param {object} components - components defined in the OAS spec. These are used to
* resolve references while generating params.
* @param {object} options - a standard list of options that's globally passed around. Check options.js for more.
* @param {object} schemaCache - object storing schemaFaker and schmeResolution caches
* @param {object} options - a standard list of options that's globally passed around. Check options.js for more.
* @param {object} schemaCache - object storing schemaFaker and schmeResolution caches
* @param {object} operationItem - object storing the OpenAPI operation.
* @param {object} sourceMap - object storing a map between OpenAPI elements and Postman collection elements.
* @returns {Object} postman response
*/
convertToPmResponse: function(response, code, originalRequest, components, options, schemaCache) {
convertToPmResponse: function(response, code, originalRequest, components, options,
schemaCache, operationItem, sourceMap) {
options = _.merge({}, defaultOptions, options);
var responseHeaders = [],
previewLanguage = 'text',
Expand Down Expand Up @@ -1662,7 +1680,11 @@ module.exports = {
}
code = code.replace(/X/g, '0');

const encodedPath = encodeURIComponent(`/${operationItem.path}`),
sourceMapKey = `#/paths/${encodedPath}/${operationItem.method}/responses/${code}`;

sdkResponse = new sdk.Response({
id: sourceMap[sourceMapKey] ? sourceMap[sourceMapKey] : undefined,
name: response.description,
code: code === 'default' ? 500 : Number(code),
header: responseHeaders,
Expand All @@ -1671,6 +1693,8 @@ module.exports = {
});
sdkResponse._postman_previewlanguage = previewLanguage;

sourceMap[sourceMapKey] = sdkResponse.id;

return sdkResponse;
},

Expand Down Expand Up @@ -1783,17 +1807,18 @@ module.exports = {

/**
* function to convert an openapi path item to postman item
* @param {*} openapi openapi object with root properties
* @param {*} operationItem path operationItem from tree structure
* @param {object} components - components defined in the OAS spec. These are used to
* @param {*} openapi openapi object with root properties
* @param {*} operationItem path operationItem from tree structure
* @param {object} components - components defined in the OAS spec. These are used to
* resolve references while generating params.
* @param {object} options - a standard list of options that's globally passed around. Check options.js for more.
* @param {object} schemaCache - object storing schemaFaker and schmeResolution caches
* @param {array} variableStore - array
* @returns {Object} postman request Item
* @no-unit-test
* @param {object} schemaCache - object storing schemaFaker and schmeResolution caches
* @param {array} variableStore - array
* @param {object} sourceMap - object storing a map between OpenAPI elements and Postman collection elements.
* @returns {Object} postman request Item
* @no-unit-test
*/
convertRequestToItem: function(openapi, operationItem, components, options, schemaCache, variableStore) {
convertRequestToItem: function(openapi, operationItem, components, options, schemaCache, variableStore, sourceMap) {
options = _.merge({}, defaultOptions, options);
var reqName,
pathVariables = openapi.baseUrlVariables,
Expand Down Expand Up @@ -1933,17 +1958,27 @@ module.exports = {
// handling authentication here (for http type only)
authHelper = this.getAuthHelper(openapi, operation.security);

const sourceMapKey = `#/paths/${encodeURIComponent(`/${operationItem.path}`)}/${operationItem.method}`,
sourceMapRequestKey = `#/paths/${encodeURIComponent(`/${operationItem.path}`)}/${operationItem.method}.request`;

// creating the request object
item = new sdk.Item({
id: sourceMap[sourceMapKey] || undefined,
name: reqName,
request: {
id: sourceMap[sourceMapRequestKey] || undefined,
description: operation.description,
url: displayUrl || baseUrl,
name: reqName,
method: operationItem.method.toUpperCase()
}
});

sourceMap[sourceMapKey] = item.id;

// NOTE: This isn't actually propagating once submitted via the Postman API.
sourceMap[sourceMapRequestKey] = item.request.id;

// using the auth helper
authMeta = operation['x-postman-meta'];
if (authMeta && authMeta.currentHelper && authMap[authMeta.currentHelper]) {
Expand Down Expand Up @@ -2061,7 +2096,7 @@ module.exports = {
thisOriginalRequest.body = {};
}
convertedResponse = this.convertToPmResponse(swagResponse, code, thisOriginalRequest,
components, options, schemaCache);
components, options, schemaCache, operationItem, sourceMap);
convertedResponse && item.responses.add(convertedResponse);
});
}
Expand Down
10 changes: 7 additions & 3 deletions lib/schemapack.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ class SchemaPack {
schemaCache = {
schemaResolutionCache: this.schemaResolutionCache,
schemaFakerCache: this.schemaFakerCache
};
},
sourceMap = this.input.sourceMap || {};

if (!this.validated) {
return callback(new OpenApiErr('The schema must be validated before attempting conversion'));
Expand Down Expand Up @@ -271,10 +272,12 @@ class SchemaPack {
// For paths, All operations are grouped based on corresponding paths
try {
if (options.folderStrategy === 'tags') {
schemaUtils.addCollectionItemsUsingTags(openapi, generatedStore, componentsAndPaths, options, schemaCache);
schemaUtils.addCollectionItemsUsingTags(openapi, generatedStore, componentsAndPaths, options,
schemaCache, sourceMap);
}
else {
schemaUtils.addCollectionItemsUsingPaths(openapi, generatedStore, componentsAndPaths, options, schemaCache);
schemaUtils.addCollectionItemsUsingPaths(openapi, generatedStore, componentsAndPaths, options,
schemaCache, sourceMap);
}
}
catch (e) {
Expand All @@ -290,6 +293,7 @@ class SchemaPack {

return callback(null, {
result: true,
sourceMap: sourceMap,
output: [{
type: 'collection',
data: collectionJSON
Expand Down