diff --git a/docs/options.md b/docs/options.md
index ee4290a0..4d193a9b 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -74,3 +74,4 @@ The `dereference` options control how JSON Schema $Ref Parser will dereference `
|Option(s) |Type |Description
|:---------------------|:-------------------|:------------
|`circular`|`boolean` or `"ignore"`|Determines whether [circular `$ref` pointers](README.md#circular-refs) are handled.
If set to `false`, then a `ReferenceError` will be thrown if the schema contains any circular references.
If set to `"ignore"`, then circular references will simply be ignored. No error will be thrown, but the [`$Refs.circular`](refs.md#circular) property will still be set to `true`.
+|`id`|`boolean` or `"ignore"`|Determines whether [`$ref` pointers to id](https://json-schema.org/understanding-json-schema/structuring.html#using-id-with-ref) are handled.
If set to `true`, then the pointer will be dereferenced.
If set to `false`, then a `ReferenceError` will be thrown if the schema contains any references to ids.
If set to `"ignore"`, then references to id will simply be ignored. No error will be thrown.
diff --git a/lib/index.d.ts b/lib/index.d.ts
index e071da95..e9ef0127 100644
--- a/lib/index.d.ts
+++ b/lib/index.d.ts
@@ -221,6 +221,16 @@ declare namespace $RefParser {
* If set to `"ignore"`, then circular references will simply be ignored. No error will be thrown, but the `$Refs.circular` property will still be set to `true`.
*/
circular?: boolean | 'ignore'
+ /**
+ * Determines whether $ref pointers to id are handled.
+ *
+ * If set to `true`, then the pointer will be dereferenced.
+ *
+ * If set to `false`, then a `ReferenceError` will be thrown if the schema contains any references to ids.
+ *
+ * If set to `"ignore"`, then references to id will simply be ignored. No error will be thrown. (default)
+ */
+ id?: boolean | 'ignore'
}
}
diff --git a/lib/pointer.js b/lib/pointer.js
index 9f5baf17..11d86bd4 100644
--- a/lib/pointer.js
+++ b/lib/pointer.js
@@ -72,7 +72,27 @@ function Pointer ($ref, path, friendlyPath) {
* of the resolved value.
*/
Pointer.prototype.resolve = function (obj, options) {
- let tokens = Pointer.parse(this.path);
+ let tokens = Pointer.parse(this.path, options);
+
+ // it's a reference to an id
+ if (typeof tokens === "string") {
+ this.path = tokens;
+
+ // Crawl the object, one token at a time
+ let def = Object.keys(obj.definitions || {}).find((key) => {
+ return obj.definitions[key].$id === tokens;
+ });
+
+ if (def === undefined) {
+ throw ono.syntax(`Error resolving $ref pointer "${this.originalPath}". \Id "${tokens}" does not exist.`);
+ }
+
+ this.value = obj.definitions[def];
+
+ // Resolve the final value
+ resolveIf$Ref(this, options);
+ return this;
+ }
// Crawl the object, one token at a time
this.value = obj;
@@ -150,9 +170,10 @@ Pointer.prototype.set = function (obj, value, options) {
* {@link https://tools.ietf.org/html/rfc6901#section-3}
*
* @param {string} path
+ * @param {$RefParserOptions} options
* @returns {string[]}
*/
-Pointer.parse = function (path) {
+Pointer.parse = function (path, options) {
// Get the JSON pointer from the path's hash
let pointer = url.getHash(path).substr(1);
@@ -171,7 +192,12 @@ Pointer.parse = function (path) {
}
if (pointer[0] !== "") {
- throw ono.syntax(`Invalid $ref pointer "${pointer}". Pointers must begin with "#/"`);
+ if (options && options.dereference.id === true) {
+ return "#" + pointer[0];
+ }
+ else {
+ throw ono.syntax(`Invalid $ref pointer "${pointer}". Pointers must begin with "#/"`);
+ }
}
return pointer.slice(1);
diff --git a/lib/ref.js b/lib/ref.js
index 30c69f45..831c8738 100644
--- a/lib/ref.js
+++ b/lib/ref.js
@@ -133,7 +133,12 @@ $Ref.isAllowed$Ref = function (value, options) {
// It's an external reference, which is allowed by the options
return true;
}
+ else if (value.$ref[0] === "#" && value.$ref.length > 1 && options && typeof options.dereference.id !== "undefined" && options.dereference.id !== "ignore") {
+ // It's a reference to an id, which is allowed by the options
+ return true;
+ }
}
+ return false;
};
/**
diff --git a/test/specs/internal-with-id/bundled.js b/test/specs/internal-with-id/bundled.js
new file mode 100644
index 00000000..27a941e3
--- /dev/null
+++ b/test/specs/internal-with-id/bundled.js
@@ -0,0 +1,32 @@
+"use strict";
+
+module.exports = {
+ definitions: {
+ address: {
+ required: ["streetAddress", "city", "state"],
+ $id: "#address",
+ type: "object",
+ properties: {
+ streetAddress: {
+ type: "string"
+ },
+ city: {
+ type: "string"
+ },
+ state: {
+ type: "string"
+ }
+ }
+ }
+ },
+ type: "object",
+ properties: {
+ billingAddress: {
+ $ref: "#address"
+ },
+ shippingAddress: {
+ $ref: "#address"
+ }
+ },
+ title: "Customer"
+};
diff --git a/test/specs/internal-with-id/dereferenced.js b/test/specs/internal-with-id/dereferenced.js
new file mode 100644
index 00000000..96e724f0
--- /dev/null
+++ b/test/specs/internal-with-id/dereferenced.js
@@ -0,0 +1,58 @@
+"use strict";
+
+module.exports = {
+ definitions: {
+ address: {
+ required: ["streetAddress", "city", "state"],
+ $id: "#address",
+ type: "object",
+ properties: {
+ streetAddress: {
+ type: "string"
+ },
+ city: {
+ type: "string"
+ },
+ state: {
+ type: "string"
+ }
+ }
+ }
+ },
+ type: "object",
+ properties: {
+ billingAddress: {
+ $id: "#address",
+ required: ["streetAddress", "city", "state"],
+ type: "object",
+ properties: {
+ streetAddress: {
+ type: "string"
+ },
+ city: {
+ type: "string"
+ },
+ state: {
+ type: "string"
+ }
+ }
+ },
+ shippingAddress: {
+ $id: "#address",
+ required: ["streetAddress", "city", "state"],
+ type: "object",
+ properties: {
+ streetAddress: {
+ type: "string"
+ },
+ city: {
+ type: "string"
+ },
+ state: {
+ type: "string"
+ }
+ }
+ }
+ },
+ title: "Customer"
+};
diff --git a/test/specs/internal-with-id/internal-with-id.spec.js b/test/specs/internal-with-id/internal-with-id.spec.js
new file mode 100644
index 00000000..39c2461e
--- /dev/null
+++ b/test/specs/internal-with-id/internal-with-id.spec.js
@@ -0,0 +1,52 @@
+"use strict";
+
+const { expect } = require("chai");
+const $RefParser = require("../../..");
+const helper = require("../../utils/helper");
+const path = require("../../utils/path");
+const parsedSchema = require("./parsed");
+const dereferencedSchema = require("./dereferenced");
+const bundledSchema = require("./bundled");
+
+describe("Schema with internal $refs with id", () => {
+ it("should parse successfully", async () => {
+ let parser = new $RefParser();
+ const schema = await parser.parse(
+ path.rel("specs/internal-with-id/internal-with-id.yaml")
+ );
+ expect(schema).to.equal(parser.schema);
+ expect(schema).to.deep.equal(parsedSchema);
+ expect(parser.$refs.paths()).to.deep.equal([
+ path.abs("specs/internal-with-id/internal-with-id.yaml")
+ ]);
+ });
+
+ it(
+ "should resolve successfully",
+ helper.testResolve(
+ path.rel("specs/internal-with-id/internal-with-id.yaml"),
+ path.abs("specs/internal-with-id/internal-with-id.yaml"),
+ parsedSchema
+ )
+ );
+
+ it("should dereference successfully", async () => {
+ let parser = new $RefParser();
+ const schema = await parser.dereference(
+ path.rel("specs/internal-with-id/internal-with-id.yaml"), { dereference: { id: true }}
+ );
+ expect(schema).to.equal(parser.schema);
+ expect(schema).to.deep.equal(dereferencedSchema);
+ // The "circular" flag should NOT be set
+ expect(parser.$refs.circular).to.equal(false);
+ });
+
+ it("should bundle successfully", async () => {
+ let parser = new $RefParser();
+ const schema = await parser.bundle(
+ path.rel("specs/internal-with-id/internal-with-id.yaml")
+ );
+ expect(schema).to.equal(parser.schema);
+ expect(schema).to.deep.equal(bundledSchema);
+ });
+});
diff --git a/test/specs/internal-with-id/internal-with-id.yaml b/test/specs/internal-with-id/internal-with-id.yaml
new file mode 100644
index 00000000..9454b989
--- /dev/null
+++ b/test/specs/internal-with-id/internal-with-id.yaml
@@ -0,0 +1,22 @@
+title: Customer
+type: object
+definitions:
+ address:
+ $id: "#address"
+ type: object
+ properties:
+ streetAddress:
+ type: string
+ city:
+ type: string
+ state:
+ type: string
+ required:
+ - streetAddress
+ - city
+ - state
+properties:
+ billingAddress:
+ $ref: "#address"
+ shippingAddress:
+ $ref: "#address"
diff --git a/test/specs/internal-with-id/parsed.js b/test/specs/internal-with-id/parsed.js
new file mode 100644
index 00000000..27a941e3
--- /dev/null
+++ b/test/specs/internal-with-id/parsed.js
@@ -0,0 +1,32 @@
+"use strict";
+
+module.exports = {
+ definitions: {
+ address: {
+ required: ["streetAddress", "city", "state"],
+ $id: "#address",
+ type: "object",
+ properties: {
+ streetAddress: {
+ type: "string"
+ },
+ city: {
+ type: "string"
+ },
+ state: {
+ type: "string"
+ }
+ }
+ }
+ },
+ type: "object",
+ properties: {
+ billingAddress: {
+ $ref: "#address"
+ },
+ shippingAddress: {
+ $ref: "#address"
+ }
+ },
+ title: "Customer"
+};