@apidevtools/json-schema-ref-parser 15.3.1 → 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 +14 -11
- package/dist/lib/dereference.js +17 -21
- package/dist/lib/index.js +3 -0
- package/dist/lib/parse.d.ts +6 -1
- package/dist/lib/parse.js +15 -1
- package/dist/lib/pointer.d.ts +4 -0
- package/dist/lib/pointer.js +30 -4
- package/dist/lib/ref.d.ts +6 -2
- package/dist/lib/ref.js +31 -2
- package/dist/lib/refs.d.ts +5 -0
- package/dist/lib/refs.js +35 -9
- package/dist/lib/resolve-external.js +22 -9
- package/dist/lib/types/index.d.ts +11 -0
- package/dist/lib/util/schema-resources.d.ts +6 -0
- package/dist/lib/util/schema-resources.js +72 -0
- package/lib/bundle.ts +49 -9
- package/lib/dereference.ts +24 -14
- package/lib/index.ts +3 -0
- package/lib/parse.ts +22 -1
- package/lib/pointer.ts +33 -4
- package/lib/ref.ts +39 -1
- package/lib/refs.ts +44 -9
- package/lib/resolve-external.ts +45 -7
- package/lib/types/index.ts +13 -0
- package/lib/util/schema-resources.ts +109 -0
- package/package.json +12 -12
package/dist/lib/bundle.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import $Ref from "./ref.js";
|
|
2
2
|
import Pointer from "./pointer.js";
|
|
3
3
|
import * as url from "./util/url.js";
|
|
4
|
+
import { getSchemaBasePath } from "./util/schema-resources.js";
|
|
4
5
|
/**
|
|
5
6
|
* Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
|
|
6
7
|
* only has *internal* references, not any *external* references.
|
|
@@ -13,7 +14,7 @@ function bundle(parser, options) {
|
|
|
13
14
|
// console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
14
15
|
// Build an inventory of all $ref pointers in the JSON Schema
|
|
15
16
|
const inventory = [];
|
|
16
|
-
crawl(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options);
|
|
17
|
+
crawl(parser, "schema", parser.$refs._root$Ref.path + "#", parser.$refs._root$Ref.path, parser.$refs._root$Ref.dynamicIdScope, "#", 0, inventory, parser.$refs, options);
|
|
17
18
|
// Get the root schema's $id (if any) for qualifying refs inside sub-schemas with their own $id
|
|
18
19
|
const rootId = parser.schema && typeof parser.schema === "object" && "$id" in parser.schema
|
|
19
20
|
? parser.schema.$id
|
|
@@ -38,13 +39,14 @@ function bundle(parser, options) {
|
|
|
38
39
|
* @param $refs
|
|
39
40
|
* @param options
|
|
40
41
|
*/
|
|
41
|
-
function crawl(parent, key, path, pathFromRoot, indirections, inventory, $refs, options) {
|
|
42
|
+
function crawl(parent, key, path, scopeBase, dynamicIdScope, pathFromRoot, indirections, inventory, $refs, options) {
|
|
42
43
|
const obj = key === null ? parent : parent[key];
|
|
43
44
|
const bundleOptions = (options.bundle || {});
|
|
44
45
|
const isExcludedPath = bundleOptions.excludedPathMatcher || (() => false);
|
|
45
46
|
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) {
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
const currentScopeBase = dynamicIdScope ? getSchemaBasePath(scopeBase, obj) : scopeBase;
|
|
48
|
+
if ($Ref.isAllowed$Ref(obj, options, dynamicIdScope)) {
|
|
49
|
+
inventory$Ref(parent, key, path, currentScopeBase, dynamicIdScope, pathFromRoot, indirections, inventory, $refs, options);
|
|
48
50
|
}
|
|
49
51
|
else {
|
|
50
52
|
// Crawl the object in a specific order that's optimized for bundling.
|
|
@@ -69,11 +71,12 @@ function crawl(parent, key, path, pathFromRoot, indirections, inventory, $refs,
|
|
|
69
71
|
const keyPath = Pointer.join(path, key);
|
|
70
72
|
const keyPathFromRoot = Pointer.join(pathFromRoot, key);
|
|
71
73
|
const value = obj[key];
|
|
72
|
-
if ($Ref.isAllowed$Ref(value)) {
|
|
73
|
-
|
|
74
|
+
if ($Ref.isAllowed$Ref(value, options, dynamicIdScope)) {
|
|
75
|
+
const valueScopeBase = dynamicIdScope ? getSchemaBasePath(currentScopeBase, value) : currentScopeBase;
|
|
76
|
+
inventory$Ref(obj, key, keyPath, valueScopeBase, dynamicIdScope, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
74
77
|
}
|
|
75
78
|
else {
|
|
76
|
-
crawl(obj, key, keyPath, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
79
|
+
crawl(obj, key, keyPath, currentScopeBase, dynamicIdScope, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
77
80
|
}
|
|
78
81
|
// We need to ensure that we have an object to work with here because we may be crawling
|
|
79
82
|
// an `examples` schema and `value` may be nullish.
|
|
@@ -99,9 +102,9 @@ function crawl(parent, key, path, pathFromRoot, indirections, inventory, $refs,
|
|
|
99
102
|
* @param $refs
|
|
100
103
|
* @param options
|
|
101
104
|
*/
|
|
102
|
-
function inventory$Ref($refParent, $refKey, path, pathFromRoot, indirections, inventory, $refs, options) {
|
|
105
|
+
function inventory$Ref($refParent, $refKey, path, scopeBase, dynamicIdScope, pathFromRoot, indirections, inventory, $refs, options) {
|
|
103
106
|
const $ref = $refKey === null ? $refParent : $refParent[$refKey];
|
|
104
|
-
const $refPath = url.resolve(path, $ref.$ref);
|
|
107
|
+
const $refPath = url.resolve(dynamicIdScope ? scopeBase : path, $ref.$ref);
|
|
105
108
|
const pointer = $refs._resolve($refPath, pathFromRoot, options);
|
|
106
109
|
if (pointer === null) {
|
|
107
110
|
return;
|
|
@@ -110,7 +113,7 @@ function inventory$Ref($refParent, $refKey, path, pathFromRoot, indirections, in
|
|
|
110
113
|
const depth = parsed.length;
|
|
111
114
|
const file = url.stripHash(pointer.path);
|
|
112
115
|
const hash = url.getHash(pointer.path);
|
|
113
|
-
const external = file !== $refs._root$Ref.path;
|
|
116
|
+
const external = file !== $refs._root$Ref.path && !$refs._aliases[file];
|
|
114
117
|
const extended = $Ref.isExtended$Ref($ref);
|
|
115
118
|
indirections += pointer.indirections;
|
|
116
119
|
const existingEntry = findInInventory(inventory, $refParent, $refKey);
|
|
@@ -139,7 +142,7 @@ function inventory$Ref($refParent, $refKey, path, pathFromRoot, indirections, in
|
|
|
139
142
|
});
|
|
140
143
|
// Recursively crawl the resolved value
|
|
141
144
|
if (!existingEntry || external) {
|
|
142
|
-
crawl(pointer.value, null, pointer.path, pathFromRoot, indirections + 1, inventory, $refs, options);
|
|
145
|
+
crawl(pointer.value, null, pointer.path, pointer.$ref.path, pointer.$ref.dynamicIdScope, pathFromRoot, indirections + 1, inventory, $refs, options);
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
/**
|
package/dist/lib/dereference.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import $Ref from "./ref.js";
|
|
2
2
|
import Pointer from "./pointer.js";
|
|
3
3
|
import * as url from "./util/url.js";
|
|
4
|
+
import { getSchemaBasePath } from "./util/schema-resources.js";
|
|
4
5
|
import { TimeoutError } from "./util/errors.js";
|
|
5
6
|
export default dereference;
|
|
6
7
|
/**
|
|
@@ -13,7 +14,7 @@ export default dereference;
|
|
|
13
14
|
function dereference(parser, options) {
|
|
14
15
|
const start = Date.now();
|
|
15
16
|
// console.log('Dereferencing $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
16
|
-
const dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path, "#", new Set(), new Set(), new Map(), parser.$refs, options, start, 0);
|
|
17
|
+
const dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path, parser.$refs._root$Ref.path, parser.$refs._root$Ref.dynamicIdScope, "#", new Set(), new Set(), new Map(), parser.$refs, options, start, 0);
|
|
17
18
|
parser.$refs.circular = dereferenced.circular;
|
|
18
19
|
parser.schema = dereferenced.value;
|
|
19
20
|
}
|
|
@@ -32,7 +33,7 @@ function dereference(parser, options) {
|
|
|
32
33
|
* @param depth - The current recursion depth
|
|
33
34
|
* @returns
|
|
34
35
|
*/
|
|
35
|
-
function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth) {
|
|
36
|
+
function crawl(obj, path, scopeBase, dynamicIdScope, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth) {
|
|
36
37
|
let dereferenced;
|
|
37
38
|
const result = {
|
|
38
39
|
value: obj,
|
|
@@ -51,8 +52,9 @@ function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedC
|
|
|
51
52
|
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) {
|
|
52
53
|
parents.add(obj);
|
|
53
54
|
processedObjects.add(obj);
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
const currentScopeBase = dynamicIdScope ? getSchemaBasePath(scopeBase, obj) : scopeBase;
|
|
56
|
+
if ($Ref.isAllowed$Ref(obj, options, dynamicIdScope)) {
|
|
57
|
+
dereferenced = dereference$Ref(obj, path, currentScopeBase, dynamicIdScope, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth);
|
|
56
58
|
result.circular = dereferenced.circular;
|
|
57
59
|
result.value = dereferenced.value;
|
|
58
60
|
}
|
|
@@ -66,8 +68,9 @@ function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedC
|
|
|
66
68
|
}
|
|
67
69
|
const value = obj[key];
|
|
68
70
|
let circular;
|
|
69
|
-
if ($Ref.isAllowed$Ref(value, options)) {
|
|
70
|
-
|
|
71
|
+
if ($Ref.isAllowed$Ref(value, options, dynamicIdScope)) {
|
|
72
|
+
const valueScopeBase = dynamicIdScope ? getSchemaBasePath(currentScopeBase, value) : currentScopeBase;
|
|
73
|
+
dereferenced = dereference$Ref(value, keyPath, valueScopeBase, dynamicIdScope, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth);
|
|
71
74
|
circular = dereferenced.circular;
|
|
72
75
|
// Avoid pointless mutations; breaks frozen objects to no profit
|
|
73
76
|
if (obj[key] !== dereferenced.value) {
|
|
@@ -104,7 +107,7 @@ function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedC
|
|
|
104
107
|
}
|
|
105
108
|
else {
|
|
106
109
|
if (!parents.has(value)) {
|
|
107
|
-
dereferenced = crawl(value, keyPath, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth + 1);
|
|
110
|
+
dereferenced = crawl(value, keyPath, currentScopeBase, dynamicIdScope, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth + 1);
|
|
108
111
|
circular = dereferenced.circular;
|
|
109
112
|
// Avoid pointless mutations; breaks frozen objects to no profit
|
|
110
113
|
if (obj[key] !== dereferenced.value) {
|
|
@@ -137,10 +140,11 @@ function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedC
|
|
|
137
140
|
* @param options
|
|
138
141
|
* @returns
|
|
139
142
|
*/
|
|
140
|
-
function dereference$Ref($ref, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth) {
|
|
143
|
+
function dereference$Ref($ref, path, scopeBase, dynamicIdScope, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth) {
|
|
141
144
|
const isExternalRef = $Ref.isExternal$Ref($ref);
|
|
142
145
|
const shouldResolveOnCwd = isExternalRef && options?.dereference?.externalReferenceResolution === "root";
|
|
143
|
-
const
|
|
146
|
+
const resolutionBase = shouldResolveOnCwd ? url.cwd() : dynamicIdScope ? scopeBase : path;
|
|
147
|
+
const $refPath = url.resolve(resolutionBase, $ref.$ref);
|
|
144
148
|
const cache = dereferencedCache.get($refPath);
|
|
145
149
|
if (cache) {
|
|
146
150
|
// If the object we found is circular we can immediately return it because it would have been
|
|
@@ -149,18 +153,10 @@ function dereference$Ref($ref, path, pathFromRoot, parents, processedObjects, de
|
|
|
149
153
|
// If the cached object however is _not_ circular and there are additional keys alongside our
|
|
150
154
|
// `$ref` pointer here we should merge them back in and return that.
|
|
151
155
|
if (!cache.circular) {
|
|
152
|
-
|
|
153
|
-
if (refKeys.length > 1) {
|
|
154
|
-
const extraKeys = {};
|
|
155
|
-
for (const key of refKeys) {
|
|
156
|
-
if (key !== "$ref" && !(key in cache.value)) {
|
|
157
|
-
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
158
|
-
extraKeys[key] = $ref[key];
|
|
159
|
-
}
|
|
160
|
-
}
|
|
156
|
+
if (Object.keys($ref).length > 1) {
|
|
161
157
|
return {
|
|
162
158
|
circular: cache.circular,
|
|
163
|
-
value:
|
|
159
|
+
value: $Ref.dereference($ref, cache.value, options, dynamicIdScope),
|
|
164
160
|
};
|
|
165
161
|
}
|
|
166
162
|
return cache;
|
|
@@ -206,11 +202,11 @@ function dereference$Ref($ref, path, pathFromRoot, parents, processedObjects, de
|
|
|
206
202
|
foundCircularReference(path, $refs, options);
|
|
207
203
|
}
|
|
208
204
|
// Dereference the JSON reference
|
|
209
|
-
let dereferencedValue = $Ref.dereference($ref, pointer.value, options);
|
|
205
|
+
let dereferencedValue = $Ref.dereference($ref, pointer.value, options, dynamicIdScope);
|
|
210
206
|
// Crawl the dereferenced value (unless it's circular)
|
|
211
207
|
if (!circular) {
|
|
212
208
|
// Determine if the dereferenced value is circular
|
|
213
|
-
const dereferenced = crawl(dereferencedValue, pointer.path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth + 1);
|
|
209
|
+
const dereferenced = crawl(dereferencedValue, pointer.path, pointer.$ref.path, pointer.$ref.dynamicIdScope, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth + 1);
|
|
214
210
|
circular = dereferenced.circular;
|
|
215
211
|
dereferencedValue = dereferenced.value;
|
|
216
212
|
}
|
package/dist/lib/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import FileResolver from "./resolvers/file.js";
|
|
|
9
9
|
import HTTPResolver from "./resolvers/http.js";
|
|
10
10
|
import { JSONParserError, InvalidPointerError, MissingPointerError, ResolverError, ParserError, UnmatchedParserError, UnmatchedResolverError, isHandledError, JSONParserErrorGroup, } from "./util/errors.js";
|
|
11
11
|
import maybe from "./util/maybe.js";
|
|
12
|
+
import { registerSchemaResources, usesDynamicIdScope } from "./util/schema-resources.js";
|
|
12
13
|
import { getJsonSchemaRefParserDefaultOptions } from "./options.js";
|
|
13
14
|
import { isUnsafeUrl } from "./util/url.js";
|
|
14
15
|
/**
|
|
@@ -68,6 +69,8 @@ export class $RefParser {
|
|
|
68
69
|
const $ref = this.$refs._add(args.path);
|
|
69
70
|
$ref.value = args.schema;
|
|
70
71
|
$ref.pathType = pathType;
|
|
72
|
+
$ref.dynamicIdScope = usesDynamicIdScope($ref.value);
|
|
73
|
+
registerSchemaResources(this.$refs, $ref.path, $ref.value, $ref.pathType, $ref.dynamicIdScope);
|
|
71
74
|
promise = Promise.resolve(args.schema);
|
|
72
75
|
}
|
|
73
76
|
else {
|
package/dist/lib/parse.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type $Refs from "./refs.js";
|
|
2
2
|
import type { ParserOptions } from "./options.js";
|
|
3
3
|
import type { JSONSchema } from "./types/index.js";
|
|
4
|
+
interface ParseTarget {
|
|
5
|
+
url: string;
|
|
6
|
+
reference?: string;
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
}
|
|
4
9
|
/**
|
|
5
10
|
* Reads and parses the specified file path or URL.
|
|
6
11
|
*/
|
|
7
|
-
declare function parse<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(
|
|
12
|
+
declare function parse<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(target: string | ParseTarget, $refs: $Refs<S, O>, options: O): Promise<string | Buffer<ArrayBufferLike> | S | undefined>;
|
|
8
13
|
export default parse;
|
package/dist/lib/parse.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import * as url from "./util/url.js";
|
|
2
|
+
import { registerSchemaResources, usesDynamicIdScope } from "./util/schema-resources.js";
|
|
2
3
|
import { filter, all, sort, run } from "./util/plugins.js";
|
|
3
4
|
import { ResolverError, ParserError, UnmatchedParserError, UnmatchedResolverError, isHandledError, } from "./util/errors.js";
|
|
4
5
|
/**
|
|
5
6
|
* Reads and parses the specified file path or URL.
|
|
6
7
|
*/
|
|
7
|
-
async function parse(
|
|
8
|
+
async function parse(target, $refs, options) {
|
|
9
|
+
let path = typeof target === "string" ? target : target.url;
|
|
10
|
+
const baseUrl = typeof target === "string" ? undefined : target.baseUrl;
|
|
11
|
+
let reference = typeof target === "string" ? undefined : target.reference;
|
|
8
12
|
// Remove the URL fragment, if any
|
|
9
13
|
const hashIndex = path.indexOf("#");
|
|
10
14
|
let hash = "";
|
|
@@ -13,6 +17,12 @@ async function parse(path, $refs, options) {
|
|
|
13
17
|
// Remove the URL fragment, if any
|
|
14
18
|
path = path.substring(0, hashIndex);
|
|
15
19
|
}
|
|
20
|
+
if (reference) {
|
|
21
|
+
const referenceHashIndex = reference.indexOf("#");
|
|
22
|
+
if (referenceHashIndex >= 0) {
|
|
23
|
+
reference = reference.substring(0, referenceHashIndex);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
16
26
|
// Add a new $Ref for this file, even though we don't have the value yet.
|
|
17
27
|
// This ensures that we don't simultaneously read & parse the same file multiple times
|
|
18
28
|
const $ref = $refs._add(path);
|
|
@@ -21,6 +31,8 @@ async function parse(path, $refs, options) {
|
|
|
21
31
|
url: path,
|
|
22
32
|
hash,
|
|
23
33
|
extension: url.getExtension(path),
|
|
34
|
+
...(reference !== undefined ? { reference } : {}),
|
|
35
|
+
...(baseUrl !== undefined ? { baseUrl } : {}),
|
|
24
36
|
};
|
|
25
37
|
// Read the file and then parse the data
|
|
26
38
|
try {
|
|
@@ -29,6 +41,8 @@ async function parse(path, $refs, options) {
|
|
|
29
41
|
file.data = resolver.result;
|
|
30
42
|
const parser = await parseFile(file, options, $refs);
|
|
31
43
|
$ref.value = parser.result;
|
|
44
|
+
$ref.dynamicIdScope = usesDynamicIdScope($ref.value);
|
|
45
|
+
registerSchemaResources($refs, $ref.path, $ref.value, $ref.pathType, $ref.dynamicIdScope);
|
|
32
46
|
return parser.result;
|
|
33
47
|
}
|
|
34
48
|
catch (err) {
|
package/dist/lib/pointer.d.ts
CHANGED
|
@@ -25,6 +25,10 @@ declare class Pointer<S extends object = JSONSchema, O extends ParserOptions<S>
|
|
|
25
25
|
* The original path or URL, used for error messages.
|
|
26
26
|
*/
|
|
27
27
|
originalPath: string;
|
|
28
|
+
/**
|
|
29
|
+
* The current base URI used to resolve nested $ref pointers while walking this pointer.
|
|
30
|
+
*/
|
|
31
|
+
scopeBase: string;
|
|
28
32
|
/**
|
|
29
33
|
* The value of the JSON pointer.
|
|
30
34
|
* Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
|
package/dist/lib/pointer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import $Ref from "./ref.js";
|
|
2
2
|
import * as url from "./util/url.js";
|
|
3
3
|
import { JSONParserError, InvalidPointerError, MissingPointerError, isHandledError } from "./util/errors.js";
|
|
4
|
+
import { getSchemaBasePath } from "./util/schema-resources.js";
|
|
4
5
|
export const nullSymbol = Symbol("null");
|
|
5
6
|
const slashes = /\//g;
|
|
6
7
|
const tildes = /~/g;
|
|
@@ -28,6 +29,10 @@ class Pointer {
|
|
|
28
29
|
* The original path or URL, used for error messages.
|
|
29
30
|
*/
|
|
30
31
|
originalPath;
|
|
32
|
+
/**
|
|
33
|
+
* The current base URI used to resolve nested $ref pointers while walking this pointer.
|
|
34
|
+
*/
|
|
35
|
+
scopeBase;
|
|
31
36
|
/**
|
|
32
37
|
* The value of the JSON pointer.
|
|
33
38
|
* Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
|
|
@@ -46,6 +51,7 @@ class Pointer {
|
|
|
46
51
|
this.$ref = $ref;
|
|
47
52
|
this.path = path;
|
|
48
53
|
this.originalPath = friendlyPath || path;
|
|
54
|
+
this.scopeBase = $ref.path || url.stripHash(path);
|
|
49
55
|
this.value = undefined;
|
|
50
56
|
this.circular = false;
|
|
51
57
|
this.indirections = 0;
|
|
@@ -68,6 +74,9 @@ class Pointer {
|
|
|
68
74
|
const found = [];
|
|
69
75
|
// Crawl the object, one token at a time
|
|
70
76
|
this.value = unwrapOrThrow(obj);
|
|
77
|
+
if (this.$ref.dynamicIdScope) {
|
|
78
|
+
this.scopeBase = getSchemaBasePath(this.scopeBase, this.value);
|
|
79
|
+
}
|
|
71
80
|
for (let i = 0; i < tokens.length; i++) {
|
|
72
81
|
// During token walking, if the current value is an extended $ref (has sibling keys
|
|
73
82
|
// alongside $ref, as allowed by JSON Schema 2019-09+), and resolveIf$Ref marks it
|
|
@@ -123,9 +132,13 @@ class Pointer {
|
|
|
123
132
|
this.value = this.value[token];
|
|
124
133
|
}
|
|
125
134
|
found.push(token);
|
|
135
|
+
if (this.$ref.dynamicIdScope) {
|
|
136
|
+
this.scopeBase = getSchemaBasePath(this.scopeBase, this.value);
|
|
137
|
+
}
|
|
126
138
|
}
|
|
127
139
|
// Resolve the final value
|
|
128
|
-
|
|
140
|
+
const finalResolutionBase = this.$ref.dynamicIdScope ? this.scopeBase : this.path;
|
|
141
|
+
if (!this.value || (this.value.$ref && url.resolve(finalResolutionBase, this.value.$ref) !== pathFromRoot)) {
|
|
129
142
|
resolveIf$Ref(this, options, pathFromRoot);
|
|
130
143
|
}
|
|
131
144
|
return this;
|
|
@@ -150,6 +163,9 @@ class Pointer {
|
|
|
150
163
|
}
|
|
151
164
|
// Crawl the object, one token at a time
|
|
152
165
|
this.value = unwrapOrThrow(obj);
|
|
166
|
+
if (this.$ref.dynamicIdScope) {
|
|
167
|
+
this.scopeBase = getSchemaBasePath(this.scopeBase, this.value);
|
|
168
|
+
}
|
|
153
169
|
for (let i = 0; i < tokens.length - 1; i++) {
|
|
154
170
|
resolveIf$Ref(this, options);
|
|
155
171
|
token = tokens[i];
|
|
@@ -161,6 +177,9 @@ class Pointer {
|
|
|
161
177
|
// The token doesn't exist, so create it
|
|
162
178
|
this.value = setValue(this, token, {});
|
|
163
179
|
}
|
|
180
|
+
if (this.$ref.dynamicIdScope) {
|
|
181
|
+
this.scopeBase = getSchemaBasePath(this.scopeBase, this.value);
|
|
182
|
+
}
|
|
164
183
|
}
|
|
165
184
|
// Set the value of the final token
|
|
166
185
|
resolveIf$Ref(this, options);
|
|
@@ -237,8 +256,9 @@ class Pointer {
|
|
|
237
256
|
*/
|
|
238
257
|
function resolveIf$Ref(pointer, options, pathFromRoot) {
|
|
239
258
|
// Is the value a JSON reference? (and allowed?)
|
|
240
|
-
if ($Ref.isAllowed$Ref(pointer.value, options)) {
|
|
241
|
-
const
|
|
259
|
+
if ($Ref.isAllowed$Ref(pointer.value, options, pointer.$ref.dynamicIdScope)) {
|
|
260
|
+
const resolutionBase = pointer.$ref.dynamicIdScope ? pointer.scopeBase : pointer.path;
|
|
261
|
+
const $refPath = url.resolve(resolutionBase, pointer.value.$ref);
|
|
242
262
|
if ($refPath === pointer.path && !isRootPath(pathFromRoot)) {
|
|
243
263
|
// The value is a reference to itself, so there's nothing to do.
|
|
244
264
|
pointer.circular = true;
|
|
@@ -252,7 +272,10 @@ function resolveIf$Ref(pointer, options, pathFromRoot) {
|
|
|
252
272
|
if ($Ref.isExtended$Ref(pointer.value)) {
|
|
253
273
|
// This JSON reference "extends" the resolved value, rather than simply pointing to it.
|
|
254
274
|
// So the resolved path does NOT change. Just the value does.
|
|
255
|
-
pointer.value = $Ref.dereference(pointer.value, resolved.value, options);
|
|
275
|
+
pointer.value = $Ref.dereference(pointer.value, resolved.value, options, pointer.$ref.dynamicIdScope);
|
|
276
|
+
if (pointer.$ref.dynamicIdScope) {
|
|
277
|
+
pointer.scopeBase = getSchemaBasePath(pointer.scopeBase, pointer.value);
|
|
278
|
+
}
|
|
256
279
|
return false;
|
|
257
280
|
}
|
|
258
281
|
else {
|
|
@@ -260,6 +283,9 @@ function resolveIf$Ref(pointer, options, pathFromRoot) {
|
|
|
260
283
|
pointer.$ref = resolved.$ref;
|
|
261
284
|
pointer.path = resolved.path;
|
|
262
285
|
pointer.value = resolved.value;
|
|
286
|
+
pointer.scopeBase = pointer.$ref.dynamicIdScope
|
|
287
|
+
? getSchemaBasePath(pointer.$ref.path, pointer.value)
|
|
288
|
+
: pointer.$ref.path;
|
|
263
289
|
}
|
|
264
290
|
return true;
|
|
265
291
|
}
|
package/dist/lib/ref.d.ts
CHANGED
|
@@ -39,6 +39,10 @@ declare class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = P
|
|
|
39
39
|
* Indicates the type of {@link $Ref#path} (e.g. "file", "http", etc.)
|
|
40
40
|
*/
|
|
41
41
|
pathType: string | unknown;
|
|
42
|
+
/**
|
|
43
|
+
* Whether this document/resource should use JSON Schema 2019-09+ nested $id scope semantics.
|
|
44
|
+
*/
|
|
45
|
+
dynamicIdScope: boolean;
|
|
42
46
|
/**
|
|
43
47
|
* List of all errors. Undefined if no errors.
|
|
44
48
|
*/
|
|
@@ -110,7 +114,7 @@ declare class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = P
|
|
|
110
114
|
* @param options
|
|
111
115
|
* @returns
|
|
112
116
|
*/
|
|
113
|
-
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;
|
|
114
118
|
/**
|
|
115
119
|
* Determines whether the given value is a JSON reference that "extends" its resolved value.
|
|
116
120
|
* That is, it has extra properties (in addition to "$ref"), so rather than simply pointing to
|
|
@@ -178,6 +182,6 @@ declare class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = P
|
|
|
178
182
|
* @param options - The options
|
|
179
183
|
* @returns - Returns the dereferenced value
|
|
180
184
|
*/
|
|
181
|
-
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;
|
|
182
186
|
}
|
|
183
187
|
export default $Ref;
|
package/dist/lib/ref.js
CHANGED
|
@@ -35,6 +35,10 @@ class $Ref {
|
|
|
35
35
|
* Indicates the type of {@link $Ref#path} (e.g. "file", "http", etc.)
|
|
36
36
|
*/
|
|
37
37
|
pathType;
|
|
38
|
+
/**
|
|
39
|
+
* Whether this document/resource should use JSON Schema 2019-09+ nested $id scope semantics.
|
|
40
|
+
*/
|
|
41
|
+
dynamicIdScope = false;
|
|
38
42
|
/**
|
|
39
43
|
* List of all errors. Undefined if no errors.
|
|
40
44
|
*/
|
|
@@ -166,12 +170,16 @@ class $Ref {
|
|
|
166
170
|
* @param options
|
|
167
171
|
* @returns
|
|
168
172
|
*/
|
|
169
|
-
static isAllowed$Ref(value, options) {
|
|
173
|
+
static isAllowed$Ref(value, options, allowPlainNameFragments = false) {
|
|
170
174
|
if (this.is$Ref(value)) {
|
|
171
175
|
if (value.$ref.substring(0, 2) === "#/" || value.$ref === "#") {
|
|
172
176
|
// It's a JSON Pointer reference, which is always allowed
|
|
173
177
|
return true;
|
|
174
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
|
+
}
|
|
175
183
|
else if (value.$ref[0] !== "#" && (!options || options.resolve?.external)) {
|
|
176
184
|
// It's an external reference, which is allowed by the options
|
|
177
185
|
return true;
|
|
@@ -248,7 +256,10 @@ class $Ref {
|
|
|
248
256
|
* @param options - The options
|
|
249
257
|
* @returns - Returns the dereferenced value
|
|
250
258
|
*/
|
|
251
|
-
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
|
+
}
|
|
252
263
|
if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) {
|
|
253
264
|
const merged = {};
|
|
254
265
|
for (const key of Object.keys($ref)) {
|
|
@@ -283,6 +294,24 @@ class $Ref {
|
|
|
283
294
|
}
|
|
284
295
|
}
|
|
285
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
|
+
}
|
|
286
315
|
function deepMerge(target, source) {
|
|
287
316
|
//return {...target, ...source};
|
|
288
317
|
// If either isn't an object, just return source (overwrite)
|
package/dist/lib/refs.d.ts
CHANGED
|
@@ -79,6 +79,8 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
79
79
|
* @param path - The file path or URL of the referenced file
|
|
80
80
|
*/
|
|
81
81
|
_add(path: string): $Ref<S, O>;
|
|
82
|
+
_addAlias(path: string, value: S, pathType?: string | unknown, dynamicIdScope?: boolean): $Ref<S, O>;
|
|
83
|
+
_addExactAlias(path: string, targetPath: string): string;
|
|
82
84
|
/**
|
|
83
85
|
* Resolves the given JSON reference.
|
|
84
86
|
*
|
|
@@ -96,6 +98,8 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
96
98
|
* @protected
|
|
97
99
|
*/
|
|
98
100
|
_$refs: $RefsMap<S, O>;
|
|
101
|
+
_aliases: $RefsMap<S, O>;
|
|
102
|
+
_exactAliases: Record<string, string>;
|
|
99
103
|
/**
|
|
100
104
|
* The {@link $Ref} object that is the root of the JSON schema.
|
|
101
105
|
*
|
|
@@ -123,5 +127,6 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
123
127
|
* @returns {object}
|
|
124
128
|
*/
|
|
125
129
|
toJSON: (...types: (string | string[])[]) => S;
|
|
130
|
+
private _getRef;
|
|
126
131
|
}
|
|
127
132
|
export {};
|
package/dist/lib/refs.js
CHANGED
|
@@ -84,10 +84,9 @@ export default class $Refs {
|
|
|
84
84
|
*/
|
|
85
85
|
set(path, value) {
|
|
86
86
|
const absPath = url.resolve(this._root$Ref.path, path);
|
|
87
|
-
const
|
|
88
|
-
const $ref = this._$refs[withoutHash];
|
|
87
|
+
const $ref = this._getRef(absPath);
|
|
89
88
|
if (!$ref) {
|
|
90
|
-
throw new Error(`Error resolving $ref pointer "${path}". \n"${
|
|
89
|
+
throw new Error(`Error resolving $ref pointer "${path}". \n"${url.stripHash(absPath)}" not found.`);
|
|
91
90
|
}
|
|
92
91
|
$ref.set(absPath, value);
|
|
93
92
|
}
|
|
@@ -100,8 +99,7 @@ export default class $Refs {
|
|
|
100
99
|
*/
|
|
101
100
|
_get$Ref(path) {
|
|
102
101
|
path = url.resolve(this._root$Ref.path, path);
|
|
103
|
-
|
|
104
|
-
return this._$refs[withoutHash];
|
|
102
|
+
return this._getRef(this._exactAliases[path] || path);
|
|
105
103
|
}
|
|
106
104
|
/**
|
|
107
105
|
* Creates a new {@link $Ref} object and adds it to this {@link $Refs} object.
|
|
@@ -116,6 +114,26 @@ export default class $Refs {
|
|
|
116
114
|
this._root$Ref = this._root$Ref || $ref;
|
|
117
115
|
return $ref;
|
|
118
116
|
}
|
|
117
|
+
_addAlias(path, value, pathType, dynamicIdScope = false) {
|
|
118
|
+
const withoutHash = url.stripHash(path);
|
|
119
|
+
if (!withoutHash || this._$refs[withoutHash] || this._aliases[withoutHash]) {
|
|
120
|
+
return this._$refs[withoutHash] || this._aliases[withoutHash];
|
|
121
|
+
}
|
|
122
|
+
const $ref = new $Ref(this);
|
|
123
|
+
$ref.path = withoutHash;
|
|
124
|
+
$ref.pathType = pathType;
|
|
125
|
+
$ref.value = value;
|
|
126
|
+
$ref.dynamicIdScope = dynamicIdScope;
|
|
127
|
+
this._aliases[withoutHash] = $ref;
|
|
128
|
+
return $ref;
|
|
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
|
+
}
|
|
119
137
|
/**
|
|
120
138
|
* Resolves the given JSON reference.
|
|
121
139
|
*
|
|
@@ -127,12 +145,12 @@ export default class $Refs {
|
|
|
127
145
|
*/
|
|
128
146
|
_resolve(path, pathFromRoot, options) {
|
|
129
147
|
const absPath = url.resolve(this._root$Ref.path, path);
|
|
130
|
-
const
|
|
131
|
-
const $ref = this.
|
|
148
|
+
const canonicalPath = this._exactAliases[absPath] || absPath;
|
|
149
|
+
const $ref = this._getRef(canonicalPath);
|
|
132
150
|
if (!$ref) {
|
|
133
|
-
throw new Error(`Error resolving $ref pointer "${path}". \n"${
|
|
151
|
+
throw new Error(`Error resolving $ref pointer "${path}". \n"${url.stripHash(absPath)}" not found.`);
|
|
134
152
|
}
|
|
135
|
-
return $ref.resolve(
|
|
153
|
+
return $ref.resolve(canonicalPath, options, path, pathFromRoot);
|
|
136
154
|
}
|
|
137
155
|
/**
|
|
138
156
|
* A map of paths/urls to {@link $Ref} objects
|
|
@@ -141,6 +159,8 @@ export default class $Refs {
|
|
|
141
159
|
* @protected
|
|
142
160
|
*/
|
|
143
161
|
_$refs = {};
|
|
162
|
+
_aliases = {};
|
|
163
|
+
_exactAliases = {};
|
|
144
164
|
/**
|
|
145
165
|
* The {@link $Ref} object that is the root of the JSON schema.
|
|
146
166
|
*
|
|
@@ -156,6 +176,8 @@ export default class $Refs {
|
|
|
156
176
|
*/
|
|
157
177
|
this.circular = false;
|
|
158
178
|
this._$refs = {};
|
|
179
|
+
this._aliases = {};
|
|
180
|
+
this._exactAliases = {};
|
|
159
181
|
// @ts-ignore
|
|
160
182
|
this._root$Ref = null;
|
|
161
183
|
}
|
|
@@ -178,6 +200,10 @@ export default class $Refs {
|
|
|
178
200
|
* @returns {object}
|
|
179
201
|
*/
|
|
180
202
|
toJSON = this.values;
|
|
203
|
+
_getRef(path) {
|
|
204
|
+
const withoutHash = url.stripHash(path);
|
|
205
|
+
return this._$refs[withoutHash] || this._aliases[withoutHash];
|
|
206
|
+
}
|
|
181
207
|
}
|
|
182
208
|
/**
|
|
183
209
|
* Returns the encoded and decoded paths keys of the given object.
|