Skip to content

Commit

Permalink
fix: resolve root level references (#329)
Browse files Browse the repository at this point in the history
Co-authored-by: JonLuca De Caro <[email protected]>
  • Loading branch information
frk-dc and jonluca authored Mar 5, 2024
1 parent cebb1f6 commit add5f77
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 5 deletions.
15 changes: 10 additions & 5 deletions lib/pointer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ class Pointer {
this.value = unwrapOrThrow(obj);

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

if (typeof this.value === "object" && this.value !== null && "$ref" in this.value) {
if (typeof this.value === "object" && this.value !== null && !isRootPath(pathFromRoot) && "$ref" in this.value) {
return this;
}

Expand All @@ -103,7 +103,7 @@ class Pointer {

// Resolve the final value
if (!this.value || (this.value.$ref && url.resolve(this.path, this.value.$ref) !== pathFromRoot)) {
resolveIf$Ref(this, options);
resolveIf$Ref(this, options, pathFromRoot);
}

return this;
Expand Down Expand Up @@ -224,15 +224,16 @@ class Pointer {
*
* @param pointer
* @param options
* @param [pathFromRoot] - the path of place that initiated resolving
* @returns - Returns `true` if the resolution path changed
*/
function resolveIf$Ref(pointer: any, options: any) {
function resolveIf$Ref(pointer: any, options: any, pathFromRoot?: any) {
// Is the value a JSON reference? (and allowed?)

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

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

return value;
}

function isRootPath(pathFromRoot: any): boolean {
return typeof pathFromRoot == "string" && Pointer.parse(pathFromRoot).length == 0;
}
14 changes: 14 additions & 0 deletions test/specs/internal-root-ref/bundled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default {
$ref: "#/definitions/user",
definitions: {
user: {
properties: {
userId: {
type: "integer",
},
},
required: ["userId"],
type: "object",
},
},
};
20 changes: 20 additions & 0 deletions test/specs/internal-root-ref/dereferenced.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export default {
definitions: {
user: {
type: "object",
properties: {
userId: {
type: "integer",
},
},
required: ["userId"],
},
},
type: "object",
properties: {
userId: {
type: "integer",
},
},
required: ["userId"],
};
46 changes: 46 additions & 0 deletions test/specs/internal-root-ref/internal-root-ref.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, it } from "vitest";
import $RefParser from "../../../lib/index.js";
import helper from "../../utils/helper.js";
import path from "../../utils/path.js";
import parsedSchema from "./parsed.js";
import dereferencedSchema from "./dereferenced.js";
import bundledSchema from "./bundled.js";

import { expect } from "vitest";

describe("Schema with $ref at root level", () => {
it("should parse successfully", async () => {
const parser = new $RefParser();
const schema = await parser.parse(path.rel("test/specs/internal-root-ref/internal-root-ref.yaml"));
expect(schema).to.equal(parser.schema);
expect(schema).to.deep.equal(parsedSchema);
expect(parser.$refs.paths()).to.deep.equal([path.abs("test/specs/internal-root-ref/internal-root-ref.yaml")]);
});

it(
"should resolve successfully",
helper.testResolve(
path.rel("test/specs/internal-root-ref/internal-root-ref.yaml"),
path.abs("test/specs/internal-root-ref/internal-root-ref.yaml"),
parsedSchema,
),
);

it("should dereference successfully", async () => {
const parser = new $RefParser();
const schema = await parser.dereference(path.rel("test/specs/internal-root-ref/internal-root-ref.yaml"));
expect(schema).to.equal(parser.schema);
expect(schema).to.deep.equal(dereferencedSchema);
// Reference equality
// @ts-expect-error TS(2532): Object is possibly 'undefined'.
expect(schema.properties.userId).to.equal(schema.definitions.user.properties.userId);
expect(parser.$refs.circular).to.equal(false);
});

it("should bundle successfully", async () => {
const parser = new $RefParser();
const schema = await parser.bundle(path.rel("test/specs/internal-root-ref/internal-root-ref.yaml"));
expect(schema).to.equal(parser.schema);
expect(schema).to.deep.equal(bundledSchema);
});
});
9 changes: 9 additions & 0 deletions test/specs/internal-root-ref/internal-root-ref.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
$ref: "#/definitions/user"
definitions:
user:
type: object
properties:
userId:
type: integer
required:
- userId
14 changes: 14 additions & 0 deletions test/specs/internal-root-ref/parsed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default {
$ref: "#/definitions/user",
definitions: {
user: {
properties: {
userId: {
type: "integer",
},
},
required: ["userId"],
type: "object",
},
},
};

0 comments on commit add5f77

Please sign in to comment.