openapi-to-postmanv2
Version:
Convert a given OpenAPI specification to Postman Collection v2.0
319 lines (256 loc) • 10.4 kB
JavaScript
/* eslint-disable one-var */
const _ = require('lodash'),
{ Collection } = require('postman-collection/lib/collection/collection'),
GraphLib = require('graphlib'),
generateSkeletonTreeFromOpenAPI = require('./helpers/collection/generateSkeletionTreeFromOpenAPI'),
generateCollectionFromOpenAPI = require('./helpers/collection/generateCollectionFromOpenAPI'),
generateFolderFromOpenAPI = require('./helpers/folder/generateFolderForOpenAPI'),
Ajv = require('ajv'),
addFormats = require('ajv-formats'),
async = require('async'),
transactionSchema = require('../assets/validationRequestListSchema.json'),
// All V1 interfaces used
OpenApiErr = require('../lib/error'),
{ validateTransaction, getMissingSchemaEndpoints } = require('./validationUtils');
const { resolvePostmanRequest } = require('./schemaUtils');
const { generateRequestItemObject, fixPathVariablesInUrl } = require('./utils');
module.exports = {
convertV2: function (context, cb) {
/**
* Start generating the Bare bone tree that should exist for the schema
*/
let collectionTree = generateSkeletonTreeFromOpenAPI(context.openapi, context.computedOptions);
/**
* Do post order traversal so we get the request nodes first and generate the request object
*/
let preOrderTraversal = GraphLib.alg.preorder(collectionTree, 'root:collection');
let collection = {},
extractedTypesObject = {};
/**
* individually start generating the folder, request, collection
* and keep adding to the collection tree.
*/
_.forEach(preOrderTraversal, function (nodeIdentified) {
let node = collectionTree.node(nodeIdentified);
switch (node.type) {
case 'collection': {
// dummy collection to be generated.
const { data, variables } = generateCollectionFromOpenAPI(context, node);
collection = new Collection(data);
collection = collection.toJSON();
collection.variable.push(...variables);
// set the ref for the collection in the node.
collectionTree.setNode(nodeIdentified,
Object.assign(node, {
ref: collection
}));
break;
}
case 'folder': {
// generate the folder form the node.
let folder = generateFolderFromOpenAPI(context, node).data || {};
// find the parent of the folder in question / root collection.
let parent = collectionTree.predecessors(nodeIdentified);
// this is directed graph, and hence have only one parent.
parent = collectionTree.node(parent && parent[0]);
// if the item construct does not exist add and initialize it to zero
if (!parent.ref.item) {
parent.ref.item = [];
}
// push the folder in the item that is in question
parent.ref.item.push(folder);
// set the ref for the newly created folder in this.
collectionTree.setNode(nodeIdentified,
Object.assign(node, {
ref: _.last(parent.ref.item)
}));
break;
}
case 'request': {
// generate the request form the node
let request = {},
collectionVariables = [],
requestObject = {},
requestTypesObject = {};
try {
({ request, collectionVariables, requestTypesObject } = resolvePostmanRequest(context,
context.openapi.paths[node.meta.path],
node.meta.path,
node.meta.method
));
requestObject = generateRequestItemObject(request);
extractedTypesObject = Object.assign({}, extractedTypesObject, requestTypesObject);
}
catch (error) {
console.error(error);
break;
}
collection.variable.push(...collectionVariables);
// find the parent of the request in question
let parent = collectionTree.predecessors(nodeIdentified);
// this is directed graph, and hence have only one parent.
parent = collectionTree.node(parent && parent[0]);
// if the item construct does not exist add and initialize it to zero
if (!parent.ref.item) {
parent.ref.item = [];
}
// push the folder in the item that is in question
parent.ref.item.push(requestObject);
// set the ref for the newly created request in this.
collectionTree.setNode(nodeIdentified,
Object.assign(node, {
ref: _.last(parent.ref.item)
}));
break;
}
case 'webhook~folder': {
// generate the folder form the node.
let folder = generateFolderFromOpenAPI(context, node).data || {};
// find the parent of the folder in question / root collection.
let parent = collectionTree.predecessors(nodeIdentified);
// this is directed graph, and hence have only one parent.
parent = collectionTree.node(parent && parent[0]);
// if the item construct does not exist add and initialize it to zero
if (!parent.ref.item) {
parent.ref.item = [];
}
// push the folder in the item that is in question
parent.ref.item.push(folder);
// set the ref for the newly created folder in this.
collectionTree.setNode(nodeIdentified,
Object.assign(node, {
ref: _.last(parent.ref.item)
}));
break;
}
case 'webhook~request': {
// generate the request form the node
let request = {},
collectionVariables = [],
requestObject = {};
// TODO: Figure out a proper fix for this
if (node.meta.method === 'parameters') {
break;
}
try {
({ request, collectionVariables } = resolvePostmanRequest(context,
context.openapi.webhooks[node.meta.path],
node.meta.path,
node.meta.method
));
requestObject = generateRequestItemObject(request);
}
catch (error) {
console.error(error);
break;
}
collection.variable.push(...collectionVariables);
// find the parent of the request in question
let parent = collectionTree.predecessors(nodeIdentified);
// this is directed graph, and hence have only one parent.
parent = collectionTree.node(parent && parent[0]);
// if the item construct does not exist add and initialize it to zero
if (!parent.ref.item) {
parent.ref.item = [];
}
// push the folder in the item that is in question
parent.ref.item.push(requestObject);
// set the ref for the newly created request in this.
collectionTree.setNode(nodeIdentified,
Object.assign(node, {
ref: _.last(parent.ref.item)
}));
break;
}
default: break;
}
});
// Remove duplicate variables as different requests could end up creating same variables
if (!_.isEmpty(collection.variable)) {
collection.variable = _.uniqBy(collection.variable, 'key');
}
if (context.enableTypeFetching) {
return cb(null, {
result: true,
output: [{
type: 'collection',
data: collection
}],
analytics: this.analytics || {},
extractedTypes: extractedTypesObject || {}
});
}
return cb(null, {
result: true,
output: [{
type: 'collection',
data: collection
}],
analytics: this.analytics || {}
});
},
/**
*
* @description Takes in a request collection (transaction object) and validates it against
* corresponding definition matching endpoint
*
* @param {Object} context - Required context from related SchemaPack function
* @param {Array} transactions - Transactions to be validated
* @param {*} callback return
* @returns {boolean} validation
*/
validateTransactionV2(context, transactions, callback) {
let schema = context.openapi,
options = context.computedOptions,
concreteUtils = context.concreteUtils,
componentsAndPaths = { concreteUtils },
schemaCache = context.schemaFakerCache,
matchedEndpoints = [];
context.schemaCache = context.schemaCache || {};
context.schemaFakerCache = context.schemaFakerCache || {};
Object.assign(componentsAndPaths, concreteUtils.getRequiredData(schema));
// create and sanitize basic spec
schema.servers = _.isEmpty(schema.servers) ? [{ url: '/' }] : schema.servers;
schema.securityDefs = _.get(schema, 'components.securitySchemes', {});
schema.baseUrl = _.get(schema, 'servers.0.url', '{{baseURL}}');
schema.baseUrlVariables = _.get(schema, 'servers.0.variables');
// Fix {scheme} and {path} vars in the URL to :scheme and :path
schema.baseUrl = fixPathVariablesInUrl(schema.baseUrl);
// check validity of transactions
try {
// add Ajv options to support validation of OpenAPI schema.
// For more details see https://ajv.js.org/#options
let ajv = new Ajv({
allErrors: true,
strict: false
}),
validate,
res;
addFormats(ajv);
validate = ajv.compile(transactionSchema);
res = validate(transactions);
if (!res) {
return callback(new OpenApiErr('Invalid syntax provided for requestList', validate.errors));
}
}
catch (e) {
return callback(new OpenApiErr('Invalid syntax provided for requestList', e));
}
return async.map(transactions, (transaction, callback) => {
return validateTransaction(context, transaction, {
schema, options, componentsAndPaths, schemaCache, matchedEndpoints
}, callback);
}, (err, result) => {
var retVal;
if (err) {
return callback(err);
}
retVal = {
requests: _.keyBy(result, 'requestId'),
missingEndpoints: getMissingSchemaEndpoints(context, schema, matchedEndpoints,
componentsAndPaths, options, schemaCache)
};
return callback(null, retVal);
});
}
};