@@ -16,63 +16,95 @@ module.exports = bundle;
1616/**
1717 * Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
1818 * only has *internal* references, not any *external* references.
19- * This method mutates the JSON schema object, adding new references and remapping existing ones.
19+ * This method mutates the JSON schema object, adding new references and re-mapping existing ones.
2020 *
2121 * @param {$RefParser} parser
2222 * @param {$RefParserOptions} options
2323 */
2424function bundle(parser, options) {
2525 util.debug('Bundling $ref pointers in %s', parser._basePath);
2626
27- var $refs = parser.$refs;
28- var basePath = util.path.stripHash(parser._basePath);
29- Object.keys($refs._$refs).forEach(function(key) {
30- var $ref = $refs._$refs[key];
31-
32- if (!$ref.pathFromRoot) {
33- // This is the root of the JSON schema, so we don't need to do anything
34- // since all of its $refs are already relative to the schema root
35- return;
36- }
27+ remap(parser.$refs, options);
28+ dereference(parser._basePath, parser.$refs, options);
29+ }
3730
38- // Crawl the value, re-mapping its pointer
39- crawl($ref.value, $ref.path + '#', $ref.pathFromRoot, parser.$refs, options);
31+ /**
32+ * Re-maps all $ref pointers in the schema, so that they are relative to the root of the schema.
33+ *
34+ * @param {$Refs} $refs
35+ * @param {$RefParserOptions} options
36+ */
37+ function remap($refs, options) {
38+ var remapped = [];
4039
41- // Replace the original $ref with the resolved value
42- //$refs.set(basePath + $ref.pathFromRoot, $ref.value, options);
40+ // Crawl the schema and determine the re-mapped values for all $ref pointers.
41+ // NOTE: We don't actually APPLY the re-mappings them yet, since that can affect other re-mappings
42+ Object.keys($refs._$refs).forEach(function(key) {
43+ var $ref = $refs._$refs[key];
44+ crawl($ref.value, $ref.path + '#', $refs, remapped, options);
4345 });
46+
47+ // Now APPLY all of the re-mappings
48+ for (var i = 0; i < remapped.length; i++) {
49+ var mapping = remapped[i];
50+ mapping.old.$ref = mapping.new.$ref;
51+ }
4452}
4553
4654/**
55+ * Recursively crawls the given value, and re-maps any JSON references.
4756 *
48- * @param obj
49- * @param path
50- * @param pointer
51- * @param $refs
52- * @param options
57+ * @param {*} obj - The value to crawl. If it's not an object or array, it will be ignored.
58+ * @param {string} path - The path to use for resolving relative JSON references
59+ * @param {$Refs} $refs - The resolved JSON references
60+ * @param {object[]} remapped - An array of the re-mapped JSON references
61+ * @param {$RefParserOptions} options
5362 */
54- function crawl(obj, path, pointer, $refs, options) {
63+ function crawl(obj, path, $refs, remapped , options) {
5564 if (obj && typeof(obj) === 'object') {
5665 Object.keys(obj).forEach(function(key) {
5766 var keyPath = path + '/' + key;
5867 var value = obj[key];
5968
6069 if ($Ref.is$Ref(value)) {
6170 // We found a $ref, so resolve it
62- util.debug('Bundling $ref pointer "%s" at %s', value.$ref, keyPath);
71+ util.debug('Re-mapping $ref pointer "%s" at %s', value.$ref, keyPath);
6372 var $refPath = url.resolve(path, value.$ref);
64- var resolved$Ref = $refs._resolve($refPath, options);
65- var $ref = resolved$Ref.$ref;
66-
67- //value.$ref = $ref.pathFromRoot
73+ var pointer = $refs._resolve($refPath, options);
74+
75+ // Re-map the value
76+ var new$RefPath = pointer.$ref.pathFromRoot + util.path.getHash(pointer.path).substr(1);
77+ util.debug(' new value: %s', new$RefPath);
78+ remapped.push({
79+ old: value,
80+ new: {$ref: new$RefPath}
81+ });
6882 }
6983 else {
70- crawl(value, keyPath, pointer, $refs, options);
84+ crawl(value, keyPath, $refs, remapped , options);
7185 }
7286 });
7387 }
7488}
7589
90+ /**
91+ * Dereferences each external $ref pointer exactly ONCE.
92+ *
93+ * @param {string} basePath
94+ * @param {$Refs} $refs
95+ * @param {$RefParserOptions} options
96+ */
97+ function dereference(basePath, $refs, options) {
98+ basePath = util.path.stripHash(basePath);
99+
100+ Object.keys($refs._$refs).forEach(function(key) {
101+ var $ref = $refs._$refs[key];
102+ if ($ref.pathFromRoot) {
103+ $refs.set(basePath + $ref.pathFromRoot, $ref.value, options);
104+ }
105+ });
106+ }
107+
76108},{"./ref":9,"./util":12,"url":90}],2:[function(require,module,exports){
77109'use strict';
78110
@@ -91,7 +123,7 @@ module.exports = dereference;
91123 */
92124function dereference(parser, options) {
93125 util.debug('Dereferencing $ref pointers in %s', parser._basePath);
94- crawl(parser.schema, parser._basePath + '#' , [], parser.$refs, options);
126+ crawl(parser.schema, parser._basePath, [], parser.$refs, options);
95127}
96128
97129/**
@@ -106,6 +138,7 @@ function dereference(parser, options) {
106138function crawl(obj, path, parents, $refs, options) {
107139 if (obj && typeof(obj) === 'object') {
108140 parents.push(obj);
141+ path = util.path.ensureHash(path);
109142
110143 Object.keys(obj).forEach(function(key) {
111144 var keyPath = path + '/' + key;
@@ -115,14 +148,14 @@ function crawl(obj, path, parents, $refs, options) {
115148 // We found a $ref, so resolve it
116149 util.debug('Dereferencing $ref pointer "%s" at %s', value.$ref, keyPath);
117150 var $refPath = url.resolve(path, value.$ref);
118- var resolved$Ref = $refs._resolve($refPath, options);
151+ var pointer = $refs._resolve($refPath, options);
119152
120153 // Dereference the JSON reference
121- obj[key] = value = resolved$Ref .value;
154+ obj[key] = value = pointer .value;
122155
123156 // Crawl the dereferenced value (unless it's circular)
124157 if (parents.indexOf(value) === -1) {
125- crawl(resolved$Ref .value, resolved$Ref .path + '#' , parents, $refs, options);
158+ crawl(pointer .value, pointer .path, parents, $refs, options);
126159 }
127160 }
128161 else if (parents.indexOf(value) === -1) {
@@ -644,15 +677,18 @@ function Pointer($ref, path) {
644677 */
645678Pointer.prototype.resolve = function(obj, options) {
646679 var tokens = Pointer.parse(this.path);
647- this.value = obj;
648680
649- // Crawl the value, one token at a time
681+ // Crawl the object, one token at a time
682+ this.value = obj;
650683 for (var i = 0; i < tokens.length; i++) {
651- resolveIf$Ref(this, options);
684+ if (resolveIf$Ref(this, options)) {
685+ // The $ref path has changed, so append the remaining tokens to the path
686+ this.path += '#/' + tokens.slice(i).join('/');
687+ }
652688
653689 var token = tokens[i];
654690 if (this.value[token] === undefined) {
655- throw ono.syntax('Error resolving $ref pointer "%s". \nToken "%s" does not exist.', path, token);
691+ throw ono.syntax('Error resolving $ref pointer "%s". \nToken "%s" does not exist.', this. path, token);
656692 }
657693 else {
658694 this.value = this.value[token];
@@ -676,6 +712,7 @@ Pointer.prototype.resolve = function(obj, options) {
676712 */
677713Pointer.prototype.set = function(obj, value, options) {
678714 var tokens = Pointer.parse(this.path);
715+ var token;
679716
680717 if (tokens.length === 0) {
681718 // There are no tokens, replace the entire object with the new value
@@ -688,7 +725,7 @@ Pointer.prototype.set = function(obj, value, options) {
688725 for (var i = 0; i < tokens.length - 1; i++) {
689726 resolveIf$Ref(this, options);
690727
691- var token = tokens[i];
728+ token = tokens[i];
692729 if (this.value && this.value[token] !== undefined) {
693730 // The token exists
694731 this.value = this.value[token];
@@ -752,6 +789,7 @@ Pointer.parse = function(path) {
752789 *
753790 * @param {Pointer} pointer
754791 * @param {$RefParserOptions} [options]
792+ * @returns {boolean} - Returns `true` if the resolution path changed
755793 */
756794function resolveIf$Ref(pointer, options) {
757795 // Is the value a JSON reference? (and allowed?)
@@ -765,6 +803,7 @@ function resolveIf$Ref(pointer, options) {
765803 pointer.$ref = resolved.$ref;
766804 pointer.path = resolved.path; // pointer.path = $refPath ???
767805 pointer.value = resolved.value;
806+ return true;
768807 }
769808 }
770809}
@@ -1028,8 +1067,7 @@ function download(protocol, u, options) {
10281067module.exports = $Ref;
10291068
10301069var Pointer = require('./pointer'),
1031- util = require('./util'),
1032- ono = require('ono');
1070+ util = require('./util');
10331071
10341072/**
10351073 * This class represents a single JSON reference and its resolved value.
@@ -1075,9 +1113,9 @@ function $Ref($refs, path) {
10751113 *
10761114 * This property is used by the {@link $RefParser.bundle} method to re-map other JSON references.
10771115 *
1078- * @type {? string}
1116+ * @type {string}
10791117 */
1080- this.pathFromRoot = undefined ;
1118+ this.pathFromRoot = '' ;
10811119
10821120 /**
10831121 * The resolved value of the JSON reference.
@@ -1219,7 +1257,7 @@ $Ref.isAllowed$Ref = function(value, options) {
12191257 }
12201258};
12211259
1222- },{"./pointer":6,"./util":12,"ono":50 }],10:[function(require,module,exports){
1260+ },{"./pointer":6,"./util":12}],10:[function(require,module,exports){
12231261'use strict';
12241262
12251263var Options = require('./options'),
@@ -1505,7 +1543,7 @@ function crawl$Ref(path, pathFromRoot, $refs, options) {
15051543 // If a cached $ref is returned, then we DON'T need to crawl it
15061544 if (!$ref.cached) {
15071545 // This is a new $ref, so store the path from the root of the JSON schema to this $ref
1508- $ref.pathFromRoot= pathFromRoot;
1546+ $ref.pathFromRoot = pathFromRoot;
15091547
15101548 // Crawl the new $ref
15111549 util.debug('Resolving $ref pointers in %s', $ref.path);
@@ -1582,6 +1620,16 @@ exports.isUrl = function isUrl(path) {
15821620 return protocolPattern.test(path);
15831621};
15841622
1623+ /**
1624+ * Adds a hash to the given path, if it doesn't already have one.
1625+ *
1626+ * @param {string} path
1627+ * @returns {string}
1628+ */
1629+ exports.ensureHash = function ensureHash(path) {
1630+ return path.indexOf('#') === -1 ? path + '#' : path;
1631+ };
1632+
15851633/**
15861634 * Returns the hash (URL fragment), if any, of the given path.
15871635 *
0 commit comments