diff --git a/README.md b/README.md
index 5d5c5e0e..0b46b7ab 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,7 @@ JSON Schema $Ref Parser is a full [JSON Reference](https://tools.ietf.org/html/d
- Can [dereference](https://apidevtools.org/json-schema-ref-parser/docs/ref-parser.html#dereferencepath-options-callback) your schema, producing a plain-old JavaScript object that's easy to work with
- Supports [circular references](https://apidevtools.org/json-schema-ref-parser/docs/#circular-refs), nested references, back-references, and cross-references between files
- Maintains object reference equality — `$ref` pointers to the same value always resolve to the same object instance
+- Can selectively `resolve` (and therefore [dereference](https://apidevtools.org/json-schema-ref-parser/docs/ref-parser.html#dereferencepath-options-callback)) only some references `$ref` pointers based upon a custom javascript function.
- [Tested](https://travis-ci.com/APIDevTools/json-schema-ref-parser) in Node and all major web browsers on Windows, Mac, and Linux
diff --git a/docs/options.md b/docs/options.md
index ee4290a0..13603346 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -28,7 +28,15 @@ $RefParser.dereference("my-schema.yaml", {
}
},
dereference: {
- circular: false // Don't allow circular $refs
+ circular: false // Don't allow circular $refs
+
+ // custom function that decides if a particular $ref is allowed to be resolved.
+ isRefResolved: function(value) {
+
+ // resolve the reference if it doesn't match /NZCodeSets or /ISO
+ return value.$ref.match(/\/NZCodeSets/g) === null &&
+ value.$ref.match(/\/ISO/g) === null
+ }
}
});
```
@@ -74,3 +82,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`.
+|`isRefResolved`|`function (value)` | Custom javascript function that determines if particular `$ref` pointers will be resolved or not. e.g. based upon the `$ref` value matching a `regexp`.
Function takes a single `value` parameter and returns `true` if the `$ref` is to be resolved; `false` if the `$ref` is not to be resolved|
\ No newline at end of file
diff --git a/lib/ref.js b/lib/ref.js
index 30c69f45..6d15a6ac 100644
--- a/lib/ref.js
+++ b/lib/ref.js
@@ -125,6 +125,12 @@ $Ref.isExternal$Ref = function (value) {
*/
$Ref.isAllowed$Ref = function (value, options) {
if ($Ref.is$Ref(value)) {
+
+ // We might have a custom resolve decision to make
+ if (options && options.dereference && options.dereference.isRefResolved) {
+ return options.dereference.isRefResolved(value);
+ }
+
if (value.$ref.substr(0, 2) === "#/" || value.$ref === "#") {
// It's a JSON Pointer reference, which is always allowed
return true;
diff --git a/test/specs/custom/bundled.js b/test/specs/custom/bundled.js
new file mode 100644
index 00000000..8eaa29ac
--- /dev/null
+++ b/test/specs/custom/bundled.js
@@ -0,0 +1,68 @@
+"use strict";
+
+module.exports =
+{
+ definitions: {
+ requiredString: {
+ title: "requiredString",
+ minLength: 1,
+ type: "string"
+ },
+ genderEnum: {
+ enum: [
+ "male",
+ "female"
+ ],
+ type: "string"
+ },
+ name: {
+ required: [
+ "first",
+ "last"
+ ],
+ type: "object",
+ properties: {
+ first: {
+ $ref: "#/definitions/requiredString"
+ },
+ last: {
+ $ref: "#/definitions/requiredString"
+ },
+ middle: {
+ type: {
+ $ref: "#/definitions/requiredString/type"
+ },
+ minLength: {
+ $ref: "#/definitions/requiredString/minLength"
+ }
+ },
+ prefix: {
+ $ref: "#/definitions/requiredString",
+ minLength: 3
+ },
+ suffix: {
+ type: "string",
+ $ref: "#/definitions/name/properties/prefix",
+ maxLength: 3
+ }
+ }
+ }
+ },
+ required: [
+ "name"
+ ],
+ type: "object",
+ properties: {
+ gender: {
+ $ref: "#/definitions/genderEnum"
+ },
+ age: {
+ minimum: 0,
+ type: "integer"
+ },
+ name: {
+ $ref: "#/definitions/name"
+ }
+ },
+ title: "Person"
+};
diff --git a/test/specs/custom/custom.spec.js b/test/specs/custom/custom.spec.js
new file mode 100644
index 00000000..8e217b1c
--- /dev/null
+++ b/test/specs/custom/custom.spec.js
@@ -0,0 +1,59 @@
+"use strict";
+
+const { expect } = require("chai");
+const $RefParser = require("../../../lib");
+const helper = require("../../utils/helper");
+const path = require("../../utils/path");
+const parsedSchema = require("./parsed");
+const dereferencedSchema = require("./dereferenced");
+const bundledSchema = require("./bundled");
+
+const options = {
+ dereference: {
+ circular: true,
+
+ isRefResolved (value) {
+
+ // don't resolve where $ref contains the word 'gender'
+ return value.$ref.match(/\/gender/g) === null;
+ }
+ }
+};
+
+describe("Schema with internal $refs (custom dereference)", () => {
+ it("should parse successfully", async () => {
+ let parser = new $RefParser();
+ const schema = await parser.parse(path.rel("specs/custom/custom.yaml"), options);
+ expect(schema).to.equal(parser.schema);
+ expect(schema).to.deep.equal(parsedSchema);
+ expect(parser.$refs.paths()).to.deep.equal([path.abs("specs/custom/custom.yaml")]);
+ });
+
+ it("should resolve successfully", helper.testResolve(
+ path.rel("specs/custom/custom.yaml"),
+ path.abs("specs/custom/custom.yaml"), parsedSchema
+ ));
+
+ it("should dereference successfully", async () => {
+ let parser = new $RefParser();
+ const schema = await parser.dereference(path.rel("specs/custom/custom.yaml"), options);
+ expect(schema).to.equal(parser.schema);
+ expect(schema).to.deep.equal(dereferencedSchema);
+ // Reference equality
+ expect(schema.properties.name).to.equal(schema.definitions.name);
+ expect(schema.definitions.requiredString)
+ .to.equal(schema.definitions.name.properties.first)
+ .to.equal(schema.definitions.name.properties.last)
+ .to.equal(schema.properties.name.properties.first)
+ .to.equal(schema.properties.name.properties.last);
+ // 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/custom/custom.yaml"), options);
+ expect(schema).to.equal(parser.schema);
+ expect(schema).to.deep.equal(bundledSchema);
+ });
+});
diff --git a/test/specs/custom/custom.yaml b/test/specs/custom/custom.yaml
new file mode 100644
index 00000000..72c6dcbd
--- /dev/null
+++ b/test/specs/custom/custom.yaml
@@ -0,0 +1,44 @@
+title: Person
+type: object
+definitions:
+ name:
+ type: object
+ required:
+ - first
+ - last
+ properties:
+ first:
+ $ref: "#/definitions/requiredString"
+ last:
+ $ref: "#/definitions/name/properties/first"
+ middle:
+ type:
+ $ref: "#/definitions/name/properties/first/type"
+ minLength:
+ $ref: "#/definitions/name/properties/last/minLength"
+ prefix:
+ $ref: "#/definitions/name/properties/last"
+ minLength: 3
+ suffix:
+ type: string
+ $ref: "#/definitions/name/properties/prefix"
+ maxLength: 3
+ requiredString:
+ title: requiredString
+ type: string
+ minLength: 1
+ genderEnum:
+ type: string
+ enum:
+ - male
+ - female
+required:
+ - name
+properties:
+ name:
+ $ref: "#/definitions/name"
+ age:
+ type: integer
+ minimum: 0
+ gender:
+ $ref: "#/definitions/genderEnum"
diff --git a/test/specs/custom/dereferenced.js b/test/specs/custom/dereferenced.js
new file mode 100644
index 00000000..484f1aa2
--- /dev/null
+++ b/test/specs/custom/dereferenced.js
@@ -0,0 +1,101 @@
+"use strict";
+
+module.exports =
+{
+ definitions: {
+ requiredString: {
+ title: "requiredString",
+ minLength: 1,
+ type: "string"
+ },
+ name: {
+ required: [
+ "first",
+ "last"
+ ],
+ type: "object",
+ properties: {
+ first: {
+ title: "requiredString",
+ type: "string",
+ minLength: 1
+ },
+ last: {
+ title: "requiredString",
+ type: "string",
+ minLength: 1
+ },
+ middle: {
+ type: "string",
+ minLength: 1
+ },
+ prefix: {
+ title: "requiredString",
+ type: "string",
+ minLength: 3
+ },
+ suffix: {
+ title: "requiredString",
+ type: "string",
+ minLength: 3,
+ maxLength: 3
+ }
+ }
+ },
+ genderEnum: {
+ enum: [
+ "male",
+ "female"
+ ],
+ type: "string",
+ }
+ },
+ required: [
+ "name"
+ ],
+ type: "object",
+ properties: {
+ gender: {
+ $ref: "#/definitions/genderEnum"
+ },
+ age: {
+ minimum: 0,
+ type: "integer"
+ },
+ name: {
+ required: [
+ "first",
+ "last"
+ ],
+ type: "object",
+ properties: {
+ first: {
+ title: "requiredString",
+ type: "string",
+ minLength: 1
+ },
+ last: {
+ title: "requiredString",
+ type: "string",
+ minLength: 1
+ },
+ middle: {
+ type: "string",
+ minLength: 1
+ },
+ prefix: {
+ title: "requiredString",
+ type: "string",
+ minLength: 3
+ },
+ suffix: {
+ title: "requiredString",
+ type: "string",
+ minLength: 3,
+ maxLength: 3
+ }
+ }
+ }
+ },
+ title: "Person"
+};
diff --git a/test/specs/custom/parsed.js b/test/specs/custom/parsed.js
new file mode 100644
index 00000000..591020d0
--- /dev/null
+++ b/test/specs/custom/parsed.js
@@ -0,0 +1,68 @@
+"use strict";
+
+module.exports =
+{
+ definitions: {
+ requiredString: {
+ title: "requiredString",
+ minLength: 1,
+ type: "string"
+ },
+ name: {
+ required: [
+ "first",
+ "last"
+ ],
+ type: "object",
+ properties: {
+ first: {
+ $ref: "#/definitions/requiredString"
+ },
+ last: {
+ $ref: "#/definitions/name/properties/first"
+ },
+ middle: {
+ type: {
+ $ref: "#/definitions/name/properties/first/type"
+ },
+ minLength: {
+ $ref: "#/definitions/name/properties/last/minLength"
+ }
+ },
+ prefix: {
+ $ref: "#/definitions/name/properties/last",
+ minLength: 3
+ },
+ suffix: {
+ type: "string",
+ $ref: "#/definitions/name/properties/prefix",
+ maxLength: 3
+ }
+ }
+ },
+ genderEnum: {
+ enum: [
+ "male",
+ "female"
+ ],
+ type: "string",
+ }
+ },
+ required: [
+ "name"
+ ],
+ type: "object",
+ properties: {
+ gender: {
+ $ref: "#/definitions/genderEnum"
+ },
+ age: {
+ minimum: 0,
+ type: "integer"
+ },
+ name: {
+ $ref: "#/definitions/name"
+ }
+ },
+ title: "Person"
+};