@apidevtools/json-schema-ref-parser 15.3.2 → 15.3.3
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/bundle.js +2 -2
- package/dist/lib/dereference.js +5 -13
- package/dist/lib/pointer.js +2 -2
- package/dist/lib/ref.d.ts +2 -2
- package/dist/lib/ref.js +27 -2
- package/dist/lib/refs.d.ts +2 -0
- package/dist/lib/refs.js +13 -3
- package/dist/lib/util/schema-resources.js +28 -3
- package/lib/bundle.ts +2 -2
- package/lib/dereference.ts +5 -13
- package/lib/pointer.ts +2 -2
- package/lib/ref.ts +34 -1
- package/lib/refs.ts +16 -3
- package/lib/util/schema-resources.ts +39 -3
- package/package.json +1 -1
package/dist/lib/bundle.js
CHANGED
|
@@ -45,7 +45,7 @@ function crawl(parent, key, path, scopeBase, dynamicIdScope, pathFromRoot, indir
|
|
|
45
45
|
const isExcludedPath = bundleOptions.excludedPathMatcher || (() => false);
|
|
46
46
|
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) {
|
|
47
47
|
const currentScopeBase = dynamicIdScope ? getSchemaBasePath(scopeBase, obj) : scopeBase;
|
|
48
|
-
if ($Ref.isAllowed$Ref(obj)) {
|
|
48
|
+
if ($Ref.isAllowed$Ref(obj, options, dynamicIdScope)) {
|
|
49
49
|
inventory$Ref(parent, key, path, currentScopeBase, dynamicIdScope, pathFromRoot, indirections, inventory, $refs, options);
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
@@ -71,7 +71,7 @@ function crawl(parent, key, path, scopeBase, dynamicIdScope, pathFromRoot, indir
|
|
|
71
71
|
const keyPath = Pointer.join(path, key);
|
|
72
72
|
const keyPathFromRoot = Pointer.join(pathFromRoot, key);
|
|
73
73
|
const value = obj[key];
|
|
74
|
-
if ($Ref.isAllowed$Ref(value)) {
|
|
74
|
+
if ($Ref.isAllowed$Ref(value, options, dynamicIdScope)) {
|
|
75
75
|
const valueScopeBase = dynamicIdScope ? getSchemaBasePath(currentScopeBase, value) : currentScopeBase;
|
|
76
76
|
inventory$Ref(obj, key, keyPath, valueScopeBase, dynamicIdScope, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
77
77
|
}
|
package/dist/lib/dereference.js
CHANGED
|
@@ -53,7 +53,7 @@ function crawl(obj, path, scopeBase, dynamicIdScope, pathFromRoot, parents, proc
|
|
|
53
53
|
parents.add(obj);
|
|
54
54
|
processedObjects.add(obj);
|
|
55
55
|
const currentScopeBase = dynamicIdScope ? getSchemaBasePath(scopeBase, obj) : scopeBase;
|
|
56
|
-
if ($Ref.isAllowed$Ref(obj, options)) {
|
|
56
|
+
if ($Ref.isAllowed$Ref(obj, options, dynamicIdScope)) {
|
|
57
57
|
dereferenced = dereference$Ref(obj, path, currentScopeBase, dynamicIdScope, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth);
|
|
58
58
|
result.circular = dereferenced.circular;
|
|
59
59
|
result.value = dereferenced.value;
|
|
@@ -68,7 +68,7 @@ function crawl(obj, path, scopeBase, dynamicIdScope, pathFromRoot, parents, proc
|
|
|
68
68
|
}
|
|
69
69
|
const value = obj[key];
|
|
70
70
|
let circular;
|
|
71
|
-
if ($Ref.isAllowed$Ref(value, options)) {
|
|
71
|
+
if ($Ref.isAllowed$Ref(value, options, dynamicIdScope)) {
|
|
72
72
|
const valueScopeBase = dynamicIdScope ? getSchemaBasePath(currentScopeBase, value) : currentScopeBase;
|
|
73
73
|
dereferenced = dereference$Ref(value, keyPath, valueScopeBase, dynamicIdScope, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth);
|
|
74
74
|
circular = dereferenced.circular;
|
|
@@ -153,18 +153,10 @@ function dereference$Ref($ref, path, scopeBase, dynamicIdScope, pathFromRoot, pa
|
|
|
153
153
|
// If the cached object however is _not_ circular and there are additional keys alongside our
|
|
154
154
|
// `$ref` pointer here we should merge them back in and return that.
|
|
155
155
|
if (!cache.circular) {
|
|
156
|
-
|
|
157
|
-
if (refKeys.length > 1) {
|
|
158
|
-
const extraKeys = {};
|
|
159
|
-
for (const key of refKeys) {
|
|
160
|
-
if (key !== "$ref" && !(key in cache.value)) {
|
|
161
|
-
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
162
|
-
extraKeys[key] = $ref[key];
|
|
163
|
-
}
|
|
164
|
-
}
|
|
156
|
+
if (Object.keys($ref).length > 1) {
|
|
165
157
|
return {
|
|
166
158
|
circular: cache.circular,
|
|
167
|
-
value:
|
|
159
|
+
value: $Ref.dereference($ref, cache.value, options, dynamicIdScope),
|
|
168
160
|
};
|
|
169
161
|
}
|
|
170
162
|
return cache;
|
|
@@ -210,7 +202,7 @@ function dereference$Ref($ref, path, scopeBase, dynamicIdScope, pathFromRoot, pa
|
|
|
210
202
|
foundCircularReference(path, $refs, options);
|
|
211
203
|
}
|
|
212
204
|
// Dereference the JSON reference
|
|
213
|
-
let dereferencedValue = $Ref.dereference($ref, pointer.value, options);
|
|
205
|
+
let dereferencedValue = $Ref.dereference($ref, pointer.value, options, dynamicIdScope);
|
|
214
206
|
// Crawl the dereferenced value (unless it's circular)
|
|
215
207
|
if (!circular) {
|
|
216
208
|
// Determine if the dereferenced value is circular
|
package/dist/lib/pointer.js
CHANGED
|
@@ -256,7 +256,7 @@ class Pointer {
|
|
|
256
256
|
*/
|
|
257
257
|
function resolveIf$Ref(pointer, options, pathFromRoot) {
|
|
258
258
|
// Is the value a JSON reference? (and allowed?)
|
|
259
|
-
if ($Ref.isAllowed$Ref(pointer.value, options)) {
|
|
259
|
+
if ($Ref.isAllowed$Ref(pointer.value, options, pointer.$ref.dynamicIdScope)) {
|
|
260
260
|
const resolutionBase = pointer.$ref.dynamicIdScope ? pointer.scopeBase : pointer.path;
|
|
261
261
|
const $refPath = url.resolve(resolutionBase, pointer.value.$ref);
|
|
262
262
|
if ($refPath === pointer.path && !isRootPath(pathFromRoot)) {
|
|
@@ -272,7 +272,7 @@ function resolveIf$Ref(pointer, options, pathFromRoot) {
|
|
|
272
272
|
if ($Ref.isExtended$Ref(pointer.value)) {
|
|
273
273
|
// This JSON reference "extends" the resolved value, rather than simply pointing to it.
|
|
274
274
|
// So the resolved path does NOT change. Just the value does.
|
|
275
|
-
pointer.value = $Ref.dereference(pointer.value, resolved.value, options);
|
|
275
|
+
pointer.value = $Ref.dereference(pointer.value, resolved.value, options, pointer.$ref.dynamicIdScope);
|
|
276
276
|
if (pointer.$ref.dynamicIdScope) {
|
|
277
277
|
pointer.scopeBase = getSchemaBasePath(pointer.scopeBase, pointer.value);
|
|
278
278
|
}
|
package/dist/lib/ref.d.ts
CHANGED
|
@@ -114,7 +114,7 @@ declare class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = P
|
|
|
114
114
|
* @param options
|
|
115
115
|
* @returns
|
|
116
116
|
*/
|
|
117
|
-
static isAllowed$Ref<S extends object = JSONSchema>(value: unknown, options?: ParserOptions<S
|
|
117
|
+
static isAllowed$Ref<S extends object = JSONSchema>(value: unknown, options?: ParserOptions<S>, allowPlainNameFragments?: boolean): true | undefined;
|
|
118
118
|
/**
|
|
119
119
|
* Determines whether the given value is a JSON reference that "extends" its resolved value.
|
|
120
120
|
* That is, it has extra properties (in addition to "$ref"), so rather than simply pointing to
|
|
@@ -182,6 +182,6 @@ declare class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = P
|
|
|
182
182
|
* @param options - The options
|
|
183
183
|
* @returns - Returns the dereferenced value
|
|
184
184
|
*/
|
|
185
|
-
static dereference<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>($ref: $Ref<S, O>, resolvedValue: S, options?: O): S;
|
|
185
|
+
static dereference<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>($ref: $Ref<S, O>, resolvedValue: S, options?: O, useSpecCompliantRefSiblings?: boolean): S;
|
|
186
186
|
}
|
|
187
187
|
export default $Ref;
|
package/dist/lib/ref.js
CHANGED
|
@@ -170,12 +170,16 @@ class $Ref {
|
|
|
170
170
|
* @param options
|
|
171
171
|
* @returns
|
|
172
172
|
*/
|
|
173
|
-
static isAllowed$Ref(value, options) {
|
|
173
|
+
static isAllowed$Ref(value, options, allowPlainNameFragments = false) {
|
|
174
174
|
if (this.is$Ref(value)) {
|
|
175
175
|
if (value.$ref.substring(0, 2) === "#/" || value.$ref === "#") {
|
|
176
176
|
// It's a JSON Pointer reference, which is always allowed
|
|
177
177
|
return true;
|
|
178
178
|
}
|
|
179
|
+
else if (allowPlainNameFragments && value.$ref[0] === "#") {
|
|
180
|
+
// JSON Schema 2019-09+ allows plain-name fragments declared via $anchor/$dynamicAnchor
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
179
183
|
else if (value.$ref[0] !== "#" && (!options || options.resolve?.external)) {
|
|
180
184
|
// It's an external reference, which is allowed by the options
|
|
181
185
|
return true;
|
|
@@ -252,7 +256,10 @@ class $Ref {
|
|
|
252
256
|
* @param options - The options
|
|
253
257
|
* @returns - Returns the dereferenced value
|
|
254
258
|
*/
|
|
255
|
-
static dereference($ref, resolvedValue, options) {
|
|
259
|
+
static dereference($ref, resolvedValue, options, useSpecCompliantRefSiblings = false) {
|
|
260
|
+
if ($Ref.isExtended$Ref($ref) && useSpecCompliantRefSiblings) {
|
|
261
|
+
return preserveRefSiblings($ref, resolvedValue);
|
|
262
|
+
}
|
|
256
263
|
if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) {
|
|
257
264
|
const merged = {};
|
|
258
265
|
for (const key of Object.keys($ref)) {
|
|
@@ -287,6 +294,24 @@ class $Ref {
|
|
|
287
294
|
}
|
|
288
295
|
}
|
|
289
296
|
}
|
|
297
|
+
function preserveRefSiblings(refValue, resolvedValue) {
|
|
298
|
+
const preserved = {};
|
|
299
|
+
const existingAllOf = "allOf" in refValue ? refValue.allOf : undefined;
|
|
300
|
+
for (const key of Object.keys(refValue)) {
|
|
301
|
+
if (key !== "$ref" && key !== "allOf") {
|
|
302
|
+
preserved[key] = refValue[key];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const allOf = [resolvedValue];
|
|
306
|
+
if (Array.isArray(existingAllOf)) {
|
|
307
|
+
allOf.push(...existingAllOf);
|
|
308
|
+
}
|
|
309
|
+
else if (existingAllOf !== undefined) {
|
|
310
|
+
allOf.push(existingAllOf);
|
|
311
|
+
}
|
|
312
|
+
preserved.allOf = allOf;
|
|
313
|
+
return preserved;
|
|
314
|
+
}
|
|
290
315
|
function deepMerge(target, source) {
|
|
291
316
|
//return {...target, ...source};
|
|
292
317
|
// If either isn't an object, just return source (overwrite)
|
package/dist/lib/refs.d.ts
CHANGED
|
@@ -80,6 +80,7 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
80
80
|
*/
|
|
81
81
|
_add(path: string): $Ref<S, O>;
|
|
82
82
|
_addAlias(path: string, value: S, pathType?: string | unknown, dynamicIdScope?: boolean): $Ref<S, O>;
|
|
83
|
+
_addExactAlias(path: string, targetPath: string): string;
|
|
83
84
|
/**
|
|
84
85
|
* Resolves the given JSON reference.
|
|
85
86
|
*
|
|
@@ -98,6 +99,7 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
98
99
|
*/
|
|
99
100
|
_$refs: $RefsMap<S, O>;
|
|
100
101
|
_aliases: $RefsMap<S, O>;
|
|
102
|
+
_exactAliases: Record<string, string>;
|
|
101
103
|
/**
|
|
102
104
|
* The {@link $Ref} object that is the root of the JSON schema.
|
|
103
105
|
*
|
package/dist/lib/refs.js
CHANGED
|
@@ -99,7 +99,7 @@ export default class $Refs {
|
|
|
99
99
|
*/
|
|
100
100
|
_get$Ref(path) {
|
|
101
101
|
path = url.resolve(this._root$Ref.path, path);
|
|
102
|
-
return this._getRef(path);
|
|
102
|
+
return this._getRef(this._exactAliases[path] || path);
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
105
|
* Creates a new {@link $Ref} object and adds it to this {@link $Refs} object.
|
|
@@ -127,6 +127,13 @@ export default class $Refs {
|
|
|
127
127
|
this._aliases[withoutHash] = $ref;
|
|
128
128
|
return $ref;
|
|
129
129
|
}
|
|
130
|
+
_addExactAlias(path, targetPath) {
|
|
131
|
+
if (!path || this._exactAliases[path]) {
|
|
132
|
+
return this._exactAliases[path];
|
|
133
|
+
}
|
|
134
|
+
this._exactAliases[path] = targetPath;
|
|
135
|
+
return targetPath;
|
|
136
|
+
}
|
|
130
137
|
/**
|
|
131
138
|
* Resolves the given JSON reference.
|
|
132
139
|
*
|
|
@@ -138,11 +145,12 @@ export default class $Refs {
|
|
|
138
145
|
*/
|
|
139
146
|
_resolve(path, pathFromRoot, options) {
|
|
140
147
|
const absPath = url.resolve(this._root$Ref.path, path);
|
|
141
|
-
const
|
|
148
|
+
const canonicalPath = this._exactAliases[absPath] || absPath;
|
|
149
|
+
const $ref = this._getRef(canonicalPath);
|
|
142
150
|
if (!$ref) {
|
|
143
151
|
throw new Error(`Error resolving $ref pointer "${path}". \n"${url.stripHash(absPath)}" not found.`);
|
|
144
152
|
}
|
|
145
|
-
return $ref.resolve(
|
|
153
|
+
return $ref.resolve(canonicalPath, options, path, pathFromRoot);
|
|
146
154
|
}
|
|
147
155
|
/**
|
|
148
156
|
* A map of paths/urls to {@link $Ref} objects
|
|
@@ -152,6 +160,7 @@ export default class $Refs {
|
|
|
152
160
|
*/
|
|
153
161
|
_$refs = {};
|
|
154
162
|
_aliases = {};
|
|
163
|
+
_exactAliases = {};
|
|
155
164
|
/**
|
|
156
165
|
* The {@link $Ref} object that is the root of the JSON schema.
|
|
157
166
|
*
|
|
@@ -168,6 +177,7 @@ export default class $Refs {
|
|
|
168
177
|
this.circular = false;
|
|
169
178
|
this._$refs = {};
|
|
170
179
|
this._aliases = {};
|
|
180
|
+
this._exactAliases = {};
|
|
171
181
|
// @ts-ignore
|
|
172
182
|
this._root$Ref = null;
|
|
173
183
|
}
|
|
@@ -20,20 +20,22 @@ export function registerSchemaResources($refs, basePath, value, pathType, dynami
|
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
const seen = new Set();
|
|
23
|
-
const visit = (node, scopeBase) => {
|
|
23
|
+
const visit = (node, scopeBase, pointerTokens) => {
|
|
24
24
|
if (!node || typeof node !== "object" || ArrayBuffer.isView(node) || seen.has(node)) {
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
seen.add(node);
|
|
28
28
|
const nextScopeBase = getSchemaBasePath(scopeBase, node);
|
|
29
|
+
const resourcePointerTokens = nextScopeBase === scopeBase ? pointerTokens : [];
|
|
29
30
|
if (nextScopeBase !== scopeBase) {
|
|
30
31
|
$refs._addAlias(nextScopeBase, node, pathType, dynamicIdScope);
|
|
31
32
|
}
|
|
33
|
+
registerAnchorAliases($refs, nextScopeBase, resourcePointerTokens, node);
|
|
32
34
|
for (const key of Object.keys(node)) {
|
|
33
|
-
visit(node[key], nextScopeBase);
|
|
35
|
+
visit(node[key], nextScopeBase, [...resourcePointerTokens, key]);
|
|
34
36
|
}
|
|
35
37
|
};
|
|
36
|
-
visit(value, basePath);
|
|
38
|
+
visit(value, basePath, []);
|
|
37
39
|
}
|
|
38
40
|
function getSchemaId(value) {
|
|
39
41
|
if (value &&
|
|
@@ -45,3 +47,26 @@ function getSchemaId(value) {
|
|
|
45
47
|
}
|
|
46
48
|
return undefined;
|
|
47
49
|
}
|
|
50
|
+
function registerAnchorAliases($refs, scopeBase, pointerTokens, value) {
|
|
51
|
+
if (!value || typeof value !== "object" || ArrayBuffer.isView(value)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const resourceBase = url.stripHash(scopeBase);
|
|
55
|
+
const targetPath = pointerTokens.length > 0 ? joinPointerPath(resourceBase, pointerTokens) : `${resourceBase}#`;
|
|
56
|
+
const anchors = [
|
|
57
|
+
value.$anchor,
|
|
58
|
+
value.$dynamicAnchor,
|
|
59
|
+
];
|
|
60
|
+
for (const anchor of anchors) {
|
|
61
|
+
if (typeof anchor === "string" && anchor.length > 0) {
|
|
62
|
+
$refs._addExactAlias(`${resourceBase}#${anchor}`, targetPath);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function joinPointerPath(basePath, tokens) {
|
|
67
|
+
let path = `${basePath}#`;
|
|
68
|
+
for (const token of tokens) {
|
|
69
|
+
path += `/${token.replace(/~/g, "~0").replace(/\//g, "~1")}`;
|
|
70
|
+
}
|
|
71
|
+
return path;
|
|
72
|
+
}
|
package/lib/bundle.ts
CHANGED
|
@@ -97,7 +97,7 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
|
|
|
97
97
|
|
|
98
98
|
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) {
|
|
99
99
|
const currentScopeBase = dynamicIdScope ? getSchemaBasePath(scopeBase, obj) : scopeBase;
|
|
100
|
-
if ($Ref.isAllowed$Ref(obj)) {
|
|
100
|
+
if ($Ref.isAllowed$Ref(obj, options, dynamicIdScope)) {
|
|
101
101
|
inventory$Ref(parent, key, path, currentScopeBase, dynamicIdScope, pathFromRoot, indirections, inventory, $refs, options);
|
|
102
102
|
} else {
|
|
103
103
|
// Crawl the object in a specific order that's optimized for bundling.
|
|
@@ -122,7 +122,7 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
|
|
|
122
122
|
const keyPathFromRoot = Pointer.join(pathFromRoot, key);
|
|
123
123
|
const value = obj[key];
|
|
124
124
|
|
|
125
|
-
if ($Ref.isAllowed$Ref(value)) {
|
|
125
|
+
if ($Ref.isAllowed$Ref(value, options, dynamicIdScope)) {
|
|
126
126
|
const valueScopeBase = dynamicIdScope ? getSchemaBasePath(currentScopeBase, value) : currentScopeBase;
|
|
127
127
|
inventory$Ref(
|
|
128
128
|
obj,
|
package/lib/dereference.ts
CHANGED
|
@@ -94,7 +94,7 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
|
|
|
94
94
|
processedObjects.add(obj);
|
|
95
95
|
const currentScopeBase = dynamicIdScope ? getSchemaBasePath(scopeBase, obj) : scopeBase;
|
|
96
96
|
|
|
97
|
-
if ($Ref.isAllowed$Ref(obj, options)) {
|
|
97
|
+
if ($Ref.isAllowed$Ref(obj, options, dynamicIdScope)) {
|
|
98
98
|
dereferenced = dereference$Ref(
|
|
99
99
|
obj,
|
|
100
100
|
path,
|
|
@@ -125,7 +125,7 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
|
|
|
125
125
|
const value = obj[key];
|
|
126
126
|
let circular;
|
|
127
127
|
|
|
128
|
-
if ($Ref.isAllowed$Ref(value, options)) {
|
|
128
|
+
if ($Ref.isAllowed$Ref(value, options, dynamicIdScope)) {
|
|
129
129
|
const valueScopeBase = dynamicIdScope ? getSchemaBasePath(currentScopeBase, value) : currentScopeBase;
|
|
130
130
|
dereferenced = dereference$Ref(
|
|
131
131
|
value,
|
|
@@ -257,18 +257,10 @@ function dereference$Ref<S extends object = JSONSchema, O extends ParserOptions<
|
|
|
257
257
|
// If the cached object however is _not_ circular and there are additional keys alongside our
|
|
258
258
|
// `$ref` pointer here we should merge them back in and return that.
|
|
259
259
|
if (!cache.circular) {
|
|
260
|
-
|
|
261
|
-
if (refKeys.length > 1) {
|
|
262
|
-
const extraKeys = {};
|
|
263
|
-
for (const key of refKeys) {
|
|
264
|
-
if (key !== "$ref" && !(key in cache.value)) {
|
|
265
|
-
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
266
|
-
extraKeys[key] = $ref[key];
|
|
267
|
-
}
|
|
268
|
-
}
|
|
260
|
+
if (Object.keys($ref).length > 1) {
|
|
269
261
|
return {
|
|
270
262
|
circular: cache.circular,
|
|
271
|
-
value:
|
|
263
|
+
value: $Ref.dereference($ref, cache.value, options, dynamicIdScope),
|
|
272
264
|
};
|
|
273
265
|
}
|
|
274
266
|
|
|
@@ -318,7 +310,7 @@ function dereference$Ref<S extends object = JSONSchema, O extends ParserOptions<
|
|
|
318
310
|
}
|
|
319
311
|
|
|
320
312
|
// Dereference the JSON reference
|
|
321
|
-
let dereferencedValue = $Ref.dereference($ref, pointer.value, options);
|
|
313
|
+
let dereferencedValue = $Ref.dereference($ref, pointer.value, options, dynamicIdScope);
|
|
322
314
|
|
|
323
315
|
// Crawl the dereferenced value (unless it's circular)
|
|
324
316
|
if (!circular) {
|
package/lib/pointer.ts
CHANGED
|
@@ -308,7 +308,7 @@ function resolveIf$Ref<S extends object = JSONSchema, O extends ParserOptions<S>
|
|
|
308
308
|
) {
|
|
309
309
|
// Is the value a JSON reference? (and allowed?)
|
|
310
310
|
|
|
311
|
-
if ($Ref.isAllowed$Ref(pointer.value, options)) {
|
|
311
|
+
if ($Ref.isAllowed$Ref(pointer.value, options, pointer.$ref.dynamicIdScope)) {
|
|
312
312
|
const resolutionBase = pointer.$ref.dynamicIdScope ? pointer.scopeBase : pointer.path;
|
|
313
313
|
const $refPath = url.resolve(resolutionBase, pointer.value.$ref);
|
|
314
314
|
|
|
@@ -326,7 +326,7 @@ function resolveIf$Ref<S extends object = JSONSchema, O extends ParserOptions<S>
|
|
|
326
326
|
if ($Ref.isExtended$Ref(pointer.value)) {
|
|
327
327
|
// This JSON reference "extends" the resolved value, rather than simply pointing to it.
|
|
328
328
|
// So the resolved path does NOT change. Just the value does.
|
|
329
|
-
pointer.value = $Ref.dereference(pointer.value, resolved.value, options);
|
|
329
|
+
pointer.value = $Ref.dereference(pointer.value, resolved.value, options, pointer.$ref.dynamicIdScope);
|
|
330
330
|
if (pointer.$ref.dynamicIdScope) {
|
|
331
331
|
pointer.scopeBase = getSchemaBasePath(pointer.scopeBase, pointer.value);
|
|
332
332
|
}
|
package/lib/ref.ts
CHANGED
|
@@ -199,11 +199,18 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
|
|
|
199
199
|
* @param options
|
|
200
200
|
* @returns
|
|
201
201
|
*/
|
|
202
|
-
static isAllowed$Ref<S extends object = JSONSchema>(
|
|
202
|
+
static isAllowed$Ref<S extends object = JSONSchema>(
|
|
203
|
+
value: unknown,
|
|
204
|
+
options?: ParserOptions<S>,
|
|
205
|
+
allowPlainNameFragments = false,
|
|
206
|
+
) {
|
|
203
207
|
if (this.is$Ref(value)) {
|
|
204
208
|
if (value.$ref.substring(0, 2) === "#/" || value.$ref === "#") {
|
|
205
209
|
// It's a JSON Pointer reference, which is always allowed
|
|
206
210
|
return true;
|
|
211
|
+
} else if (allowPlainNameFragments && value.$ref[0] === "#") {
|
|
212
|
+
// JSON Schema 2019-09+ allows plain-name fragments declared via $anchor/$dynamicAnchor
|
|
213
|
+
return true;
|
|
207
214
|
} else if (value.$ref[0] !== "#" && (!options || options.resolve?.external)) {
|
|
208
215
|
// It's an external reference, which is allowed by the options
|
|
209
216
|
return true;
|
|
@@ -286,7 +293,12 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
|
|
|
286
293
|
$ref: $Ref<S, O>,
|
|
287
294
|
resolvedValue: S,
|
|
288
295
|
options?: O,
|
|
296
|
+
useSpecCompliantRefSiblings = false,
|
|
289
297
|
): S {
|
|
298
|
+
if ($Ref.isExtended$Ref($ref) && useSpecCompliantRefSiblings) {
|
|
299
|
+
return preserveRefSiblings($ref, resolvedValue) as S;
|
|
300
|
+
}
|
|
301
|
+
|
|
290
302
|
if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) {
|
|
291
303
|
const merged = {} as Partial<S>;
|
|
292
304
|
for (const key of Object.keys($ref)) {
|
|
@@ -325,6 +337,27 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
|
|
|
325
337
|
}
|
|
326
338
|
}
|
|
327
339
|
|
|
340
|
+
function preserveRefSiblings<T>(refValue: Record<string, any>, resolvedValue: T): T {
|
|
341
|
+
const preserved = {} as Record<string, any>;
|
|
342
|
+
const existingAllOf = "allOf" in refValue ? refValue.allOf : undefined;
|
|
343
|
+
|
|
344
|
+
for (const key of Object.keys(refValue)) {
|
|
345
|
+
if (key !== "$ref" && key !== "allOf") {
|
|
346
|
+
preserved[key] = refValue[key];
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const allOf = [resolvedValue];
|
|
351
|
+
if (Array.isArray(existingAllOf)) {
|
|
352
|
+
allOf.push(...existingAllOf);
|
|
353
|
+
} else if (existingAllOf !== undefined) {
|
|
354
|
+
allOf.push(existingAllOf);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
preserved.allOf = allOf;
|
|
358
|
+
return preserved as T;
|
|
359
|
+
}
|
|
360
|
+
|
|
328
361
|
function deepMerge<T>(target: Partial<T>, source: Partial<T>): T {
|
|
329
362
|
//return {...target, ...source};
|
|
330
363
|
|
package/lib/refs.ts
CHANGED
|
@@ -112,7 +112,7 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
112
112
|
*/
|
|
113
113
|
_get$Ref(path: string) {
|
|
114
114
|
path = url.resolve(this._root$Ref.path!, path);
|
|
115
|
-
return this._getRef(path);
|
|
115
|
+
return this._getRef(this._exactAliases[path] || path);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/**
|
|
@@ -149,6 +149,15 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
149
149
|
return $ref;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
_addExactAlias(path: string, targetPath: string) {
|
|
153
|
+
if (!path || this._exactAliases[path]) {
|
|
154
|
+
return this._exactAliases[path];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this._exactAliases[path] = targetPath;
|
|
158
|
+
return targetPath;
|
|
159
|
+
}
|
|
160
|
+
|
|
152
161
|
/**
|
|
153
162
|
* Resolves the given JSON reference.
|
|
154
163
|
*
|
|
@@ -160,13 +169,14 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
160
169
|
*/
|
|
161
170
|
_resolve(path: string, pathFromRoot: string, options?: O) {
|
|
162
171
|
const absPath = url.resolve(this._root$Ref.path!, path);
|
|
163
|
-
const
|
|
172
|
+
const canonicalPath = this._exactAliases[absPath] || absPath;
|
|
173
|
+
const $ref = this._getRef(canonicalPath);
|
|
164
174
|
|
|
165
175
|
if (!$ref) {
|
|
166
176
|
throw new Error(`Error resolving $ref pointer "${path}". \n"${url.stripHash(absPath)}" not found.`);
|
|
167
177
|
}
|
|
168
178
|
|
|
169
|
-
return $ref.resolve(
|
|
179
|
+
return $ref.resolve(canonicalPath, options, path, pathFromRoot);
|
|
170
180
|
}
|
|
171
181
|
|
|
172
182
|
/**
|
|
@@ -179,6 +189,8 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
179
189
|
|
|
180
190
|
_aliases: $RefsMap<S, O> = {};
|
|
181
191
|
|
|
192
|
+
_exactAliases: Record<string, string> = {};
|
|
193
|
+
|
|
182
194
|
/**
|
|
183
195
|
* The {@link $Ref} object that is the root of the JSON schema.
|
|
184
196
|
*
|
|
@@ -197,6 +209,7 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
197
209
|
|
|
198
210
|
this._$refs = {};
|
|
199
211
|
this._aliases = {};
|
|
212
|
+
this._exactAliases = {};
|
|
200
213
|
|
|
201
214
|
// @ts-ignore
|
|
202
215
|
this._root$Ref = null;
|
|
@@ -38,7 +38,7 @@ export function registerSchemaResources<S extends object = JSONSchema, O extends
|
|
|
38
38
|
|
|
39
39
|
const seen = new Set<object>();
|
|
40
40
|
|
|
41
|
-
const visit = (node: unknown, scopeBase: string) => {
|
|
41
|
+
const visit = (node: unknown, scopeBase: string, pointerTokens: string[]) => {
|
|
42
42
|
if (!node || typeof node !== "object" || ArrayBuffer.isView(node) || seen.has(node)) {
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
@@ -46,16 +46,18 @@ export function registerSchemaResources<S extends object = JSONSchema, O extends
|
|
|
46
46
|
seen.add(node);
|
|
47
47
|
|
|
48
48
|
const nextScopeBase = getSchemaBasePath(scopeBase, node);
|
|
49
|
+
const resourcePointerTokens = nextScopeBase === scopeBase ? pointerTokens : [];
|
|
49
50
|
if (nextScopeBase !== scopeBase) {
|
|
50
51
|
$refs._addAlias(nextScopeBase, node as S, pathType, dynamicIdScope);
|
|
51
52
|
}
|
|
53
|
+
registerAnchorAliases($refs, nextScopeBase, resourcePointerTokens, node);
|
|
52
54
|
|
|
53
55
|
for (const key of Object.keys(node)) {
|
|
54
|
-
visit((node as Record<string, unknown>)[key], nextScopeBase);
|
|
56
|
+
visit((node as Record<string, unknown>)[key], nextScopeBase, [...resourcePointerTokens, key]);
|
|
55
57
|
}
|
|
56
58
|
};
|
|
57
59
|
|
|
58
|
-
visit(value, basePath);
|
|
60
|
+
visit(value, basePath, []);
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
function getSchemaId(value: unknown): string | undefined {
|
|
@@ -71,3 +73,37 @@ function getSchemaId(value: unknown): string | undefined {
|
|
|
71
73
|
|
|
72
74
|
return undefined;
|
|
73
75
|
}
|
|
76
|
+
|
|
77
|
+
function registerAnchorAliases<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(
|
|
78
|
+
$refs: $Refs<S, O>,
|
|
79
|
+
scopeBase: string,
|
|
80
|
+
pointerTokens: string[],
|
|
81
|
+
value: unknown,
|
|
82
|
+
) {
|
|
83
|
+
if (!value || typeof value !== "object" || ArrayBuffer.isView(value)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const resourceBase = url.stripHash(scopeBase);
|
|
88
|
+
const targetPath = pointerTokens.length > 0 ? joinPointerPath(resourceBase, pointerTokens) : `${resourceBase}#`;
|
|
89
|
+
const anchors = [
|
|
90
|
+
(value as { $anchor?: unknown }).$anchor,
|
|
91
|
+
(value as { $dynamicAnchor?: unknown }).$dynamicAnchor,
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
for (const anchor of anchors) {
|
|
95
|
+
if (typeof anchor === "string" && anchor.length > 0) {
|
|
96
|
+
$refs._addExactAlias(`${resourceBase}#${anchor}`, targetPath);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function joinPointerPath(basePath: string, tokens: string[]) {
|
|
102
|
+
let path = `${basePath}#`;
|
|
103
|
+
|
|
104
|
+
for (const token of tokens) {
|
|
105
|
+
path += `/${token.replace(/~/g, "~0").replace(/\//g, "~1")}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return path;
|
|
109
|
+
}
|