Skip to content

Commit dfbf4bb

Browse files
authored
feat: add onBundle callback (#391)
1 parent 6c77265 commit dfbf4bb

File tree

4 files changed

+123
-1
lines changed

4 files changed

+123
-1
lines changed

lib/bundle.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type $Refs from "./refs.js";
55
import type $RefParser from "./index";
66
import type { ParserOptions } from "./index";
77
import type { JSONSchema } from "./index";
8+
import type { BundleOptions } from "./options";
89

910
export interface InventoryEntry {
1011
$ref: any;
@@ -65,8 +66,10 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
6566
options: O,
6667
) {
6768
const obj = key === null ? parent : parent[key as keyof typeof parent];
69+
const bundleOptions = (options.bundle || {}) as BundleOptions;
70+
const isExcludedPath = bundleOptions.excludedPathMatcher || (() => false);
6871

69-
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
72+
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) {
7073
if ($Ref.isAllowed$Ref(obj)) {
7174
inventory$Ref(parent, key, path, pathFromRoot, indirections, inventory, $refs, options);
7275
} else {
@@ -97,6 +100,10 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
97100
} else {
98101
crawl(obj, key, keyPath, keyPathFromRoot, indirections, inventory, $refs, options);
99102
}
103+
104+
if (value["$ref"]) {
105+
bundleOptions?.onBundle?.(value["$ref"], obj[key], obj as any, key);
106+
}
100107
}
101108
}
102109
}

lib/options.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@ export type DeepPartial<T> = T extends object
1212
[P in keyof T]?: DeepPartial<T[P]>;
1313
}
1414
: T;
15+
16+
export interface BundleOptions {
17+
/**
18+
* A function, called for each path, which can return true to stop this path and all
19+
* subpaths from being processed further. This is useful in schemas where some
20+
* subpaths contain literal $ref keys that should not be changed.
21+
*/
22+
excludedPathMatcher?(path: string): boolean;
23+
24+
/**
25+
* Callback invoked during bundling.
26+
*
27+
* @argument {string} path - The path being processed (ie. the `$ref` string)
28+
* @argument {JSONSchemaObject} value - The JSON-Schema that the `$ref` resolved to
29+
* @argument {JSONSchemaObject} parent - The parent of the processed object
30+
* @argument {string} parentPropName - The prop name of the parent object whose value was processed
31+
*/
32+
onBundle?(path: string, value: JSONSchemaObject, parent?: JSONSchemaObject, parentPropName?: string): void;
33+
}
34+
1535
export interface DereferenceOptions {
1636
/**
1737
* Determines whether circular `$ref` pointers are handled.
@@ -107,6 +127,11 @@ export interface $RefParserOptions<S extends object = JSONSchema> {
107127
*/
108128
continueOnError: boolean;
109129

130+
/**
131+
* The `bundle` options control how JSON Schema `$Ref` Parser will process `$ref` pointers within the JSON schema.
132+
*/
133+
bundle: BundleOptions;
134+
110135
/**
111136
* The `dereference` options control how JSON Schema `$Ref` Parser will dereference `$ref` pointers within the JSON schema.
112137
*/
@@ -168,6 +193,20 @@ export const getJsonSchemaRefParserDefaultOptions = () => {
168193
*/
169194
continueOnError: false,
170195

196+
/**
197+
* Determines the types of JSON references that are allowed.
198+
*/
199+
bundle: {
200+
/**
201+
* A function, called for each path, which can return true to stop this path and all
202+
* subpaths from being processed further. This is useful in schemas where some
203+
* subpaths contain literal $ref keys that should not be changed.
204+
*
205+
* @type {function}
206+
*/
207+
excludedPathMatcher: () => false,
208+
},
209+
171210
/**
172211
* Determines the types of JSON references that are allowed.
173212
*/
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { describe, it } from "vitest";
2+
import $RefParser from "../../../lib/index.js";
3+
import pathUtils from "../../utils/path.js";
4+
5+
import { expect } from "vitest";
6+
import type { Options } from "../../../lib/options";
7+
8+
describe("Schema with a $ref", () => {
9+
it("should call onBundle", async () => {
10+
const parser = new $RefParser();
11+
const calls: any = [];
12+
const schema = pathUtils.rel("test/specs/bundle-callback/bundle-callback.yaml");
13+
const options = {
14+
bundle: {
15+
onBundle(path, value, parent, parentPropName) {
16+
calls.push(JSON.parse(JSON.stringify({ path, value, parent, parentPropName })));
17+
},
18+
},
19+
} as Options;
20+
await parser.bundle(schema, options);
21+
22+
expect(calls).to.deep.equal([
23+
{
24+
path: "#/definitions/b",
25+
value: { $ref: "#/definitions/b" },
26+
parent: {
27+
a: {
28+
$ref: "#/definitions/b",
29+
},
30+
b: {
31+
$ref: "#/definitions/a",
32+
},
33+
},
34+
parentPropName: "a",
35+
},
36+
{
37+
path: "#/definitions/a",
38+
value: { $ref: "#/definitions/a" },
39+
parent: {
40+
a: {
41+
$ref: "#/definitions/b",
42+
},
43+
b: {
44+
$ref: "#/definitions/a",
45+
},
46+
},
47+
parentPropName: "b",
48+
},
49+
{
50+
path: "#/definitions/a",
51+
value: { $ref: "#/definitions/a" },
52+
parent: {
53+
c: {
54+
type: "string",
55+
},
56+
d: {
57+
$ref: "#/definitions/a",
58+
},
59+
},
60+
parentPropName: "d",
61+
},
62+
]);
63+
});
64+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
title: test
2+
type: object
3+
definitions:
4+
a:
5+
$ref: "#/definitions/b"
6+
b:
7+
$ref: "#/definitions/a"
8+
properties:
9+
c:
10+
type: string
11+
d:
12+
$ref: "#/definitions/a"

0 commit comments

Comments
 (0)