Skip to content

Commit 8cb25e3

Browse files
Fixed some circular reference bugs
1 parent defac46 commit 8cb25e3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2625
-641
lines changed

dist/ref-parser.js

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,11 @@ function dereference(parser, options) {
138138
* @param {object[]} parents - An array of the parent objects that have already been dereferenced
139139
* @param {$Refs} $refs - The resolved JSON references
140140
* @param {$RefParserOptions} options
141+
* @returns {boolean} - Returns true if a circular reference was found
141142
*/
142143
function crawl(obj, path, parents, $refs, options) {
144+
var isCircular = false;
145+
143146
if (obj && typeof(obj) === 'object') {
144147
parents.push(obj);
145148

@@ -155,53 +158,83 @@ function crawl(obj, path, parents, $refs, options) {
155158

156159
// Check for circular references
157160
var circular = pointer.circular || parents.indexOf(pointer.value) !== -1;
158-
$refs.circular = $refs.circular || circular;
159-
if ($refs.circular && !options.$refs.circular) {
160-
throw ono.reference('Circular $ref pointer found at %s', keyPath);
161-
}
161+
circular && (isCircular = foundCircularReference(keyPath, $refs, options));
162162

163163
// Dereference the JSON reference
164-
value = dereference$Ref(obj, key, pointer.value);
164+
var dereferencedValue = getDereferencedValue(value, pointer.value);
165165

166166
// Crawl the dereferenced value (unless it's circular)
167167
if (!circular) {
168-
crawl(value, pointer.path, parents, $refs, options);
168+
// If the `crawl` method returns true, then dereferenced value is circular
169+
circular = crawl(dereferencedValue, pointer.path, parents, $refs, options);
170+
isCircular = isCircular || circular;
171+
}
172+
173+
// Replace the JSON reference with the dereferenced value
174+
if (!circular || options.$refs.circular === true) {
175+
obj[key] = dereferencedValue;
169176
}
170177
}
171-
else if (parents.indexOf(value) === -1) {
172-
crawl(value, keyPath, parents, $refs, options);
178+
else {
179+
if (parents.indexOf(value) === -1) {
180+
isCircular = crawl(value, keyPath, parents, $refs, options);
181+
}
182+
else {
183+
isCircular = foundCircularReference(keyPath, $refs, options);
184+
}
173185
}
174186
});
175187

176188
parents.pop();
177189
}
190+
return isCircular;
178191
}
179192

180193
/**
181-
* Replaces the specified JSON reference with its resolved value.
194+
* Returns the dereferenced value of the given JSON reference.
182195
*
183-
* @param {object} obj - The object that contains the JSON reference
184-
* @param {string} key - The key of the JSON reference within `obj`
185-
* @param {*} value - The resolved value
186-
* @returns {*} - Returns the new value of the JSON reference
196+
* @param {object} currentValue - the current value, which contains a JSON reference ("$ref" property)
197+
* @param {*} resolvedValue - the resolved value, which can be any type
198+
* @returns {*} - Returns the dereferenced value
187199
*/
188-
function dereference$Ref(obj, key, value) {
189-
var $refObj = obj[key];
190-
191-
if (value && typeof(value) === 'object' && Object.keys($refObj).length > 1) {
192-
// The JSON reference has additional properties (other than "$ref"),
200+
function getDereferencedValue(currentValue, resolvedValue) {
201+
if (resolvedValue && typeof(resolvedValue) === 'object' && Object.keys(currentValue).length > 1) {
202+
// The current value has additional properties (other than "$ref"),
193203
// so merge the resolved value rather than completely replacing the reference
194-
delete $refObj.$ref;
195-
Object.keys(value).forEach(function(key) {
196-
if (!(key in $refObj)) {
197-
$refObj[key] = value[key];
204+
var merged = {};
205+
Object.keys(currentValue).forEach(function(key) {
206+
if (key !== '$ref') {
207+
merged[key] = currentValue[key];
208+
}
209+
});
210+
Object.keys(resolvedValue).forEach(function(key) {
211+
if (!(key in merged)) {
212+
merged[key] = resolvedValue[key];
198213
}
199214
});
215+
return merged;
200216
}
201217
else {
202218
// Completely replace the original reference with the resolved value
203-
return obj[key] = value;
219+
return resolvedValue;
220+
}
221+
}
222+
223+
/**
224+
* Called when a circular reference is found.
225+
* It sets the {@link $Refs#circular} flag, and throws an error if options.$refs.circular is false.
226+
*
227+
* @param {string} keyPath - The JSON Reference path of the circular reference
228+
* @param {$Refs} $refs
229+
* @param {$RefParserOptions} options
230+
* @returns {boolean} - always returns true, to indicate that a circular reference was found
231+
*/
232+
function foundCircularReference(keyPath, $refs, options) {
233+
$refs.circular = true;
234+
if (!options.$refs.circular) {
235+
throw ono.reference('Circular $ref pointer found at %s', keyPath);
204236
}
237+
return true;
205238
}
206239

207240
},{"./pointer":6,"./ref":9,"./util":12,"ono":65,"url":90}],3:[function(require,module,exports){
@@ -542,7 +575,8 @@ function $RefParserOptions(options) {
542575
/**
543576
* Allow circular (recursive) JSON references?
544577
* If false, then a {@link ReferenceError} will be thrown if a circular reference is found.
545-
* @type {boolean}
578+
* If "ignore", then circular references will not be dereferenced.
579+
* @type {boolean|string}
546580
*/
547581
circular: true
548582
};
@@ -880,20 +914,20 @@ Pointer.join = function(base, tokens) {
880914
function resolveIf$Ref(pointer, options) {
881915
// Is the value a JSON reference? (and allowed?)
882916
if ($Ref.isAllowed$Ref(pointer.value, options)) {
883-
// Does the JSON reference have other properties (other than "$ref")?
884-
// If so, then don't resolve it, since it represents a new type
885-
if (Object.keys(pointer.value).length === 1) {
886-
var $refPath = url.resolve(pointer.path, pointer.value.$ref);
887-
888-
if ($refPath === pointer.path) {
889-
// The value is a reference to itself, so there's nothing to do.
890-
pointer.circular = true;
891-
}
892-
else {
917+
var $refPath = url.resolve(pointer.path, pointer.value.$ref);
918+
919+
if ($refPath === pointer.path) {
920+
// The value is a reference to itself, so there's nothing to do.
921+
pointer.circular = true;
922+
}
923+
else {
924+
// Does the JSON reference have other properties (other than "$ref")?
925+
// If so, then don't resolve it, since it represents a new type
926+
if (Object.keys(pointer.value).length === 1) {
893927
// Resolve the reference
894928
var resolved = pointer.$ref.$refs._resolve($refPath);
895929
pointer.$ref = resolved.$ref;
896-
pointer.path = resolved.path; // pointer.path = $refPath ???
930+
pointer.path = resolved.path;
897931
pointer.value = resolved.value;
898932
return true;
899933
}

dist/ref-parser.js.map

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

0 commit comments

Comments
 (0)