From aac8945217b8cdf8f52b75c28be652873090f5ae Mon Sep 17 00:00:00 2001 From: Frank Schmid Date: Wed, 11 Nov 2015 11:09:40 +0100 Subject: [PATCH] Fixed issue #27 Included new options resolveRemoteRefs, resolveFilRefs and resolveLocalRefs --- .gitattributes | 8 ++ index.js | 42 ++++++++-- lib/utils.js | 4 + test/test-json-refs.js | 174 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..37dde69 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Auto detect text files and perform LF normalization +* text=auto + +*.yaml text eol=lf +*.yml text eol=lf +*.js text eol=lf +*.json text eol=lf +*.md text eol=lf diff --git a/index.js b/index.js index 679cff7..d74d2fe 100644 --- a/index.js +++ b/index.js @@ -222,6 +222,20 @@ var isRemotePointer = module.exports.isRemotePointer = function isRemotePointer return ptr !== '' && ptr.charAt(0) !== '#'; }; +/** + * Returns whether or not the JSON Pointer is a file reference. + * + * @param {string} ptr - The JSON Pointer + * + * @returns {boolean} true if the JSON Pointer is a file or false if not + * + * @throws Error if the arguments are missing or invalid + */ +var isFilePointer = module.exports.isFilePointer = function isFilePointer (ptr) { + // The set of file pointers is a subset of the set of remote pointers without scheme prefix + return isRemotePointer(ptr) && !(/^[a-zA-Z]+:\/\//.test(ptr)); +}; + /** * Takes a JSON Reference and returns an array of path segments. * @@ -423,11 +437,13 @@ function realResolveRefs (json, options, metadata) { } // All references at this point should be local except missing/invalid references - _.each(findRefs(json), function (ref, refPtr) { - if (!isRemotePointer(ref)) { - replaceReference(ref, refPtr); - } - }); + if (_.isUndefined(options.resolveLocalRefs) || options.resolveLocalRefs) { + _.each(findRefs(json), function (ref, refPtr) { + if (!isRemotePointer(ref)) { + replaceReference(ref, refPtr); + } + }); + } // Remove full locations from reference metadata if (!_.isUndefined(options.location)) { @@ -508,7 +524,12 @@ function resolveRemoteRefs (json, options, parentPtr, parents, metadata) { } _.each(findRefs(json), function (ptr, refPtr) { - if (isRemotePointer(ptr)) { + // Use resolve filters from options to resolve/not resolve references + var isFilePtr = isFilePointer(ptr); + var isRemotePtr = isRemotePointer(ptr); + + if ((isFilePtr && (_.isUndefined(options.resolveFileRefs) || options.resolveFileRefs)) || + (!isFilePtr && isRemotePtr && (_.isUndefined(options.resolveRemoteRefs) || options.resolveRemoteRefs))) { allTasks = allTasks.then(function () { var remoteLocation = computeUrl(options.location, ptr); var refParts = ptr.split('#'); @@ -586,6 +607,9 @@ function resolveRemoteRefs (json, options, parentPtr, parents, metadata) { * @param {object} [options] - The options (All options are passed down to whitlockjc/path-loader) * @param {number} [options.depth=1] - The depth to resolve circular references * @param {string} [options.location] - The location to which relative references should be resolved + * @param {boolean} [options.resolveLocalRefs=true] - Resolve local references + * @param {boolean} [options.resolveRemoteRefs=true] - Resolve remote references + * @param {boolean} [options.resolveFileRefs=true] - Resolve file references * @param {prepareRequestCallback} [options.prepareRequest] - The callback used to prepare an HTTP request * @param {processContentCallback} [options.processContent] - The callback used to process a reference's content * @param {resultCallback} [done] - The result callback @@ -682,6 +706,12 @@ module.exports.resolveRefs = function resolveRefs (json, options, done) { throw new Error('options.depth must be a number'); } else if (!_.isUndefined(options.depth) && options.depth < 0) { throw new Error('options.depth must be greater or equal to zero'); + } else if (!_.isUndefined(options.resolveLocalRefs) && !_.isBoolean(options.resolveLocalRefs)) { + throw new Error('options.resolveLocalRefs must be a boolean'); + } else if (!_.isUndefined(options.resolveRemoteRefs) && !_.isBoolean(options.resolveRemoteRefs)) { + throw new Error('options.resolveRemoteRefs must be a boolean'); + } else if (!_.isUndefined(options.resolveFileRefs) && !_.isBoolean(options.resolveFileRefs)) { + throw new Error('options.resolveFileRefs must be a boolean'); } }); diff --git a/lib/utils.js b/lib/utils.js index e848755..13b888b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -42,6 +42,10 @@ var isArray = module.exports.isArray = function (obj) { return isType(obj, 'Array'); }; +module.exports.isBoolean = function (obj) { + return isType(obj, 'Boolean'); +}; + module.exports.isError = function (obj) { return isType(obj, 'Error'); }; diff --git a/test/test-json-refs.js b/test/test-json-refs.js index aa2fb23..9c34ae1 100644 --- a/test/test-json-refs.js +++ b/test/test-json-refs.js @@ -1,4 +1,4 @@ -/* global afterEach, describe, it, window */ +/* global afterEach, before, describe, it, window */ /* * The MIT License (MIT) @@ -203,6 +203,9 @@ describe('json-refs', function () { 'options.depth must be a number': [{}, {depth: true}], 'options.depth must be greater or equal to zero': [{}, {depth: -1}], 'options.location must be a string': [{}, {location: 123}], + 'options.resolveLocalRefs must be a boolean': [{}, {resolveLocalRefs: 'wrongType'}], + 'options.resolveFileRefs must be a boolean': [{}, {resolveFileRefs: 'wrongType'}], + 'options.resolveRemoteRefs must be a boolean': [{}, {resolveRemoteRefs: 'wrongType'}], 'options.prepareRequest must be a function': [{}, {prepareRequest: 'wrongType'}], 'options.processContent must be a function': [{}, {processContent: 'wrongType'}], 'done must be a function': [{}, {}, 'wrongType'] @@ -1611,5 +1614,174 @@ describe('json-refs', function () { }) .then(done, done); }); + + describe('should handle options.resolveXXXRefs', function () { + var json; + + // Initialize test object + before(function () { + json = { + info: { + localRef: { + $ref: '#/definitions/item' + }, + fileRef: { + $ref: 'project.json' + }, + remoteRef: { + $ref: 'http://localhost:44444/project.json' + } + }, + definitions: { + item: { + type: 'object', + properties: { + itemProperty: { + type: 'string' + } + } + } + } + }; + }); + + it('resolveLocalRefs = false', function (done) { + var cOptions = _.cloneDeep(options); + var cJson = _.cloneDeep(json); + + cOptions.resolveLocalRefs = false; + + jsonRefs.resolveRefs(cJson, cOptions) + .then(function (results) { + assert.notDeepEqual(cJson, results.resolved); + + // Make sure the original JSON is untouched + assert.deepEqual(json, cJson); + + // Make sure the enabled references are resolved + assert.deepEqual(results.resolved.info.fileRef, projectJson); + assert.deepEqual(results.resolved.info.remoteRef, projectJson); + + // Make sure that disabled reference is untouched + assert.deepEqual(results.resolved.info.localRef, json.info.localRef); + }) + .then(done, done); + }); + + it('resolveLocalRefs = true', function (done) { + var cOptions = _.cloneDeep(options); + var cJson = _.cloneDeep(json); + + cOptions.resolveLocalRefs = true; + + jsonRefs.resolveRefs(cJson, cOptions) + .then(function (results) { + assert.notDeepEqual(cJson, results.resolved); + + // Make sure the original JSON is untouched + assert.deepEqual(json, cJson); + + // Make sure the enabled references are resolved + assert.deepEqual(results.resolved.info.fileRef, projectJson); + assert.deepEqual(results.resolved.info.remoteRef, projectJson); + + // Make sure that explicitly enabled reference does not change behavior + assert.deepEqual(results.resolved.info.localRef, json.definitions.item); + }) + .then(done, done); + }); + + it('resolveFileRefs = false', function (done) { + var cOptions = _.cloneDeep(options); + var cJson = _.cloneDeep(json); + + cOptions.resolveFileRefs = false; + + jsonRefs.resolveRefs(cJson, cOptions) + .then(function (results) { + assert.notDeepEqual(cJson, results.resolved); + + // Make sure the original JSON is untouched + assert.deepEqual(json, cJson); + + // Make sure the enabled references are resolved + assert.deepEqual(results.resolved.info.localRef, json.definitions.item); + assert.deepEqual(results.resolved.info.remoteRef, projectJson); + + // Make sure that disabled reference is untouched + assert.deepEqual(results.resolved.info.fileRef, json.info.fileRef); + }) + .then(done, done); + }); + + it('resolveFileRefs = true', function (done) { + var cOptions = _.cloneDeep(options); + var cJson = _.cloneDeep(json); + + cOptions.resolveFileRefs = true; + + jsonRefs.resolveRefs(cJson, cOptions) + .then(function (results) { + assert.notDeepEqual(cJson, results.resolved); + + // Make sure the original JSON is untouched + assert.deepEqual(json, cJson); + + // Make sure the enabled references are resolved + assert.deepEqual(results.resolved.info.localRef, json.definitions.item); + assert.deepEqual(results.resolved.info.remoteRef, projectJson); + + // Make sure that explicitly enabled reference does not change behavior + assert.deepEqual(results.resolved.info.fileRef, projectJson); + }) + .then(done, done); + }); + + it('resolveRemoteRefs = false', function (done) { + var cOptions = _.cloneDeep(options); + var cJson = _.cloneDeep(json); + + cOptions.resolveRemoteRefs = false; + + jsonRefs.resolveRefs(cJson, cOptions) + .then(function (results) { + assert.notDeepEqual(cJson, results.resolved); + + // Make sure the original JSON is untouched + assert.deepEqual(json, cJson); + + // Make sure the enabled references are resolved + assert.deepEqual(results.resolved.info.localRef, json.definitions.item); + assert.deepEqual(results.resolved.info.fileRef, projectJson); + + // Make sure that disabled reference is untouched + assert.deepEqual(results.resolved.info.remoteRef, json.info.remoteRef); + }) + .then(done, done); + }); + + it('resolveRemoteRefs = true', function (done) { + var cOptions = _.cloneDeep(options); + var cJson = _.cloneDeep(json); + + cOptions.resolveRemoteRefs = true; + + jsonRefs.resolveRefs(cJson, cOptions) + .then(function (results) { + assert.notDeepEqual(cJson, results.resolved); + + // Make sure the original JSON is untouched + assert.deepEqual(json, cJson); + + // Make sure the enabled references are resolved + assert.deepEqual(results.resolved.info.localRef, json.definitions.item); + assert.deepEqual(results.resolved.info.fileRef, projectJson); + + // Make sure that explicitly enabled reference does not change behavior + assert.deepEqual(results.resolved.info.remoteRef, projectJson); + }) + .then(done, done); + }); + }); }); });