diff --git a/docs/options.md b/docs/options.md
index f2e9492b..4dfc3107 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -31,6 +31,9 @@ $RefParser.dereference("my-schema.yaml", {
excludedPathMatcher: (
path, // Skip dereferencing content under any 'example' key
) => path.includes("/example/"),
+ onCircular: (
+ path, // Callback invoked during circular $ref detection
+ ) => console.log(path),
onDereference: (
path,
value, // Callback invoked during dereferencing
@@ -78,4 +81,5 @@ The `dereference` options control how JSON Schema $Ref Parser will dereference `
| :-------------------- | :--------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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`. |
| `excludedPathMatcher` | `(string) => boolean` | A function, called for each path, which can return true to stop this path and all subpaths from being dereferenced further. This is useful in schemas where some subpaths contain literal `$ref` keys that should not be dereferenced. |
+| `onCircular` | `(string) => void` | A function, called immediately after detecting a circular `$ref` with the circular `$ref` in question. |
| `onDereference` | `(string, JSONSchemaObjectType, JSONSchemaObjectType, string) => void` | A function, called immediately after dereferencing, with: the resolved JSON Schema value, the `$ref` being dereferenced, the object holding the dereferenced prop, the dereferenced prop name. |
diff --git a/lib/dereference.ts b/lib/dereference.ts
index b56b58bb..886b2cbe 100644
--- a/lib/dereference.ts
+++ b/lib/dereference.ts
@@ -272,7 +272,8 @@ function dereference$Ref {
expect(parser.$refs.circular).to.equal(true);
});
- it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
+ it('should not dereference circular $refs if "options.dereference.circular" is "ignore"', async () => {
const parser = new $RefParser();
const schema = await parser.dereference(path.rel("test/specs/circular-extended/circular-extended-self.yaml"), {
@@ -55,7 +55,7 @@ describe("Schema with circular $refs that extend each other", () => {
expect(parser.$refs.circular).to.equal(true);
});
- it('should throw an error if "options.$refs.circular" is false', async () => {
+ it('should throw an error if "options.dereference.circular" is false', async () => {
const parser = new $RefParser();
try {
@@ -130,7 +130,7 @@ describe("Schema with circular $refs that extend each other", () => {
expect(schema.definitions.person.properties.pet.properties).to.equal(schema.definitions.pet.properties);
});
- it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
+ it('should not dereference circular $refs if "options.dereference.circular" is "ignore"', async () => {
const parser = new $RefParser();
const schema = await parser.dereference(
@@ -145,7 +145,7 @@ describe("Schema with circular $refs that extend each other", () => {
expect(parser.$refs.circular).to.equal(true);
});
- it('should throw an error if "options.$refs.circular" is false', async () => {
+ it('should throw an error if "options.dereference.circular" is false', async () => {
const parser = new $RefParser();
try {
@@ -232,7 +232,7 @@ describe("Schema with circular $refs that extend each other", () => {
expect(schema.definitions.child.properties.pet.properties).to.equal(schema.definitions.pet.properties);
});
- it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
+ it('should not dereference circular $refs if "options.dereference.circular" is "ignore"', async () => {
const parser = new $RefParser();
const schema = await parser.dereference(
@@ -247,7 +247,7 @@ describe("Schema with circular $refs that extend each other", () => {
expect(parser.$refs.circular).to.equal(true);
});
- it('should throw an error if "options.$refs.circular" is false', async () => {
+ it('should throw an error if "options.dereference.circular" is false', async () => {
const parser = new $RefParser();
try {
@@ -335,7 +335,7 @@ describe("Schema with circular $refs that extend each other", () => {
expect(schema.definitions.pet.properties).to.equal(schema.definitions.child.properties.pet.properties);
});
- it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
+ it('should not dereference circular $refs if "options.dereference.circular" is "ignore"', async () => {
const parser = new $RefParser();
const schema = await parser.dereference(
@@ -348,7 +348,7 @@ describe("Schema with circular $refs that extend each other", () => {
expect(parser.$refs.circular).to.equal(true);
});
- it('should throw an error if "options.$refs.circular" is false', async () => {
+ it('should throw an error if "options.dereference.circular" is false', async () => {
const parser = new $RefParser();
try {
diff --git a/test/specs/circular-external/circular-external.spec.ts b/test/specs/circular-external/circular-external.spec.ts
index 0f1b5379..4cdf62f6 100644
--- a/test/specs/circular-external/circular-external.spec.ts
+++ b/test/specs/circular-external/circular-external.spec.ts
@@ -53,7 +53,7 @@ describe("Schema with circular (recursive) external $refs", () => {
expect(schema.definitions.child.properties.parents.items).to.equal(schema.definitions.parent);
});
- it('should throw an error if "options.$refs.circular" is false', async () => {
+ it('should throw an error if "options.dereference.circular" is false', async () => {
const parser = new $RefParser();
try {
diff --git a/test/specs/circular/circular.spec.ts b/test/specs/circular/circular.spec.ts
index 7c416287..da2e2526 100644
--- a/test/specs/circular/circular.spec.ts
+++ b/test/specs/circular/circular.spec.ts
@@ -54,7 +54,7 @@ describe("Schema with circular (recursive) $refs", () => {
expect(schema.definitions.child.properties.pet).to.equal(schema.definitions.pet);
});
- it('should produce the same results if "options.$refs.circular" is "ignore"', async () => {
+ it('should produce the same results if "options.dereference.circular" is "ignore"', async () => {
const parser = new $RefParser();
const schema = await parser.dereference(path.rel("test/specs/circular/circular-self.yaml"), {
@@ -66,7 +66,7 @@ describe("Schema with circular (recursive) $refs", () => {
expect(parser.$refs.circular).to.equal(true);
});
- it('should throw an error if "options.$refs.circular" is false', async () => {
+ it('should throw an error if "options.dereference.circular" is false', async () => {
const parser = new $RefParser();
try {
@@ -87,6 +87,24 @@ describe("Schema with circular (recursive) $refs", () => {
}
});
+ it("should call onCircular if `options.dereference.onCircular` is present", async () => {
+ const parser = new $RefParser();
+
+ const circularRefs: string[] = [];
+ const schema = await parser.dereference(path.rel("test/specs/circular/circular-self.yaml"), {
+ dereference: {
+ onCircular(path: string) {
+ circularRefs.push(path);
+ },
+ },
+ });
+ expect(schema).to.equal(parser.schema);
+ expect(schema).to.deep.equal(dereferencedSchema.self);
+ // The "circular" flag should be set
+ expect(parser.$refs.circular).to.equal(true);
+ expect(circularRefs).to.have.length(1);
+ });
+
it("should bundle successfully", async () => {
const parser = new $RefParser();
const schema = await parser.bundle(path.rel("test/specs/circular/circular-self.yaml"));
@@ -149,7 +167,7 @@ describe("Schema with circular (recursive) $refs", () => {
expect(schema.definitions.person.properties.pet).to.equal(schema.definitions.pet);
});
- it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
+ it('should not dereference circular $refs if "options.dereference.circular" is "ignore"', async () => {
const parser = new $RefParser();
const schema = await parser.dereference(path.rel("test/specs/circular/circular-ancestor.yaml"), {
@@ -164,7 +182,7 @@ describe("Schema with circular (recursive) $refs", () => {
expect(schema.definitions.person.properties.pet).to.equal(schema.definitions.pet);
});
- it('should throw an error if "options.$refs.circular" is false', async () => {
+ it('should throw an error if "options.dereference.circular" is false', async () => {
const parser = new $RefParser();
try {
@@ -247,7 +265,7 @@ describe("Schema with circular (recursive) $refs", () => {
expect(schema.definitions.child.properties.parents.items).to.equal(schema.definitions.parent);
});
- it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
+ it('should not dereference circular $refs if "options.dereference.circular" is "ignore"', async () => {
const parser = new $RefParser();
const schema = await parser.dereference(path.rel("test/specs/circular/circular-indirect.yaml"), {
@@ -262,7 +280,7 @@ describe("Schema with circular (recursive) $refs", () => {
expect(schema.definitions.child.properties.pet).to.equal(schema.definitions.pet);
});
- it('should throw an error if "options.$refs.circular" is false', async () => {
+ it('should throw an error if "options.dereference.circular" is false', async () => {
const parser = new $RefParser();
try {
@@ -347,7 +365,7 @@ describe("Schema with circular (recursive) $refs", () => {
expect(schema.definitions.child.properties.children.items).to.equal(schema.definitions.child);
});
- it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
+ it('should not dereference circular $refs if "options.dereference.circular" is "ignore"', async () => {
const parser = new $RefParser();
const schema = await parser.dereference(path.rel("test/specs/circular/circular-indirect-ancestor.yaml"), {
@@ -362,7 +380,7 @@ describe("Schema with circular (recursive) $refs", () => {
expect(schema.definitions.child.properties.pet).to.equal(schema.definitions.pet);
});
- it('should throw an error if "options.$refs.circular" is false', async () => {
+ it('should throw an error if "options.dereference.circular" is false', async () => {
const parser = new $RefParser();
try {
diff --git a/test/specs/deep-circular/deep-circular.spec.ts b/test/specs/deep-circular/deep-circular.spec.ts
index ec432869..d23e50b8 100644
--- a/test/specs/deep-circular/deep-circular.spec.ts
+++ b/test/specs/deep-circular/deep-circular.spec.ts
@@ -55,7 +55,7 @@ describe("Schema with deeply-nested circular $refs", () => {
.to.equal(schema.properties.level1.properties.level2.properties.level3.properties.level4.properties.name);
});
- it('should throw an error if "options.$refs.circular" is false', async () => {
+ it('should throw an error if "options.dereference.circular" is false', async () => {
const parser = new $RefParser();
try {