Skip to content

Commit 8e8fb5a

Browse files
Merge pull request #169 from stoplightio/fix/range-error-circular-dereferencing
Handle direct external circular references
2 parents 5674d19 + 05b5b18 commit 8e8fb5a

File tree

8 files changed

+62
-5
lines changed

8 files changed

+62
-5
lines changed

lib/pointer.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,15 @@ function Pointer ($ref, path, friendlyPath) {
6464
*
6565
* @param {*} obj - The object that will be crawled
6666
* @param {$RefParserOptions} options
67+
* @param {string} pathFromRoot - the path of place that initiated resolving
6768
*
6869
* @returns {Pointer}
6970
* Returns a JSON pointer whose {@link Pointer#value} is the resolved value.
7071
* If resolving this value required resolving other JSON references, then
7172
* the {@link Pointer#$ref} and {@link Pointer#path} will reflect the resolution path
7273
* of the resolved value.
7374
*/
74-
Pointer.prototype.resolve = function (obj, options) {
75+
Pointer.prototype.resolve = function (obj, options, pathFromRoot) {
7576
let tokens = Pointer.parse(this.path, this.originalPath);
7677

7778
// Crawl the object, one token at a time
@@ -83,6 +84,10 @@ Pointer.prototype.resolve = function (obj, options) {
8384
this.path = Pointer.join(this.path, tokens.slice(i));
8485
}
8586

87+
if (typeof this.value === "object" && this.value !== null && "$ref" in this.value) {
88+
return this;
89+
}
90+
8691
let token = tokens[i];
8792
if (this.value[token] === undefined || this.value[token] === null) {
8893
this.value = null;
@@ -94,7 +99,10 @@ Pointer.prototype.resolve = function (obj, options) {
9499
}
95100

96101
// Resolve the final value
97-
resolveIf$Ref(this, options);
102+
if (!this.value || this.value.$ref && url.resolve(this.path, this.value.$ref) !== pathFromRoot) {
103+
resolveIf$Ref(this, options);
104+
}
105+
98106
return this;
99107
};
100108

@@ -226,7 +234,7 @@ function resolveIf$Ref (pointer, options) {
226234
pointer.circular = true;
227235
}
228236
else {
229-
let resolved = pointer.$ref.$refs._resolve($refPath, url.getHash(pointer.path), options);
237+
let resolved = pointer.$ref.$refs._resolve($refPath, pointer.path, options);
230238
pointer.indirections += resolved.indirections + 1;
231239

232240
if ($Ref.isExtended$Ref(pointer.value)) {

lib/ref.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ $Ref.prototype.get = function (path, options) {
112112
$Ref.prototype.resolve = function (path, options, friendlyPath, pathFromRoot) {
113113
let pointer = new Pointer(this, path, friendlyPath);
114114
try {
115-
return pointer.resolve(this.value, options);
115+
return pointer.resolve(this.value, options, pathFromRoot);
116116
}
117117
catch (err) {
118118
if (!options || !options.continueOnError || !isHandledError(err)) {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
foo:
2+
$ref: ./circular-external-direct-root.yaml#/foo
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
$ref: ./circular-external-direct-child.yaml#/foo
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"use strict";
2+
3+
const chai = require("chai");
4+
const chaiSubset = require("chai-subset");
5+
chai.use(chaiSubset);
6+
const { expect } = chai;
7+
const $RefParser = require("../../../lib");
8+
const path = require("../../utils/path");
9+
const parsedSchema = require("./parsed");
10+
const dereferencedSchema = require("./dereferenced");
11+
12+
describe("Schema with direct circular (recursive) external $refs", () => {
13+
it("should parse successfully", async () => {
14+
let parser = new $RefParser();
15+
const schema = await parser.parse(path.rel("specs/circular-external-direct/circular-external-direct-root.yaml"));
16+
expect(schema).to.equal(parser.schema);
17+
expect(schema).to.deep.equal(parsedSchema.schema);
18+
expect(parser.$refs.paths()).to.deep.equal([path.abs("specs/circular-external-direct/circular-external-direct-root.yaml")]);
19+
// The "circular" flag should NOT be set
20+
// (it only gets set by `dereference`)
21+
expect(parser.$refs.circular).to.equal(false);
22+
});
23+
24+
it("should dereference successfully", async () => {
25+
let parser = new $RefParser();
26+
const schema = await parser.dereference(path.rel("specs/circular-external-direct/circular-external-direct-root.yaml"));
27+
expect(schema).to.equal(parser.schema);
28+
expect(schema).to.deep.equal(dereferencedSchema);
29+
// The "circular" flag should be set
30+
expect(parser.$refs.circular).to.equal(true);
31+
});
32+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"use strict";
2+
3+
module.exports =
4+
{
5+
$ref: "./circular-external-direct-root.yaml#/foo",
6+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"use strict";
2+
3+
module.exports =
4+
{
5+
schema: {
6+
$ref: "./circular-external-direct-child.yaml#/foo",
7+
},
8+
};

test/specs/circular-external/dereferenced.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const dereferencedSchema = module.exports =
2525
},
2626
},
2727
thing: {
28-
$ref: "#/definitions/thing"
28+
$ref: "circular-external.yaml#/definitions/thing"
2929
},
3030
person: {
3131
title: "person",

0 commit comments

Comments
 (0)