Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
11 changes: 10 additions & 1 deletion docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
});
```
Expand Down Expand Up @@ -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.<br><br>If set to `false`, then a `ReferenceError` will be thrown if the schema contains any circular references.<br><br> 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`.<br><br>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|
6 changes: 6 additions & 0 deletions lib/ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
68 changes: 68 additions & 0 deletions test/specs/custom/bundled.js
Original file line number Diff line number Diff line change
@@ -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"
};
59 changes: 59 additions & 0 deletions test/specs/custom/custom.spec.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
44 changes: 44 additions & 0 deletions test/specs/custom/custom.yaml
Original file line number Diff line number Diff line change
@@ -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"
101 changes: 101 additions & 0 deletions test/specs/custom/dereferenced.js
Original file line number Diff line number Diff line change
@@ -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"
};
68 changes: 68 additions & 0 deletions test/specs/custom/parsed.js
Original file line number Diff line number Diff line change
@@ -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"
};