Skip to content

Commit add5f77

Browse files
frk-dcjonluca
andauthored
fix: resolve root level references (#329)
Co-authored-by: JonLuca De Caro <[email protected]>
1 parent cebb1f6 commit add5f77

File tree

6 files changed

+113
-5
lines changed

6 files changed

+113
-5
lines changed

lib/pointer.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,12 @@ class Pointer {
8383
this.value = unwrapOrThrow(obj);
8484

8585
for (let i = 0; i < tokens.length; i++) {
86-
if (resolveIf$Ref(this, options)) {
86+
if (resolveIf$Ref(this, options, pathFromRoot)) {
8787
// The $ref path has changed, so append the remaining tokens to the path
8888
this.path = Pointer.join(this.path, tokens.slice(i));
8989
}
9090

91-
if (typeof this.value === "object" && this.value !== null && "$ref" in this.value) {
91+
if (typeof this.value === "object" && this.value !== null && !isRootPath(pathFromRoot) && "$ref" in this.value) {
9292
return this;
9393
}
9494

@@ -103,7 +103,7 @@ class Pointer {
103103

104104
// Resolve the final value
105105
if (!this.value || (this.value.$ref && url.resolve(this.path, this.value.$ref) !== pathFromRoot)) {
106-
resolveIf$Ref(this, options);
106+
resolveIf$Ref(this, options, pathFromRoot);
107107
}
108108

109109
return this;
@@ -224,15 +224,16 @@ class Pointer {
224224
*
225225
* @param pointer
226226
* @param options
227+
* @param [pathFromRoot] - the path of place that initiated resolving
227228
* @returns - Returns `true` if the resolution path changed
228229
*/
229-
function resolveIf$Ref(pointer: any, options: any) {
230+
function resolveIf$Ref(pointer: any, options: any, pathFromRoot?: any) {
230231
// Is the value a JSON reference? (and allowed?)
231232

232233
if ($Ref.isAllowed$Ref(pointer.value, options)) {
233234
const $refPath = url.resolve(pointer.path, pointer.value.$ref);
234235

235-
if ($refPath === pointer.path) {
236+
if ($refPath === pointer.path && !isRootPath(pathFromRoot)) {
236237
// The value is a reference to itself, so there's nothing to do.
237238
pointer.circular = true;
238239
} else {
@@ -294,3 +295,7 @@ function unwrapOrThrow(value: any) {
294295

295296
return value;
296297
}
298+
299+
function isRootPath(pathFromRoot: any): boolean {
300+
return typeof pathFromRoot == "string" && Pointer.parse(pathFromRoot).length == 0;
301+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export default {
2+
$ref: "#/definitions/user",
3+
definitions: {
4+
user: {
5+
properties: {
6+
userId: {
7+
type: "integer",
8+
},
9+
},
10+
required: ["userId"],
11+
type: "object",
12+
},
13+
},
14+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export default {
2+
definitions: {
3+
user: {
4+
type: "object",
5+
properties: {
6+
userId: {
7+
type: "integer",
8+
},
9+
},
10+
required: ["userId"],
11+
},
12+
},
13+
type: "object",
14+
properties: {
15+
userId: {
16+
type: "integer",
17+
},
18+
},
19+
required: ["userId"],
20+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, it } from "vitest";
2+
import $RefParser from "../../../lib/index.js";
3+
import helper from "../../utils/helper.js";
4+
import path from "../../utils/path.js";
5+
import parsedSchema from "./parsed.js";
6+
import dereferencedSchema from "./dereferenced.js";
7+
import bundledSchema from "./bundled.js";
8+
9+
import { expect } from "vitest";
10+
11+
describe("Schema with $ref at root level", () => {
12+
it("should parse successfully", async () => {
13+
const parser = new $RefParser();
14+
const schema = await parser.parse(path.rel("test/specs/internal-root-ref/internal-root-ref.yaml"));
15+
expect(schema).to.equal(parser.schema);
16+
expect(schema).to.deep.equal(parsedSchema);
17+
expect(parser.$refs.paths()).to.deep.equal([path.abs("test/specs/internal-root-ref/internal-root-ref.yaml")]);
18+
});
19+
20+
it(
21+
"should resolve successfully",
22+
helper.testResolve(
23+
path.rel("test/specs/internal-root-ref/internal-root-ref.yaml"),
24+
path.abs("test/specs/internal-root-ref/internal-root-ref.yaml"),
25+
parsedSchema,
26+
),
27+
);
28+
29+
it("should dereference successfully", async () => {
30+
const parser = new $RefParser();
31+
const schema = await parser.dereference(path.rel("test/specs/internal-root-ref/internal-root-ref.yaml"));
32+
expect(schema).to.equal(parser.schema);
33+
expect(schema).to.deep.equal(dereferencedSchema);
34+
// Reference equality
35+
// @ts-expect-error TS(2532): Object is possibly 'undefined'.
36+
expect(schema.properties.userId).to.equal(schema.definitions.user.properties.userId);
37+
expect(parser.$refs.circular).to.equal(false);
38+
});
39+
40+
it("should bundle successfully", async () => {
41+
const parser = new $RefParser();
42+
const schema = await parser.bundle(path.rel("test/specs/internal-root-ref/internal-root-ref.yaml"));
43+
expect(schema).to.equal(parser.schema);
44+
expect(schema).to.deep.equal(bundledSchema);
45+
});
46+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
$ref: "#/definitions/user"
2+
definitions:
3+
user:
4+
type: object
5+
properties:
6+
userId:
7+
type: integer
8+
required:
9+
- userId
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export default {
2+
$ref: "#/definitions/user",
3+
definitions: {
4+
user: {
5+
properties: {
6+
userId: {
7+
type: "integer",
8+
},
9+
},
10+
required: ["userId"],
11+
type: "object",
12+
},
13+
},
14+
};

0 commit comments

Comments
 (0)