Skip to content

Commit

Permalink
Merge branch 'release/1.1.12'
Browse files Browse the repository at this point in the history
  • Loading branch information
umeshp7 committed Mar 25, 2020
2 parents 65898fd + 8e31ca7 commit 0b696be
Show file tree
Hide file tree
Showing 10 changed files with 733 additions and 86 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# OpenAPI-Postman Changelog

#### v1.1.12 (Mar 26, 2020)
* Fix for https://github.com/postmanlabs/openapi-to-postman/issues/133 and https://github.com/postmanlabs/openapi-to-postman/issues/101
* Ignore resolving circular references.
* Upgrade commander from 2.3.0 to 2.20.3
* Upgrade postman-collection from 3.5.1 to 3.5.5

#### v1.1.11 (Mar 14, 2020)
* Safely handling invalid reference schemas/properties

Expand Down
34 changes: 24 additions & 10 deletions lib/deref.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,22 @@ module.exports = {
* @param {*} components components in openapi spec.
* @param {object} schemaResolutionCache stores already resolved references
* @param {*} stack counter which keeps a tab on nested schemas
* @param {*} seenRef References that are repeated. Used to identify circular references.
* @returns {*} schema - schema that adheres to all individual schemas in schemaArr
*/
resolveAllOf: function (schemaArr, parameterSourceOption, components, schemaResolutionCache, stack = 0) {
resolveAllOf: function (schemaArr, parameterSourceOption, components, schemaResolutionCache, stack = 0, seenRef) {
if (!(schemaArr instanceof Array)) {
return null;
}

if (schemaArr.length === 1) {
// for just one entry in allOf, don't need to enforce type: object restriction
return this.resolveRefs(schemaArr[0], parameterSourceOption, components, schemaResolutionCache, stack);
return this.resolveRefs(schemaArr[0], parameterSourceOption, components, schemaResolutionCache, stack, seenRef);
}

// generate one object for each schema
let indivObjects = schemaArr.map((schema) => {
return this.resolveRefs(schema, parameterSourceOption, components, schemaResolutionCache, stack);
return this.resolveRefs(schema, parameterSourceOption, components, schemaResolutionCache, stack, seenRef);
}).filter((schema) => {
return schema.type === 'object';
}),
Expand Down Expand Up @@ -107,9 +108,10 @@ module.exports = {
* @param {*} components components in openapi spec.
* @param {object} schemaResolutionCache stores already resolved references
* @param {*} stack counter which keeps a tab on nested schemas
* @param {*} seenRef - References that are repeated. Used to identify circular references.
* @returns {*} schema satisfying JSON-schema-faker.
*/
resolveRefs: function (schema, parameterSourceOption, components, schemaResolutionCache, stack = 0) {
resolveRefs: function (schema, parameterSourceOption, components, schemaResolutionCache, stack = 0, seenRef = {}) {
var resolvedSchema, prop, splitRef;
stack++;
schemaResolutionCache = schemaResolutionCache || {};
Expand All @@ -122,16 +124,27 @@ module.exports = {
}

if (schema.anyOf) {
return this.resolveRefs(schema.anyOf[0], parameterSourceOption, components, schemaResolutionCache, stack);
return this.resolveRefs(schema.anyOf[0], parameterSourceOption, components, schemaResolutionCache, stack,
_.cloneDeep(seenRef));
}
if (schema.oneOf) {
return this.resolveRefs(schema.oneOf[0], parameterSourceOption, components, schemaResolutionCache, stack);
return this.resolveRefs(schema.oneOf[0], parameterSourceOption, components, schemaResolutionCache, stack,
_.cloneDeep(seenRef));
}
if (schema.allOf) {
return this.resolveAllOf(schema.allOf, parameterSourceOption, components, schemaResolutionCache, stack);
return this.resolveAllOf(schema.allOf, parameterSourceOption, components, schemaResolutionCache, stack,
_.cloneDeep(seenRef));
}
if (schema.$ref && _.isFunction(schema.$ref.split)) {
let refKey = schema.$ref;

// if this reference is seen before, ignore and move on.
if (seenRef[refKey]) {
return { value: '<Circular reference to ' + refKey + ' detected>' };
}
// add to seen array if not encountered before.
seenRef[refKey] = stack;

// points to an existing location
// .split will return [#, components, schemas, schemaName]
splitRef = refKey.split('/');
Expand All @@ -148,9 +161,10 @@ module.exports = {
// splitRef.slice(1) will return ['components', 'schemas', 'PaginationEnvelope', 'properties', 'page']
// not using _.get here because that fails if there's a . in the property name (Pagination.Envelope, for example)
resolvedSchema = this._getEscaped(components, splitRef.slice(1));

if (resolvedSchema) {
let refResolvedSchema = this.resolveRefs(resolvedSchema, parameterSourceOption,
components, schemaResolutionCache, stack);
components, schemaResolutionCache, stack, _.cloneDeep(seenRef));
schemaResolutionCache[refKey] = refResolvedSchema;
return refResolvedSchema;
}
Expand Down Expand Up @@ -182,7 +196,7 @@ module.exports = {
}
/* eslint-enable */
tempSchema.properties[prop] = this.resolveRefs(property,
parameterSourceOption, components, schemaResolutionCache, stack);
parameterSourceOption, components, schemaResolutionCache, stack, _.cloneDeep(seenRef));
}
}
return tempSchema;
Expand All @@ -201,7 +215,7 @@ module.exports = {
// without this, schemas with circular references aren't faked correctly
let tempSchema = _.omit(schema, 'items');
tempSchema.items = this.resolveRefs(schema.items, parameterSourceOption,
components, schemaResolutionCache, stack);
components, schemaResolutionCache, stack, _.cloneDeep(seenRef));
return tempSchema;
}
else if (!schema.hasOwnProperty('default')) {
Expand Down
106 changes: 97 additions & 9 deletions lib/schemaUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -589,10 +589,11 @@ 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} variableStore - array for storing collection variables
* @returns {*} Postman itemGroup or request
* @no-unit-test
*/
convertChildToItemGroup: function (openapi, child, components, options, schemaCache) {
convertChildToItemGroup: function (openapi, child, components, options, schemaCache, variableStore) {
options = _.merge({}, defaultOptions, options);

var resource = child,
Expand Down Expand Up @@ -620,14 +621,14 @@ module.exports = {
resourceSubChild = resource.children[subChild];

resourceSubChild.name = resource.name + '/' + resourceSubChild.name;
return this.convertChildToItemGroup(openapi, resourceSubChild, components, options, schemaCache);
return this.convertChildToItemGroup(openapi, resourceSubChild, components, options, schemaCache, variableStore);
}
/* 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)
this.convertRequestToItem(openapi, resource.requests[i], components, options, schemaCache, variableStore)
);
}

Expand All @@ -637,7 +638,8 @@ module.exports = {
for (subChild in resource.children) {
if (resource.children.hasOwnProperty(subChild) && resource.children[subChild].requestCount > 0) {
itemGroup.items.add(
this.convertChildToItemGroup(openapi, resource.children[subChild], components, options, schemaCache)
this.convertChildToItemGroup(openapi, resource.children[subChild], components, options, schemaCache,
variableStore)
);
}
}
Expand All @@ -648,14 +650,15 @@ 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);
return this.convertRequestToItem(openapi, resource.requests[0], components, options, schemaCache, variableStore);
}

// 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);
return this.convertChildToItemGroup(openapi, resource.children[subChild], components, options, schemaCache,
variableStore);
}
}
},
Expand Down Expand Up @@ -1497,6 +1500,67 @@ module.exports = {
return refObj;
},

/** Finds all the possible path variables in a given path string
* @param {string} path Path string : /pets/{petId}
* @returns {array} Array of path variables.
*/
findPathVariablesFromPath: function (path) {

// /{{path}}/{{file}}.{{format}}/{{hello}} return [ '{{path}}', '{{hello}}' ]
// https://regex101.com/r/XGL4Gh/1
return path.match(/(\/\{\{[^\/\{\}]+\}\})(?=\/|$)/g);
},

/** Finds all the possible collection variables in a given path string
* @param {string} path Path string : /pets/{petId}
* @returns {array} Array of collection variables.
*/
findCollectionVariablesFromPath: function (path) {

// /:path/{{file}}.{{format}}/:hello => only {{file}} and {{format}} will match
// https://regex101.com/r/XGL4Gh/2
return path.match(/(\{\{[^\/\{\}]+\}\})/g);
},

/** Separates outs collection and path variables from the reqUrl
*
* @param {string} reqUrl Request Url
* @param {Array} pathVars Path variables
*
* @returns {Object} reqUrl, updated path Variables array and collection Variables.
*/
sanitizeUrlPathParams: function (reqUrl, pathVars) {
var matches,
collectionVars = [];

// converts all the of the following:
// /{{path}}/{{file}}.{{format}}/{{hello}} => /:path/{{file}}.{{format}}/:hello
matches = this.findPathVariablesFromPath(reqUrl);
if (matches) {
matches.forEach((match) => {
const replaceWith = match.replace(/{{/g, ':').replace(/}}/g, '');
reqUrl = reqUrl.replace(match, replaceWith);
});
}

// Separates pathVars array and collectionVars.
matches = this.findCollectionVariablesFromPath(reqUrl);
if (matches) {
matches.forEach((match) => {
const collVar = match.replace(/{{/g, '').replace(/}}/g, '');

pathVars = pathVars.filter((item) => {
if (item.name === collVar) {
collectionVars.push(item);
}
return !(item.name === collVar);
});
});
}

return { reqUrl, pathVars, collectionVars };
},

/**
* function to convert an openapi path item to postman item
* @param {*} openapi openapi object with root properties
Expand All @@ -1505,10 +1569,11 @@ 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 {array} variableStore - array
* @returns {Object} postman request Item
* @no-unit-test
*/
convertRequestToItem: function(openapi, operationItem, components, options, schemaCache) {
convertRequestToItem: function(openapi, operationItem, components, options, schemaCache, variableStore) {
options = _.merge({}, defaultOptions, options);
var reqName,
pathVariables = openapi.baseUrlVariables,
Expand All @@ -1528,11 +1593,34 @@ module.exports = {
swagResponse,
localServers = _.get(operationItem, 'properties.servers'),
exampleRequestBody,
sanitizeResult,
globalServers = _.get(operationItem, 'servers');

// handling path templating in request url if any
reqUrl = reqUrl.replace(/{/g, ':').replace(/}/g, '');

// convert all {anything} to {{anything}}
reqUrl = this.fixPathVariablesInUrl(reqUrl);

// convert all /{{one}}/{{two}} to /:one/:two
// Doesn't touch /{{file}}.{{format}}
sanitizeResult = this.sanitizeUrlPathParams(reqUrl, reqParams.path);

// Updated reqUrl
reqUrl = sanitizeResult.reqUrl;

// Updated reqParams.path
reqParams.path = sanitizeResult.pathVars;

// Add collection variables to the variableStore.
sanitizeResult.collectionVars.forEach((element) => {
if (!variableStore[element.name]) {
variableStore[element.name] = {
id: element.name,
value: element.default || '',
description: element.description,
type: 'collection'
};
}
});
// accounting for the overriding of the root level and path level servers object if present at the operation level

if (Array.isArray(localServers) && localServers.length) {
Expand Down
14 changes: 12 additions & 2 deletions lib/schemapack.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,28 @@ const COLLECTION_NAME = 'Converted from OpenAPI',
generateCollection = function (specWrapper, generatedStore, components, options, schemaCache) {
var folderTree = specWrapper.tree, // this is the trie we generate (as a scaffold to the collection)
openapi = specWrapper.spec, // this is the JSON-version of the openAPI spec
child;
child,
key,
variableStore = {};

for (child in folderTree.root.children) {
// A Postman request or folder is added if atleast one request is present in that sub-child's tree
// requestCount is a property added to each node (folder/request) while constructing the trie
if (folderTree.root.children.hasOwnProperty(child) && folderTree.root.children[child].requestCount > 0) {
generatedStore.collection.items.add(
schemaUtils.convertChildToItemGroup(openapi, folderTree.root.children[child],
components, options, schemaCache)
components, options, schemaCache, variableStore)
);
}
}
for (key in variableStore) {
// variableStore contains all the kinds of variable created.
// Add only the variables with type 'collection' to generatedStore.collection.variables
if (variableStore[key].type === 'collection') {
const collectionVar = new sdk.Variable(variableStore[key]);
generatedStore.collection.variables.add(collectionVar);
}
}
};


Expand Down
Loading

0 comments on commit 0b696be

Please sign in to comment.