@hey-api/json-schema-ref-parser 0.0.0-next-20260212230650
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/LICENSE.md +21 -0
- package/README.md +117 -0
- package/dist/index.d.mts +645 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2124 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +63 -0
- package/src/__tests__/bundle.test.ts +59 -0
- package/src/__tests__/index.test.ts +43 -0
- package/src/__tests__/pointer.test.ts +34 -0
- package/src/__tests__/utils.ts +3 -0
- package/src/bundle.ts +743 -0
- package/src/dereference.ts +290 -0
- package/src/index.ts +599 -0
- package/src/options.ts +112 -0
- package/src/parse.ts +65 -0
- package/src/parsers/binary.ts +13 -0
- package/src/parsers/json.ts +38 -0
- package/src/parsers/text.ts +21 -0
- package/src/parsers/yaml.ts +26 -0
- package/src/pointer.ts +352 -0
- package/src/ref.ts +283 -0
- package/src/refs.ts +231 -0
- package/src/resolve-external.ts +142 -0
- package/src/resolvers/file.ts +25 -0
- package/src/resolvers/url.ts +99 -0
- package/src/types/index.ts +58 -0
- package/src/util/convert-path-to-posix.ts +8 -0
- package/src/util/errors.ts +155 -0
- package/src/util/is-windows.ts +2 -0
- package/src/util/plugins.ts +55 -0
- package/src/util/url.ts +265 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { ono } from '@jsdevtools/ono';
|
|
2
|
+
|
|
3
|
+
import type { $RefParser } from '.';
|
|
4
|
+
import type { DereferenceOptions, ParserOptions } from './options';
|
|
5
|
+
import Pointer from './pointer';
|
|
6
|
+
import $Ref from './ref';
|
|
7
|
+
import type $Refs from './refs';
|
|
8
|
+
import type { JSONSchema } from './types';
|
|
9
|
+
import { TimeoutError } from './util/errors';
|
|
10
|
+
import * as url from './util/url';
|
|
11
|
+
|
|
12
|
+
export default dereference;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Crawls the JSON schema, finds all JSON references, and dereferences them.
|
|
16
|
+
* This method mutates the JSON schema object, replacing JSON references with their resolved value.
|
|
17
|
+
*
|
|
18
|
+
* @param parser
|
|
19
|
+
* @param options
|
|
20
|
+
*/
|
|
21
|
+
function dereference(parser: $RefParser, options: ParserOptions) {
|
|
22
|
+
const start = Date.now();
|
|
23
|
+
// console.log('Dereferencing $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
24
|
+
const dereferenced = crawl<JSONSchema>(
|
|
25
|
+
parser.schema,
|
|
26
|
+
parser.$refs._root$Ref.path!,
|
|
27
|
+
'#',
|
|
28
|
+
new Set(),
|
|
29
|
+
new Set(),
|
|
30
|
+
new Map(),
|
|
31
|
+
parser.$refs,
|
|
32
|
+
options,
|
|
33
|
+
start,
|
|
34
|
+
);
|
|
35
|
+
parser.$refs.circular = dereferenced.circular;
|
|
36
|
+
parser.schema = dereferenced.value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Recursively crawls the given value, and dereferences any JSON references.
|
|
41
|
+
*
|
|
42
|
+
* @param obj - The value to crawl. If it's not an object or array, it will be ignored.
|
|
43
|
+
* @param path - The full path of `obj`, possibly with a JSON Pointer in the hash
|
|
44
|
+
* @param pathFromRoot - The path of `obj` from the schema root
|
|
45
|
+
* @param parents - An array of the parent objects that have already been dereferenced
|
|
46
|
+
* @param processedObjects - An array of all the objects that have already been processed
|
|
47
|
+
* @param dereferencedCache - An map of all the dereferenced objects
|
|
48
|
+
* @param $refs
|
|
49
|
+
* @param options
|
|
50
|
+
* @param startTime - The time when the dereferencing started
|
|
51
|
+
* @returns
|
|
52
|
+
*/
|
|
53
|
+
function crawl<S extends object = JSONSchema>(
|
|
54
|
+
obj: any,
|
|
55
|
+
path: string,
|
|
56
|
+
pathFromRoot: string,
|
|
57
|
+
parents: Set<any>,
|
|
58
|
+
processedObjects: Set<any>,
|
|
59
|
+
dereferencedCache: any,
|
|
60
|
+
$refs: $Refs<S>,
|
|
61
|
+
options: ParserOptions,
|
|
62
|
+
startTime: number,
|
|
63
|
+
) {
|
|
64
|
+
let dereferenced;
|
|
65
|
+
const result = {
|
|
66
|
+
circular: false,
|
|
67
|
+
value: obj,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (options && options.timeoutMs) {
|
|
71
|
+
if (Date.now() - startTime > options.timeoutMs) {
|
|
72
|
+
throw new TimeoutError(options.timeoutMs);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const derefOptions = (options.dereference || {}) as DereferenceOptions;
|
|
76
|
+
const isExcludedPath = derefOptions.excludedPathMatcher || (() => false);
|
|
77
|
+
|
|
78
|
+
if (derefOptions?.circular === 'ignore' || !processedObjects.has(obj)) {
|
|
79
|
+
if (
|
|
80
|
+
obj &&
|
|
81
|
+
typeof obj === 'object' &&
|
|
82
|
+
!ArrayBuffer.isView(obj) &&
|
|
83
|
+
!isExcludedPath(pathFromRoot)
|
|
84
|
+
) {
|
|
85
|
+
parents.add(obj);
|
|
86
|
+
processedObjects.add(obj);
|
|
87
|
+
|
|
88
|
+
if ($Ref.isAllowed$Ref(obj)) {
|
|
89
|
+
dereferenced = dereference$Ref(
|
|
90
|
+
obj,
|
|
91
|
+
path,
|
|
92
|
+
pathFromRoot,
|
|
93
|
+
parents,
|
|
94
|
+
processedObjects,
|
|
95
|
+
dereferencedCache,
|
|
96
|
+
$refs,
|
|
97
|
+
options,
|
|
98
|
+
startTime,
|
|
99
|
+
);
|
|
100
|
+
result.circular = dereferenced.circular;
|
|
101
|
+
result.value = dereferenced.value;
|
|
102
|
+
} else {
|
|
103
|
+
for (const key of Object.keys(obj)) {
|
|
104
|
+
const keyPath = Pointer.join(path, key);
|
|
105
|
+
const keyPathFromRoot = Pointer.join(pathFromRoot, key);
|
|
106
|
+
|
|
107
|
+
if (isExcludedPath(keyPathFromRoot)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const value = obj[key];
|
|
112
|
+
let circular = false;
|
|
113
|
+
|
|
114
|
+
if ($Ref.isAllowed$Ref(value)) {
|
|
115
|
+
dereferenced = dereference$Ref(
|
|
116
|
+
value,
|
|
117
|
+
keyPath,
|
|
118
|
+
keyPathFromRoot,
|
|
119
|
+
parents,
|
|
120
|
+
processedObjects,
|
|
121
|
+
dereferencedCache,
|
|
122
|
+
$refs,
|
|
123
|
+
options,
|
|
124
|
+
startTime,
|
|
125
|
+
);
|
|
126
|
+
circular = dereferenced.circular;
|
|
127
|
+
// Avoid pointless mutations; breaks frozen objects to no profit
|
|
128
|
+
if (obj[key] !== dereferenced.value) {
|
|
129
|
+
obj[key] = dereferenced.value;
|
|
130
|
+
derefOptions?.onDereference?.(value.$ref, obj[key], obj, key);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
if (!parents.has(value)) {
|
|
134
|
+
dereferenced = crawl(
|
|
135
|
+
value,
|
|
136
|
+
keyPath,
|
|
137
|
+
keyPathFromRoot,
|
|
138
|
+
parents,
|
|
139
|
+
processedObjects,
|
|
140
|
+
dereferencedCache,
|
|
141
|
+
$refs,
|
|
142
|
+
options,
|
|
143
|
+
startTime,
|
|
144
|
+
);
|
|
145
|
+
circular = dereferenced.circular;
|
|
146
|
+
// Avoid pointless mutations; breaks frozen objects to no profit
|
|
147
|
+
if (obj[key] !== dereferenced.value) {
|
|
148
|
+
obj[key] = dereferenced.value;
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
circular = foundCircularReference(keyPath, $refs, options);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Set the "isCircular" flag if this or any other property is circular
|
|
156
|
+
result.circular = result.circular || circular;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
parents.delete(obj);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Dereferences the given JSON Reference, and then crawls the resulting value.
|
|
169
|
+
*
|
|
170
|
+
* @param $ref - The JSON Reference to resolve
|
|
171
|
+
* @param path - The full path of `$ref`, possibly with a JSON Pointer in the hash
|
|
172
|
+
* @param pathFromRoot - The path of `$ref` from the schema root
|
|
173
|
+
* @param parents - An array of the parent objects that have already been dereferenced
|
|
174
|
+
* @param processedObjects - An array of all the objects that have already been dereferenced
|
|
175
|
+
* @param dereferencedCache - An map of all the dereferenced objects
|
|
176
|
+
* @param $refs
|
|
177
|
+
* @param options
|
|
178
|
+
* @returns
|
|
179
|
+
*/
|
|
180
|
+
function dereference$Ref<S extends object = JSONSchema>(
|
|
181
|
+
$ref: any,
|
|
182
|
+
path: string,
|
|
183
|
+
pathFromRoot: string,
|
|
184
|
+
parents: Set<any>,
|
|
185
|
+
processedObjects: any,
|
|
186
|
+
dereferencedCache: any,
|
|
187
|
+
$refs: $Refs<S>,
|
|
188
|
+
options: ParserOptions,
|
|
189
|
+
startTime: number,
|
|
190
|
+
) {
|
|
191
|
+
const $refPath = url.resolve(path, $ref.$ref);
|
|
192
|
+
|
|
193
|
+
const cache = dereferencedCache.get($refPath);
|
|
194
|
+
if (cache && !cache.circular) {
|
|
195
|
+
const refKeys = Object.keys($ref);
|
|
196
|
+
if (refKeys.length > 1) {
|
|
197
|
+
const extraKeys = {};
|
|
198
|
+
for (const key of refKeys) {
|
|
199
|
+
if (key !== '$ref' && !(key in cache.value)) {
|
|
200
|
+
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
201
|
+
extraKeys[key] = $ref[key];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
circular: cache.circular,
|
|
206
|
+
value: Object.assign({}, structuredClone(cache.value), extraKeys),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Return a deep-cloned value so each occurrence is an independent copy
|
|
211
|
+
return { circular: cache.circular, value: structuredClone(cache.value) };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const pointer = $refs._resolve($refPath, path, options);
|
|
215
|
+
|
|
216
|
+
if (pointer === null) {
|
|
217
|
+
return {
|
|
218
|
+
circular: false,
|
|
219
|
+
value: null,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check for circular references
|
|
224
|
+
const directCircular = pointer.circular;
|
|
225
|
+
let circular = directCircular || parents.has(pointer.value);
|
|
226
|
+
if (circular) {
|
|
227
|
+
foundCircularReference(path, $refs, options);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Dereference the JSON reference
|
|
231
|
+
let dereferencedValue = $Ref.dereference($ref, pointer.value);
|
|
232
|
+
|
|
233
|
+
// Crawl the dereferenced value (unless it's circular)
|
|
234
|
+
if (!circular) {
|
|
235
|
+
// Determine if the dereferenced value is circular
|
|
236
|
+
const dereferenced = crawl(
|
|
237
|
+
dereferencedValue,
|
|
238
|
+
pointer.path,
|
|
239
|
+
pathFromRoot,
|
|
240
|
+
parents,
|
|
241
|
+
processedObjects,
|
|
242
|
+
dereferencedCache,
|
|
243
|
+
$refs,
|
|
244
|
+
options,
|
|
245
|
+
startTime,
|
|
246
|
+
);
|
|
247
|
+
circular = dereferenced.circular;
|
|
248
|
+
dereferencedValue = dereferenced.value;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (circular && !directCircular && options.dereference?.circular === 'ignore') {
|
|
252
|
+
// The user has chosen to "ignore" circular references, so don't change the value
|
|
253
|
+
dereferencedValue = $ref;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (directCircular) {
|
|
257
|
+
// The pointer is a DIRECT circular reference (i.e. it references itself).
|
|
258
|
+
// So replace the $ref path with the absolute path from the JSON Schema root
|
|
259
|
+
dereferencedValue.$ref = pathFromRoot;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const dereferencedObject = {
|
|
263
|
+
circular,
|
|
264
|
+
value: dereferencedValue,
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// only cache if no extra properties than $ref
|
|
268
|
+
if (Object.keys($ref).length === 1) {
|
|
269
|
+
dereferencedCache.set($refPath, dereferencedObject);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return dereferencedObject;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Called when a circular reference is found.
|
|
277
|
+
* It sets the {@link $Refs#circular} flag, and throws an error if options.dereference.circular is false.
|
|
278
|
+
*
|
|
279
|
+
* @param keyPath - The JSON Reference path of the circular reference
|
|
280
|
+
* @param $refs
|
|
281
|
+
* @param options
|
|
282
|
+
* @returns - always returns true, to indicate that a circular reference was found
|
|
283
|
+
*/
|
|
284
|
+
function foundCircularReference(keyPath: any, $refs: any, options: any) {
|
|
285
|
+
$refs.circular = true;
|
|
286
|
+
if (!options.dereference.circular) {
|
|
287
|
+
throw ono.reference(`Circular $ref pointer found at ${keyPath}`);
|
|
288
|
+
}
|
|
289
|
+
return true;
|
|
290
|
+
}
|