Skip to content

Commit f202cb2

Browse files
author
Vishal Shingala
committed
Added optional support for remote refs resolution
1 parent b64d910 commit f202cb2

File tree

12 files changed

+220
-89
lines changed

12 files changed

+220
-89
lines changed

lib/options.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,24 @@ module.exports = {
107107
external: true,
108108
usage: ['CONVERSION']
109109
},
110+
{
111+
name: 'Resolve remote references',
112+
id: 'resolveRemoteRefs',
113+
type: 'boolean',
114+
default: false,
115+
description: 'Select whether to resolve remote references.',
116+
external: true,
117+
usage: ['CONVERSION']
118+
},
119+
{
120+
name: 'Source URL of definition',
121+
id: 'sourceUrl',
122+
type: 'string',
123+
default: '',
124+
description: 'Specify source URL of definition to resolve remote references mentioned in it.',
125+
external: true,
126+
usage: ['CONVERSION']
127+
},
110128
{
111129
name: 'Enable Schema Faking',
112130
id: 'schemaFaker',

lib/parse.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,40 @@ module.exports = {
177177
cache: [],
178178
externals: [],
179179
externalRefs: {},
180+
ignoreIOErrors: true,
180181
rewriteRefs: true,
181182
openapi: openapi,
182183
files: files
183184
});
184185
},
185186

187+
/**
188+
* Resolves remote and URL $ref from OpenAPI definition based on given options
189+
*
190+
* @param {*} openapi - OpenAPI definition
191+
* @param {*} options - options
192+
* @param {*} cb - callback function
193+
* @return {*} - err if present
194+
*/
195+
resolveRemoteRefs: function (openapi, options, cb) {
196+
if (options.resolveRemoteRefs) {
197+
return resolver.resolve(openapi, options.sourceUrl, {
198+
resolve: true,
199+
externals: [],
200+
externalRefs: {},
201+
ignoreIOErrors: true,
202+
openapi: openapi
203+
})
204+
.then(function() {
205+
return cb(null);
206+
})
207+
.catch(function(err) {
208+
return cb(err);
209+
});
210+
}
211+
return cb(null);
212+
},
213+
186214
/** Resolves all OpenAPI file references and returns a single OAS Object
187215
*
188216
* @param {Object} source Root file path

lib/schemapack.js

Lines changed: 77 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -237,90 +237,99 @@ class SchemaPack {
237237
return callback(new OpenApiErr('The schema must be validated before attempting conversion'));
238238
}
239239

240-
// this cannot be attempted before validation
241-
componentsAndPaths = {
242-
components: this.openapi.components,
243-
paths: this.openapi.paths
244-
};
240+
parse.resolveRemoteRefs(this.openapi, options, (err) => {
241+
if (err) {
242+
return callback(null, {
243+
result: false,
244+
reason: err.name + ': ' + err.message
245+
});
246+
}
245247

246-
// create and sanitize basic spec
247-
openapi = this.openapi;
248-
openapi.servers = _.isEmpty(openapi.servers) ? [{ url: '/' }] : openapi.servers;
249-
openapi.securityDefs = _.get(openapi, 'components.securitySchemes', {});
250-
openapi.baseUrl = _.get(openapi, 'servers.0.url', '{{baseURL}}');
248+
// this cannot be attempted before validation
249+
componentsAndPaths = {
250+
components: this.openapi.components,
251+
paths: this.openapi.paths
252+
};
251253

252-
// TODO: Multiple server variables need to be saved as environments
253-
openapi.baseUrlVariables = _.get(openapi, 'servers.0.variables');
254+
// create and sanitize basic spec
255+
openapi = this.openapi;
256+
openapi.servers = _.isEmpty(openapi.servers) ? [{ url: '/' }] : openapi.servers;
257+
openapi.securityDefs = _.get(openapi, 'components.securitySchemes', {});
258+
openapi.baseUrl = _.get(openapi, 'servers.0.url', '{{baseURL}}');
254259

255-
// Fix {scheme} and {path} vars in the URL to :scheme and :path
256-
openapi.baseUrl = schemaUtils.fixPathVariablesInUrl(openapi.baseUrl);
260+
// TODO: Multiple server variables need to be saved as environments
261+
openapi.baseUrlVariables = _.get(openapi, 'servers.0.variables');
257262

258-
// Creating a new instance of a Postman collection
259-
// All generated folders and requests will go inside this
260-
generatedStore.collection = new sdk.Collection({
261-
info: {
262-
name: _.get(openapi, 'info.title', COLLECTION_NAME)
263-
}
264-
});
263+
// Fix {scheme} and {path} vars in the URL to :scheme and :path
264+
openapi.baseUrl = schemaUtils.fixPathVariablesInUrl(openapi.baseUrl);
265+
266+
// Creating a new instance of a Postman collection
267+
// All generated folders and requests will go inside this
268+
generatedStore.collection = new sdk.Collection({
269+
info: {
270+
name: _.get(openapi, 'info.title', COLLECTION_NAME)
271+
}
272+
});
265273

266-
if (openapi.security) {
267-
authHelper = schemaUtils.getAuthHelper(openapi, openapi.security);
268-
if (authHelper) {
269-
generatedStore.collection.auth = authHelper;
274+
if (openapi.security) {
275+
authHelper = schemaUtils.getAuthHelper(openapi, openapi.security);
276+
if (authHelper) {
277+
generatedStore.collection.auth = authHelper;
278+
}
270279
}
271-
}
272-
// ---- Collection Variables ----
273-
// adding the collection variables for all the necessary root level variables
274-
// and adding them to the collection variables
275-
schemaUtils.convertToPmCollectionVariables(
276-
openapi.baseUrlVariables,
277-
'baseUrl',
278-
openapi.baseUrl
279-
).forEach((element) => {
280-
generatedStore.collection.variables.add(element);
281-
});
280+
// ---- Collection Variables ----
281+
// adding the collection variables for all the necessary root level variables
282+
// and adding them to the collection variables
283+
schemaUtils.convertToPmCollectionVariables(
284+
openapi.baseUrlVariables,
285+
'baseUrl',
286+
openapi.baseUrl
287+
).forEach((element) => {
288+
generatedStore.collection.variables.add(element);
289+
});
282290

283-
generatedStore.collection.describe(schemaUtils.getCollectionDescription(openapi));
291+
generatedStore.collection.describe(schemaUtils.getCollectionDescription(openapi));
284292

285-
// Only change the stack limit if the optimizeConversion option is true
286-
if (options.optimizeConversion) {
287-
// Deciding stack limit based on size of the schema, number of refs and number of paths.
288-
analysis = schemaUtils.analyzeSpec(openapi);
293+
// Only change the stack limit if the optimizeConversion option is true
294+
if (options.optimizeConversion) {
295+
// Deciding stack limit based on size of the schema, number of refs and number of paths.
296+
analysis = schemaUtils.analyzeSpec(openapi);
289297

290-
// Update options on the basis of analysis.
291-
options = schemaUtils.determineOptions(analysis, options);
292-
}
298+
// Update options on the basis of analysis.
299+
options = schemaUtils.determineOptions(analysis, options);
300+
}
293301

294302

295-
// ---- Collection Items ----
296-
// Adding the collection items from openapi spec based on folderStrategy option
297-
// For tags, All operations are grouped based on respective tags object
298-
// For paths, All operations are grouped based on corresponding paths
299-
try {
300-
if (options.folderStrategy === 'tags') {
301-
schemaUtils.addCollectionItemsUsingTags(openapi, generatedStore, componentsAndPaths, options, schemaCache);
303+
// ---- Collection Items ----
304+
// Adding the collection items from openapi spec based on folderStrategy option
305+
// For tags, All operations are grouped based on respective tags object
306+
// For paths, All operations are grouped based on corresponding paths
307+
try {
308+
if (options.folderStrategy === 'tags') {
309+
schemaUtils.addCollectionItemsUsingTags(openapi, generatedStore, componentsAndPaths, options, schemaCache);
310+
}
311+
else {
312+
schemaUtils.addCollectionItemsUsingPaths(openapi, generatedStore, componentsAndPaths, options, schemaCache);
313+
}
302314
}
303-
else {
304-
schemaUtils.addCollectionItemsUsingPaths(openapi, generatedStore, componentsAndPaths, options, schemaCache);
315+
catch (e) {
316+
return callback(e);
305317
}
306-
}
307-
catch (e) {
308-
return callback(e);
309-
}
310318

311-
collectionJSON = generatedStore.collection.toJSON();
319+
collectionJSON = generatedStore.collection.toJSON();
312320

313-
// this needs to be deleted as even if version is not specified to sdk,
314-
// it returns a version property with value set as undefined
315-
// this fails validation against v2.1 collection schema definition.
316-
delete collectionJSON.info.version;
321+
// this needs to be deleted as even if version is not specified to sdk,
322+
// it returns a version property with value set as undefined
323+
// this fails validation against v2.1 collection schema definition.
324+
delete collectionJSON.info.version;
317325

318-
return callback(null, {
319-
result: true,
320-
output: [{
321-
type: 'collection',
322-
data: collectionJSON
323-
}]
326+
return callback(null, {
327+
result: true,
328+
output: [{
329+
type: 'collection',
330+
data: collectionJSON
331+
}]
332+
});
324333
});
325334
}
326335

lib/utils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ module.exports = {
2222

2323
// check the type of the value of that option came from the user
2424
switch (defaultOptions[id].type) {
25+
case 'string':
2526
case 'boolean':
2627
if (typeof userOptions[id] === defaultOptions[id].type) {
2728
retVal[id] = userOptions[id];

package-lock.json

Lines changed: 19 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@
121121
"commander": "2.20.3",
122122
"js-yaml": "3.13.1",
123123
"lodash": "4.17.20",
124-
"oas-resolver-browser": "2.3.3",
124+
"oas-resolver-browser": "2.5.0",
125125
"path-browserify": "1.0.1",
126126
"postman-collection": "3.6.6",
127127
"yaml": "1.8.3"

scripts/test-unit.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,6 @@ fi
3838

3939
# run test
4040
node --max-old-space-size=2048 ./node_modules/.bin/nyc ${COVERAGE_REPORT} --report-dir ./.coverage \
41-
-x **/assets/** --print both ./node_modules/.bin/_mocha \
41+
-x **/assets/** --print both ./node_modules/.bin/_mocha --timeout 15000 \
4242
--reporter ${MOCHA_REPORTER} --reporter-options output=${XUNIT_FILE} \
4343
test/unit/*.test.js --recursive --prof --grep "$1";
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
openapi: "3.0.0"
2+
info:
3+
version: 1.0.0
4+
title: Swagger Petstore
5+
license:
6+
name: MIT
7+
servers:
8+
- url: http://petstore.swagger.io/v1
9+
paths:
10+
/pets:
11+
get:
12+
summary: List all pets
13+
operationId: listPets
14+
parameters:
15+
- $ref: 'https://raw.githubusercontent.com/postmanlabs/openapi-to-postman/develop/test/data/petstore%20separate%20yaml/spec/parameters.yaml#/tagsParam'
16+
- $ref: 'https://raw.githubusercontent.com/postmanlabs/openapi-to-postman/develop/test/data/petstore%20separate%20yaml/spec/parameters.yaml#/limitsParam'
17+
responses:
18+
'200':
19+
description: An paged array of pets
20+
content:
21+
application/json:
22+
schema:
23+
$ref: "./spec/Pet.yaml"
24+
default:
25+
description: unexpected error
26+
content:
27+
application/json:
28+
schema:
29+
$ref: "./common/Error.yaml"

test/system/structure.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const optionIds = [
88
'folderStrategy',
99
'indentCharacter',
1010
'requestNameSource',
11+
'resolveRemoteRefs',
12+
'sourceUrl',
1113
'includeAuthInfoInExample',
1214
'shortValidationErrors',
1315
'validationPropertiesToIgnore',
@@ -78,6 +80,18 @@ const optionIds = [
7880
' If “Fallback” is selected, the request will be named after one of the following schema' +
7981
' values: `description`, `operationid`, `url`.'
8082
},
83+
resolveRemoteRefs: {
84+
name: 'Resolve remote references',
85+
type: 'boolean',
86+
default: false,
87+
description: 'Select whether to resolve remote references.'
88+
},
89+
sourceUrl: {
90+
name: 'Source URL of definition',
91+
type: 'string',
92+
default: '',
93+
description: 'Specify source URL of definition to resolve remote references mentioned in it.'
94+
},
8195
includeAuthInfoInExample: {
8296
name: 'Include auth info in example requests',
8397
type: 'boolean',

0 commit comments

Comments
 (0)