@hey-api/json-schema-ref-parser 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/__tests__/bundle.test.d.ts +1 -0
- package/dist/lib/__tests__/bundle.test.js +16 -0
- package/dist/lib/bundle.d.ts +8 -9
- package/dist/lib/bundle.js +125 -92
- package/dist/lib/index.js +2 -2
- package/lib/__tests__/bundle.test.ts +14 -0
- package/lib/__tests__/spec/circular-ref-with-description.json +11 -0
- package/lib/bundle.ts +206 -122
- package/lib/index.ts +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
7
|
+
const vitest_1 = require("vitest");
|
|
8
|
+
const __1 = require("..");
|
|
9
|
+
(0, vitest_1.describe)('bundle', () => {
|
|
10
|
+
(0, vitest_1.it)('handles circular reference with description', async () => {
|
|
11
|
+
const refParser = new __1.$RefParser();
|
|
12
|
+
const pathOrUrlOrSchema = node_path_1.default.resolve('lib', '__tests__', 'spec', 'circular-ref-with-description.json');
|
|
13
|
+
const schema = await refParser.bundle({ pathOrUrlOrSchema });
|
|
14
|
+
(0, vitest_1.expect)(schema).not.toBeUndefined();
|
|
15
|
+
});
|
|
16
|
+
});
|
package/dist/lib/bundle.d.ts
CHANGED
|
@@ -2,17 +2,17 @@ import type { ParserOptions } from "./options.js";
|
|
|
2
2
|
import type { $RefParser } from "./index";
|
|
3
3
|
export interface InventoryEntry {
|
|
4
4
|
$ref: any;
|
|
5
|
-
parent: any;
|
|
6
|
-
key: any;
|
|
7
|
-
pathFromRoot: any;
|
|
8
|
-
depth: any;
|
|
9
|
-
file: any;
|
|
10
|
-
hash: any;
|
|
11
|
-
value: any;
|
|
12
5
|
circular: any;
|
|
6
|
+
depth: any;
|
|
13
7
|
extended: any;
|
|
14
8
|
external: any;
|
|
9
|
+
file: any;
|
|
10
|
+
hash: any;
|
|
15
11
|
indirections: any;
|
|
12
|
+
key: any;
|
|
13
|
+
parent: any;
|
|
14
|
+
pathFromRoot: any;
|
|
15
|
+
value: any;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
|
|
@@ -22,5 +22,4 @@ export interface InventoryEntry {
|
|
|
22
22
|
* @param parser
|
|
23
23
|
* @param options
|
|
24
24
|
*/
|
|
25
|
-
declare
|
|
26
|
-
export default bundle;
|
|
25
|
+
export declare const bundle: (parser: $RefParser, options: ParserOptions) => void;
|
package/dist/lib/bundle.js
CHANGED
|
@@ -36,90 +36,31 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.bundle = void 0;
|
|
40
|
+
const isEqual_1 = __importDefault(require("lodash/isEqual"));
|
|
39
41
|
const ref_js_1 = __importDefault(require("./ref.js"));
|
|
40
42
|
const pointer_js_1 = __importDefault(require("./pointer.js"));
|
|
41
43
|
const url = __importStar(require("./util/url.js"));
|
|
42
44
|
/**
|
|
43
|
-
*
|
|
44
|
-
* only has *internal* references, not any *external* references.
|
|
45
|
-
* This method mutates the JSON schema object, adding new references and re-mapping existing ones.
|
|
46
|
-
*
|
|
47
|
-
* @param parser
|
|
48
|
-
* @param options
|
|
49
|
-
*/
|
|
50
|
-
function bundle(parser, options) {
|
|
51
|
-
// console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
52
|
-
// Build an inventory of all $ref pointers in the JSON Schema
|
|
53
|
-
const inventory = [];
|
|
54
|
-
crawl(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options);
|
|
55
|
-
// Remap all $ref pointers
|
|
56
|
-
remap(inventory);
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Recursively crawls the given value, and inventories all JSON references.
|
|
60
|
-
*
|
|
61
|
-
* @param parent - The object containing the value to crawl. If the value is not an object or array, it will be ignored.
|
|
62
|
-
* @param key - The property key of `parent` to be crawled
|
|
63
|
-
* @param path - The full path of the property being crawled, possibly with a JSON Pointer in the hash
|
|
64
|
-
* @param pathFromRoot - The path of the property being crawled, from the schema root
|
|
65
|
-
* @param indirections
|
|
66
|
-
* @param inventory - An array of already-inventoried $ref pointers
|
|
67
|
-
* @param $refs
|
|
68
|
-
* @param options
|
|
45
|
+
* TODO
|
|
69
46
|
*/
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
else {
|
|
77
|
-
// Crawl the object in a specific order that's optimized for bundling.
|
|
78
|
-
// This is important because it determines how `pathFromRoot` gets built,
|
|
79
|
-
// which later determines which keys get dereferenced and which ones get remapped
|
|
80
|
-
const keys = Object.keys(obj).sort((a, b) => {
|
|
81
|
-
// Most people will expect references to be bundled into the the "definitions" property,
|
|
82
|
-
// so we always crawl that property first, if it exists.
|
|
83
|
-
if (a === "definitions") {
|
|
84
|
-
return -1;
|
|
85
|
-
}
|
|
86
|
-
else if (b === "definitions") {
|
|
87
|
-
return 1;
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
// Otherwise, crawl the keys based on their length.
|
|
91
|
-
// This produces the shortest possible bundled references
|
|
92
|
-
return a.length - b.length;
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
for (const key of keys) {
|
|
96
|
-
const keyPath = pointer_js_1.default.join(path, key);
|
|
97
|
-
const keyPathFromRoot = pointer_js_1.default.join(pathFromRoot, key);
|
|
98
|
-
const value = obj[key];
|
|
99
|
-
if (ref_js_1.default.isAllowed$Ref(value)) {
|
|
100
|
-
inventory$Ref(obj, key, path, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
crawl(obj, key, keyPath, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
47
|
+
const findInInventory = (inventory, $refParent, $refKey) => {
|
|
48
|
+
for (const entry of inventory) {
|
|
49
|
+
if (entry) {
|
|
50
|
+
if ((0, isEqual_1.default)(entry.parent, $refParent)) {
|
|
51
|
+
if (entry.key === $refKey) {
|
|
52
|
+
return entry;
|
|
104
53
|
}
|
|
105
54
|
}
|
|
106
55
|
}
|
|
107
56
|
}
|
|
108
|
-
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
109
59
|
/**
|
|
110
60
|
* Inventories the given JSON Reference (i.e. records detailed information about it so we can
|
|
111
61
|
* optimize all $refs in the schema), and then crawls the resolved value.
|
|
112
|
-
*
|
|
113
|
-
* @param $refParent - The object that contains a JSON Reference as one of its keys
|
|
114
|
-
* @param $refKey - The key in `$refParent` that is a JSON Reference
|
|
115
|
-
* @param path - The full path of the JSON Reference at `$refKey`, possibly with a JSON Pointer in the hash
|
|
116
|
-
* @param indirections - unknown
|
|
117
|
-
* @param pathFromRoot - The path of the JSON Reference at `$refKey`, from the schema root
|
|
118
|
-
* @param inventory - An array of already-inventoried $ref pointers
|
|
119
|
-
* @param $refs
|
|
120
|
-
* @param options
|
|
121
62
|
*/
|
|
122
|
-
|
|
63
|
+
const inventory$Ref = ({ $refKey, $refParent, $refs, indirections, inventory, options, path, pathFromRoot, }) => {
|
|
123
64
|
const $ref = $refKey === null ? $refParent : $refParent[$refKey];
|
|
124
65
|
const $refPath = url.resolve(path, $ref.$ref);
|
|
125
66
|
const pointer = $refs._resolve($refPath, pathFromRoot, options);
|
|
@@ -145,23 +86,101 @@ function inventory$Ref($refParent, $refKey, path, pathFromRoot, indirections, in
|
|
|
145
86
|
}
|
|
146
87
|
inventory.push({
|
|
147
88
|
$ref, // The JSON Reference (e.g. {$ref: string})
|
|
148
|
-
parent: $refParent, // The object that contains this $ref pointer
|
|
149
|
-
key: $refKey, // The key in `parent` that is the $ref pointer
|
|
150
|
-
pathFromRoot, // The path to the $ref pointer, from the JSON Schema root
|
|
151
|
-
depth, // How far from the JSON Schema root is this $ref pointer?
|
|
152
|
-
file, // The file that the $ref pointer resolves to
|
|
153
|
-
hash, // The hash within `file` that the $ref pointer resolves to
|
|
154
|
-
value: pointer.value, // The resolved value of the $ref pointer
|
|
155
89
|
circular: pointer.circular, // Is this $ref pointer DIRECTLY circular? (i.e. it references itself)
|
|
90
|
+
depth, // How far from the JSON Schema root is this $ref pointer?
|
|
156
91
|
extended, // Does this $ref extend its resolved value? (i.e. it has extra properties, in addition to "$ref")
|
|
157
92
|
external, // Does this $ref pointer point to a file other than the main JSON Schema file?
|
|
93
|
+
file, // The file that the $ref pointer resolves to
|
|
94
|
+
hash, // The hash within `file` that the $ref pointer resolves to
|
|
158
95
|
indirections, // The number of indirect references that were traversed to resolve the value
|
|
96
|
+
key: $refKey, // The key in `parent` that is the $ref pointer
|
|
97
|
+
parent: $refParent, // The object that contains this $ref pointer
|
|
98
|
+
pathFromRoot, // The path to the $ref pointer, from the JSON Schema root
|
|
99
|
+
value: pointer.value, // The resolved value of the $ref pointer
|
|
159
100
|
});
|
|
160
101
|
// Recursively crawl the resolved value
|
|
161
102
|
if (!existingEntry || external) {
|
|
162
|
-
crawl(
|
|
103
|
+
crawl({
|
|
104
|
+
parent: pointer.value,
|
|
105
|
+
key: null,
|
|
106
|
+
path: pointer.path,
|
|
107
|
+
pathFromRoot,
|
|
108
|
+
indirections: indirections + 1,
|
|
109
|
+
inventory,
|
|
110
|
+
$refs,
|
|
111
|
+
options,
|
|
112
|
+
});
|
|
163
113
|
}
|
|
164
|
-
}
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Recursively crawls the given value, and inventories all JSON references.
|
|
117
|
+
*/
|
|
118
|
+
const crawl = ({ $refs, indirections, inventory, key, options, parent, path, pathFromRoot, }) => {
|
|
119
|
+
const obj = key === null ? parent : parent[key];
|
|
120
|
+
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
|
|
121
|
+
if (ref_js_1.default.isAllowed$Ref(obj)) {
|
|
122
|
+
inventory$Ref({
|
|
123
|
+
$refParent: parent,
|
|
124
|
+
$refKey: key,
|
|
125
|
+
path,
|
|
126
|
+
pathFromRoot,
|
|
127
|
+
indirections,
|
|
128
|
+
inventory,
|
|
129
|
+
$refs,
|
|
130
|
+
options,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Crawl the object in a specific order that's optimized for bundling.
|
|
135
|
+
// This is important because it determines how `pathFromRoot` gets built,
|
|
136
|
+
// which later determines which keys get dereferenced and which ones get remapped
|
|
137
|
+
const keys = Object.keys(obj).sort((a, b) => {
|
|
138
|
+
// Most people will expect references to be bundled into the "definitions" property,
|
|
139
|
+
// so we always crawl that property first, if it exists.
|
|
140
|
+
if (a === "definitions") {
|
|
141
|
+
return -1;
|
|
142
|
+
}
|
|
143
|
+
else if (b === "definitions") {
|
|
144
|
+
return 1;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// Otherwise, crawl the keys based on their length.
|
|
148
|
+
// This produces the shortest possible bundled references
|
|
149
|
+
return a.length - b.length;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
for (const key of keys) {
|
|
153
|
+
const keyPath = pointer_js_1.default.join(path, key);
|
|
154
|
+
const keyPathFromRoot = pointer_js_1.default.join(pathFromRoot, key);
|
|
155
|
+
const value = obj[key];
|
|
156
|
+
if (ref_js_1.default.isAllowed$Ref(value)) {
|
|
157
|
+
inventory$Ref({
|
|
158
|
+
$refParent: obj,
|
|
159
|
+
$refKey: key,
|
|
160
|
+
path,
|
|
161
|
+
pathFromRoot: keyPathFromRoot,
|
|
162
|
+
indirections,
|
|
163
|
+
inventory,
|
|
164
|
+
$refs,
|
|
165
|
+
options,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
crawl({
|
|
170
|
+
parent: obj,
|
|
171
|
+
key,
|
|
172
|
+
path: keyPath,
|
|
173
|
+
pathFromRoot: keyPathFromRoot,
|
|
174
|
+
indirections,
|
|
175
|
+
inventory,
|
|
176
|
+
$refs,
|
|
177
|
+
options,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
165
184
|
/**
|
|
166
185
|
* Re-maps every $ref pointer, so that they're all relative to the root of the JSON Schema.
|
|
167
186
|
* Each referenced value is dereferenced EXACTLY ONCE. All subsequent references to the same
|
|
@@ -275,19 +294,33 @@ function remap(inventory) {
|
|
|
275
294
|
// }
|
|
276
295
|
// }
|
|
277
296
|
}
|
|
278
|
-
/**
|
|
279
|
-
* TODO
|
|
280
|
-
*/
|
|
281
|
-
function findInInventory(inventory, $refParent, $refKey) {
|
|
282
|
-
for (const existingEntry of inventory) {
|
|
283
|
-
if (existingEntry && existingEntry.parent === $refParent && existingEntry.key === $refKey) {
|
|
284
|
-
return existingEntry;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return undefined;
|
|
288
|
-
}
|
|
289
297
|
function removeFromInventory(inventory, entry) {
|
|
290
298
|
const index = inventory.indexOf(entry);
|
|
291
299
|
inventory.splice(index, 1);
|
|
292
300
|
}
|
|
293
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
|
|
303
|
+
* only has *internal* references, not any *external* references.
|
|
304
|
+
* This method mutates the JSON schema object, adding new references and re-mapping existing ones.
|
|
305
|
+
*
|
|
306
|
+
* @param parser
|
|
307
|
+
* @param options
|
|
308
|
+
*/
|
|
309
|
+
const bundle = (parser, options) => {
|
|
310
|
+
// console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
311
|
+
// Build an inventory of all $ref pointers in the JSON Schema
|
|
312
|
+
const inventory = [];
|
|
313
|
+
crawl({
|
|
314
|
+
parent: parser,
|
|
315
|
+
key: 'schema',
|
|
316
|
+
path: parser.$refs._root$Ref.path + "#",
|
|
317
|
+
pathFromRoot: "#",
|
|
318
|
+
indirections: 0,
|
|
319
|
+
inventory,
|
|
320
|
+
$refs: parser.$refs,
|
|
321
|
+
options,
|
|
322
|
+
});
|
|
323
|
+
// Remap all $ref pointers
|
|
324
|
+
remap(inventory);
|
|
325
|
+
};
|
|
326
|
+
exports.bundle = bundle;
|
package/dist/lib/index.js
CHANGED
|
@@ -40,7 +40,7 @@ exports.sendRequest = exports.$RefParser = exports.getResolvedInput = void 0;
|
|
|
40
40
|
const refs_js_1 = __importDefault(require("./refs.js"));
|
|
41
41
|
const parse_js_1 = require("./parse.js");
|
|
42
42
|
const resolve_external_js_1 = require("./resolve-external.js");
|
|
43
|
-
const bundle_js_1 =
|
|
43
|
+
const bundle_js_1 = require("./bundle.js");
|
|
44
44
|
const dereference_js_1 = __importDefault(require("./dereference.js"));
|
|
45
45
|
const url = __importStar(require("./util/url.js"));
|
|
46
46
|
const errors_js_1 = require("./util/errors.js");
|
|
@@ -130,7 +130,7 @@ class $RefParser {
|
|
|
130
130
|
if (errors.length > 0) {
|
|
131
131
|
throw new errors_js_1.JSONParserErrorGroup(this);
|
|
132
132
|
}
|
|
133
|
-
(0, bundle_js_1.
|
|
133
|
+
(0, bundle_js_1.bundle)(this, this.options);
|
|
134
134
|
const errors2 = errors_js_1.JSONParserErrorGroup.getParserErrors(this);
|
|
135
135
|
if (errors2.length > 0) {
|
|
136
136
|
throw new errors_js_1.JSONParserErrorGroup(this);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { $RefParser } from '..';
|
|
6
|
+
|
|
7
|
+
describe('bundle', () => {
|
|
8
|
+
it('handles circular reference with description', async () => {
|
|
9
|
+
const refParser = new $RefParser();
|
|
10
|
+
const pathOrUrlOrSchema = path.resolve('lib', '__tests__', 'spec', 'circular-ref-with-description.json');
|
|
11
|
+
const schema = await refParser.bundle({ pathOrUrlOrSchema });
|
|
12
|
+
expect(schema).not.toBeUndefined();
|
|
13
|
+
});
|
|
14
|
+
});
|
package/lib/bundle.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import isEqual from "lodash/isEqual";
|
|
2
|
+
|
|
1
3
|
import $Ref from "./ref.js";
|
|
2
4
|
import type { ParserOptions } from "./options.js";
|
|
3
5
|
import Pointer from "./pointer.js";
|
|
@@ -8,123 +10,76 @@ import type { JSONSchema } from "./types/index.js";
|
|
|
8
10
|
|
|
9
11
|
export interface InventoryEntry {
|
|
10
12
|
$ref: any;
|
|
11
|
-
parent: any;
|
|
12
|
-
key: any;
|
|
13
|
-
pathFromRoot: any;
|
|
14
|
-
depth: any;
|
|
15
|
-
file: any;
|
|
16
|
-
hash: any;
|
|
17
|
-
value: any;
|
|
18
13
|
circular: any;
|
|
14
|
+
depth: any;
|
|
19
15
|
extended: any;
|
|
20
16
|
external: any;
|
|
17
|
+
file: any;
|
|
18
|
+
hash: any;
|
|
21
19
|
indirections: any;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* This method mutates the JSON schema object, adding new references and re-mapping existing ones.
|
|
27
|
-
*
|
|
28
|
-
* @param parser
|
|
29
|
-
* @param options
|
|
30
|
-
*/
|
|
31
|
-
function bundle(
|
|
32
|
-
parser: $RefParser,
|
|
33
|
-
options: ParserOptions,
|
|
34
|
-
) {
|
|
35
|
-
// console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
36
|
-
|
|
37
|
-
// Build an inventory of all $ref pointers in the JSON Schema
|
|
38
|
-
const inventory: InventoryEntry[] = [];
|
|
39
|
-
crawl<JSONSchema>(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options);
|
|
40
|
-
|
|
41
|
-
// Remap all $ref pointers
|
|
42
|
-
remap(inventory);
|
|
20
|
+
key: any;
|
|
21
|
+
parent: any;
|
|
22
|
+
pathFromRoot: any;
|
|
23
|
+
value: any;
|
|
43
24
|
}
|
|
44
25
|
|
|
45
26
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* @param parent - The object containing the value to crawl. If the value is not an object or array, it will be ignored.
|
|
49
|
-
* @param key - The property key of `parent` to be crawled
|
|
50
|
-
* @param path - The full path of the property being crawled, possibly with a JSON Pointer in the hash
|
|
51
|
-
* @param pathFromRoot - The path of the property being crawled, from the schema root
|
|
52
|
-
* @param indirections
|
|
53
|
-
* @param inventory - An array of already-inventoried $ref pointers
|
|
54
|
-
* @param $refs
|
|
55
|
-
* @param options
|
|
27
|
+
* TODO
|
|
56
28
|
*/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
inventory: InventoryEntry[],
|
|
64
|
-
$refs: $Refs<S>,
|
|
65
|
-
options: ParserOptions,
|
|
66
|
-
) {
|
|
67
|
-
const obj = key === null ? parent : parent[key as keyof typeof parent];
|
|
68
|
-
|
|
69
|
-
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
|
|
70
|
-
if ($Ref.isAllowed$Ref(obj)) {
|
|
71
|
-
inventory$Ref(parent, key, path, pathFromRoot, indirections, inventory, $refs, options);
|
|
72
|
-
} else {
|
|
73
|
-
// Crawl the object in a specific order that's optimized for bundling.
|
|
74
|
-
// This is important because it determines how `pathFromRoot` gets built,
|
|
75
|
-
// which later determines which keys get dereferenced and which ones get remapped
|
|
76
|
-
const keys = Object.keys(obj).sort((a, b) => {
|
|
77
|
-
// Most people will expect references to be bundled into the the "definitions" property,
|
|
78
|
-
// so we always crawl that property first, if it exists.
|
|
79
|
-
if (a === "definitions") {
|
|
80
|
-
return -1;
|
|
81
|
-
} else if (b === "definitions") {
|
|
82
|
-
return 1;
|
|
83
|
-
} else {
|
|
84
|
-
// Otherwise, crawl the keys based on their length.
|
|
85
|
-
// This produces the shortest possible bundled references
|
|
86
|
-
return a.length - b.length;
|
|
87
|
-
}
|
|
88
|
-
}) as (keyof typeof obj)[];
|
|
89
|
-
|
|
90
|
-
for (const key of keys) {
|
|
91
|
-
const keyPath = Pointer.join(path, key);
|
|
92
|
-
const keyPathFromRoot = Pointer.join(pathFromRoot, key);
|
|
93
|
-
const value = obj[key];
|
|
94
|
-
|
|
95
|
-
if ($Ref.isAllowed$Ref(value)) {
|
|
96
|
-
inventory$Ref(obj, key, path, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
97
|
-
} else {
|
|
98
|
-
crawl(obj, key, keyPath, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
29
|
+
const findInInventory = (inventory: Array<InventoryEntry>, $refParent: any, $refKey: any) => {
|
|
30
|
+
for (const entry of inventory) {
|
|
31
|
+
if (entry) {
|
|
32
|
+
if (isEqual(entry.parent, $refParent)) {
|
|
33
|
+
if (entry.key === $refKey) {
|
|
34
|
+
return entry;
|
|
99
35
|
}
|
|
100
36
|
}
|
|
101
37
|
}
|
|
102
38
|
}
|
|
103
|
-
|
|
39
|
+
return undefined;
|
|
40
|
+
};
|
|
104
41
|
|
|
105
42
|
/**
|
|
106
43
|
* Inventories the given JSON Reference (i.e. records detailed information about it so we can
|
|
107
44
|
* optimize all $refs in the schema), and then crawls the resolved value.
|
|
108
|
-
*
|
|
109
|
-
* @param $refParent - The object that contains a JSON Reference as one of its keys
|
|
110
|
-
* @param $refKey - The key in `$refParent` that is a JSON Reference
|
|
111
|
-
* @param path - The full path of the JSON Reference at `$refKey`, possibly with a JSON Pointer in the hash
|
|
112
|
-
* @param indirections - unknown
|
|
113
|
-
* @param pathFromRoot - The path of the JSON Reference at `$refKey`, from the schema root
|
|
114
|
-
* @param inventory - An array of already-inventoried $ref pointers
|
|
115
|
-
* @param $refs
|
|
116
|
-
* @param options
|
|
117
45
|
*/
|
|
118
|
-
|
|
119
|
-
$
|
|
120
|
-
$
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
46
|
+
const inventory$Ref = <S extends object = JSONSchema>({
|
|
47
|
+
$refKey,
|
|
48
|
+
$refParent,
|
|
49
|
+
$refs,
|
|
50
|
+
indirections,
|
|
51
|
+
inventory,
|
|
52
|
+
options,
|
|
53
|
+
path,
|
|
54
|
+
pathFromRoot,
|
|
55
|
+
}: {
|
|
56
|
+
/**
|
|
57
|
+
* The key in `$refParent` that is a JSON Reference
|
|
58
|
+
*/
|
|
59
|
+
$refKey: string | null;
|
|
60
|
+
/**
|
|
61
|
+
* The object that contains a JSON Reference as one of its keys
|
|
62
|
+
*/
|
|
63
|
+
$refParent: any;
|
|
64
|
+
$refs: $Refs<S>;
|
|
65
|
+
/**
|
|
66
|
+
* unknown
|
|
67
|
+
*/
|
|
68
|
+
indirections: number;
|
|
69
|
+
/**
|
|
70
|
+
* An array of already-inventoried $ref pointers
|
|
71
|
+
*/
|
|
72
|
+
inventory: Array<InventoryEntry>;
|
|
73
|
+
options: ParserOptions;
|
|
74
|
+
/**
|
|
75
|
+
* The full path of the JSON Reference at `$refKey`, possibly with a JSON Pointer in the hash
|
|
76
|
+
*/
|
|
77
|
+
path: string;
|
|
78
|
+
/**
|
|
79
|
+
* The path of the JSON Reference at `$refKey`, from the schema root
|
|
80
|
+
*/
|
|
81
|
+
pathFromRoot: string;
|
|
82
|
+
}) => {
|
|
128
83
|
const $ref = $refKey === null ? $refParent : $refParent[$refKey];
|
|
129
84
|
const $refPath = url.resolve(path, $ref.$ref);
|
|
130
85
|
const pointer = $refs._resolve($refPath, pathFromRoot, options);
|
|
@@ -151,24 +106,135 @@ function inventory$Ref<S extends object = JSONSchema>(
|
|
|
151
106
|
|
|
152
107
|
inventory.push({
|
|
153
108
|
$ref, // The JSON Reference (e.g. {$ref: string})
|
|
154
|
-
parent: $refParent, // The object that contains this $ref pointer
|
|
155
|
-
key: $refKey, // The key in `parent` that is the $ref pointer
|
|
156
|
-
pathFromRoot, // The path to the $ref pointer, from the JSON Schema root
|
|
157
|
-
depth, // How far from the JSON Schema root is this $ref pointer?
|
|
158
|
-
file, // The file that the $ref pointer resolves to
|
|
159
|
-
hash, // The hash within `file` that the $ref pointer resolves to
|
|
160
|
-
value: pointer.value, // The resolved value of the $ref pointer
|
|
161
109
|
circular: pointer.circular, // Is this $ref pointer DIRECTLY circular? (i.e. it references itself)
|
|
110
|
+
depth, // How far from the JSON Schema root is this $ref pointer?
|
|
162
111
|
extended, // Does this $ref extend its resolved value? (i.e. it has extra properties, in addition to "$ref")
|
|
163
112
|
external, // Does this $ref pointer point to a file other than the main JSON Schema file?
|
|
113
|
+
file, // The file that the $ref pointer resolves to
|
|
114
|
+
hash, // The hash within `file` that the $ref pointer resolves to
|
|
164
115
|
indirections, // The number of indirect references that were traversed to resolve the value
|
|
116
|
+
key: $refKey, // The key in `parent` that is the $ref pointer
|
|
117
|
+
parent: $refParent, // The object that contains this $ref pointer
|
|
118
|
+
pathFromRoot, // The path to the $ref pointer, from the JSON Schema root
|
|
119
|
+
value: pointer.value, // The resolved value of the $ref pointer
|
|
165
120
|
});
|
|
166
121
|
|
|
167
122
|
// Recursively crawl the resolved value
|
|
168
123
|
if (!existingEntry || external) {
|
|
169
|
-
crawl(
|
|
124
|
+
crawl({
|
|
125
|
+
parent: pointer.value,
|
|
126
|
+
key: null,
|
|
127
|
+
path: pointer.path,
|
|
128
|
+
pathFromRoot,
|
|
129
|
+
indirections: indirections + 1,
|
|
130
|
+
inventory,
|
|
131
|
+
$refs,
|
|
132
|
+
options,
|
|
133
|
+
});
|
|
170
134
|
}
|
|
171
|
-
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Recursively crawls the given value, and inventories all JSON references.
|
|
139
|
+
*/
|
|
140
|
+
const crawl = <S extends object = JSONSchema>({
|
|
141
|
+
$refs,
|
|
142
|
+
indirections,
|
|
143
|
+
inventory,
|
|
144
|
+
key,
|
|
145
|
+
options,
|
|
146
|
+
parent,
|
|
147
|
+
path,
|
|
148
|
+
pathFromRoot,
|
|
149
|
+
}: {
|
|
150
|
+
$refs: $Refs<S>;
|
|
151
|
+
indirections: number;
|
|
152
|
+
/**
|
|
153
|
+
* An array of already-inventoried $ref pointers
|
|
154
|
+
*/
|
|
155
|
+
inventory: Array<InventoryEntry>;
|
|
156
|
+
/**
|
|
157
|
+
* The property key of `parent` to be crawled
|
|
158
|
+
*/
|
|
159
|
+
key: string | null;
|
|
160
|
+
options: ParserOptions;
|
|
161
|
+
/**
|
|
162
|
+
* The object containing the value to crawl. If the value is not an object or array, it will be ignored.
|
|
163
|
+
*/
|
|
164
|
+
parent: object | $RefParser;
|
|
165
|
+
/**
|
|
166
|
+
* The full path of the property being crawled, possibly with a JSON Pointer in the hash
|
|
167
|
+
*/
|
|
168
|
+
path: string;
|
|
169
|
+
/**
|
|
170
|
+
* The path of the property being crawled, from the schema root
|
|
171
|
+
*/
|
|
172
|
+
pathFromRoot: string;
|
|
173
|
+
}) => {
|
|
174
|
+
const obj = key === null ? parent : parent[key as keyof typeof parent];
|
|
175
|
+
|
|
176
|
+
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
|
|
177
|
+
if ($Ref.isAllowed$Ref(obj)) {
|
|
178
|
+
inventory$Ref({
|
|
179
|
+
$refParent: parent,
|
|
180
|
+
$refKey: key,
|
|
181
|
+
path,
|
|
182
|
+
pathFromRoot,
|
|
183
|
+
indirections,
|
|
184
|
+
inventory,
|
|
185
|
+
$refs,
|
|
186
|
+
options,
|
|
187
|
+
});
|
|
188
|
+
} else {
|
|
189
|
+
// Crawl the object in a specific order that's optimized for bundling.
|
|
190
|
+
// This is important because it determines how `pathFromRoot` gets built,
|
|
191
|
+
// which later determines which keys get dereferenced and which ones get remapped
|
|
192
|
+
const keys = Object.keys(obj).sort((a, b) => {
|
|
193
|
+
// Most people will expect references to be bundled into the "definitions" property,
|
|
194
|
+
// so we always crawl that property first, if it exists.
|
|
195
|
+
if (a === "definitions") {
|
|
196
|
+
return -1;
|
|
197
|
+
} else if (b === "definitions") {
|
|
198
|
+
return 1;
|
|
199
|
+
} else {
|
|
200
|
+
// Otherwise, crawl the keys based on their length.
|
|
201
|
+
// This produces the shortest possible bundled references
|
|
202
|
+
return a.length - b.length;
|
|
203
|
+
}
|
|
204
|
+
}) as (keyof typeof obj)[];
|
|
205
|
+
|
|
206
|
+
for (const key of keys) {
|
|
207
|
+
const keyPath = Pointer.join(path, key);
|
|
208
|
+
const keyPathFromRoot = Pointer.join(pathFromRoot, key);
|
|
209
|
+
const value = obj[key];
|
|
210
|
+
|
|
211
|
+
if ($Ref.isAllowed$Ref(value)) {
|
|
212
|
+
inventory$Ref({
|
|
213
|
+
$refParent: obj,
|
|
214
|
+
$refKey: key,
|
|
215
|
+
path,
|
|
216
|
+
pathFromRoot: keyPathFromRoot,
|
|
217
|
+
indirections,
|
|
218
|
+
inventory,
|
|
219
|
+
$refs,
|
|
220
|
+
options,
|
|
221
|
+
});
|
|
222
|
+
} else {
|
|
223
|
+
crawl({
|
|
224
|
+
parent: obj,
|
|
225
|
+
key,
|
|
226
|
+
path: keyPath,
|
|
227
|
+
pathFromRoot: keyPathFromRoot,
|
|
228
|
+
indirections,
|
|
229
|
+
inventory,
|
|
230
|
+
$refs,
|
|
231
|
+
options,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
172
238
|
|
|
173
239
|
/**
|
|
174
240
|
* Re-maps every $ref pointer, so that they're all relative to the root of the JSON Schema.
|
|
@@ -280,20 +346,38 @@ function remap(inventory: InventoryEntry[]) {
|
|
|
280
346
|
// }
|
|
281
347
|
}
|
|
282
348
|
|
|
283
|
-
/**
|
|
284
|
-
* TODO
|
|
285
|
-
*/
|
|
286
|
-
function findInInventory(inventory: InventoryEntry[], $refParent: any, $refKey: any) {
|
|
287
|
-
for (const existingEntry of inventory) {
|
|
288
|
-
if (existingEntry && existingEntry.parent === $refParent && existingEntry.key === $refKey) {
|
|
289
|
-
return existingEntry;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
return undefined;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
349
|
function removeFromInventory(inventory: InventoryEntry[], entry: any) {
|
|
296
350
|
const index = inventory.indexOf(entry);
|
|
297
351
|
inventory.splice(index, 1);
|
|
298
352
|
}
|
|
299
|
-
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
|
|
356
|
+
* only has *internal* references, not any *external* references.
|
|
357
|
+
* This method mutates the JSON schema object, adding new references and re-mapping existing ones.
|
|
358
|
+
*
|
|
359
|
+
* @param parser
|
|
360
|
+
* @param options
|
|
361
|
+
*/
|
|
362
|
+
export const bundle = (
|
|
363
|
+
parser: $RefParser,
|
|
364
|
+
options: ParserOptions,
|
|
365
|
+
) => {
|
|
366
|
+
// console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
367
|
+
|
|
368
|
+
// Build an inventory of all $ref pointers in the JSON Schema
|
|
369
|
+
const inventory: InventoryEntry[] = [];
|
|
370
|
+
crawl<JSONSchema>({
|
|
371
|
+
parent: parser,
|
|
372
|
+
key: 'schema',
|
|
373
|
+
path: parser.$refs._root$Ref.path + "#",
|
|
374
|
+
pathFromRoot: "#",
|
|
375
|
+
indirections: 0,
|
|
376
|
+
inventory,
|
|
377
|
+
$refs: parser.$refs,
|
|
378
|
+
options,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Remap all $ref pointers
|
|
382
|
+
remap(inventory);
|
|
383
|
+
};
|
package/lib/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import $Refs from "./refs.js";
|
|
2
2
|
import { newFile, parseFile } from "./parse.js";
|
|
3
3
|
import { resolveExternal } from "./resolve-external.js";
|
|
4
|
-
import _bundle from "./bundle.js";
|
|
4
|
+
import { bundle as _bundle } from "./bundle.js";
|
|
5
5
|
import _dereference from "./dereference.js";
|
|
6
6
|
import * as url from "./util/url.js";
|
|
7
7
|
import { isHandledError, JSONParserErrorGroup } from "./util/errors.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hey-api/json-schema-ref-parser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Parse, Resolve, and Dereference JSON Schema $ref pointers",
|
|
5
5
|
"homepage": "https://heyapi.dev/",
|
|
6
6
|
"repository": {
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@eslint/js": "^9.16.0",
|
|
58
58
|
"@types/eslint": "9.6.1",
|
|
59
59
|
"@types/js-yaml": "^4.0.9",
|
|
60
|
+
"@types/lodash": "^4",
|
|
60
61
|
"@types/node": "^22",
|
|
61
62
|
"@typescript-eslint/eslint-plugin": "^8.17.0",
|
|
62
63
|
"@typescript-eslint/parser": "^8.17.0",
|
|
@@ -80,7 +81,8 @@
|
|
|
80
81
|
"dependencies": {
|
|
81
82
|
"@jsdevtools/ono": "^7.1.3",
|
|
82
83
|
"@types/json-schema": "^7.0.15",
|
|
83
|
-
"js-yaml": "^4.1.0"
|
|
84
|
+
"js-yaml": "^4.1.0",
|
|
85
|
+
"lodash": "^4.17.21"
|
|
84
86
|
},
|
|
85
87
|
"release": {
|
|
86
88
|
"branches": [
|