@apidevtools/json-schema-ref-parser 14.2.0 → 15.0.0
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/README.md +0 -4
- package/dist/lib/bundle.d.ts +3 -3
- package/dist/lib/bundle.js +16 -54
- package/dist/lib/dereference.d.ts +1 -2
- package/dist/lib/dereference.js +12 -50
- package/dist/lib/index.js +42 -95
- package/dist/lib/normalize-args.d.ts +1 -1
- package/dist/lib/normalize-args.js +4 -7
- package/dist/lib/options.d.ts +6 -0
- package/dist/lib/options.js +16 -23
- package/dist/lib/parse.js +11 -46
- package/dist/lib/parsers/binary.js +1 -3
- package/dist/lib/parsers/json.js +4 -6
- package/dist/lib/parsers/text.js +3 -5
- package/dist/lib/parsers/yaml.js +12 -11
- package/dist/lib/pointer.d.ts +3 -2
- package/dist/lib/pointer.js +18 -63
- package/dist/lib/ref.d.ts +5 -3
- package/dist/lib/ref.js +60 -52
- package/dist/lib/refs.d.ts +1 -1
- package/dist/lib/refs.js +7 -46
- package/dist/lib/resolve-external.js +10 -48
- package/dist/lib/resolvers/file.js +7 -45
- package/dist/lib/resolvers/http.js +5 -40
- package/dist/lib/types/index.d.ts +1 -1
- package/dist/lib/types/index.js +1 -2
- package/dist/lib/util/convert-path-to-posix.js +3 -9
- package/dist/lib/util/errors.d.ts +1 -1
- package/dist/lib/util/errors.js +17 -33
- package/dist/lib/util/is-windows.js +1 -5
- package/dist/lib/util/maybe.js +4 -10
- package/dist/lib/util/next.js +1 -3
- package/dist/lib/util/plugins.js +4 -10
- package/dist/lib/util/url.d.ts +1 -1
- package/dist/lib/util/url.js +40 -88
- package/lib/bundle.ts +12 -9
- package/lib/dereference.ts +3 -4
- package/lib/normalize-args.ts +1 -1
- package/lib/options.ts +8 -0
- package/lib/parsers/yaml.ts +6 -1
- package/lib/pointer.ts +19 -20
- package/lib/ref.ts +55 -5
- package/lib/refs.ts +2 -2
- package/lib/types/index.ts +1 -1
- package/lib/util/errors.ts +2 -2
- package/lib/util/url.ts +13 -7
- package/package.json +21 -20
package/dist/lib/util/url.js
CHANGED
|
@@ -1,64 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.parse = void 0;
|
|
40
|
-
exports.resolve = resolve;
|
|
41
|
-
exports.cwd = cwd;
|
|
42
|
-
exports.getProtocol = getProtocol;
|
|
43
|
-
exports.getExtension = getExtension;
|
|
44
|
-
exports.stripQuery = stripQuery;
|
|
45
|
-
exports.getHash = getHash;
|
|
46
|
-
exports.stripHash = stripHash;
|
|
47
|
-
exports.isHttp = isHttp;
|
|
48
|
-
exports.isUnsafeUrl = isUnsafeUrl;
|
|
49
|
-
exports.isFileSystemPath = isFileSystemPath;
|
|
50
|
-
exports.fromFileSystemPath = fromFileSystemPath;
|
|
51
|
-
exports.toFileSystemPath = toFileSystemPath;
|
|
52
|
-
exports.safePointerToPath = safePointerToPath;
|
|
53
|
-
exports.relative = relative;
|
|
54
|
-
const convert_path_to_posix_1 = __importDefault(require("./convert-path-to-posix"));
|
|
55
|
-
const path_1 = __importStar(require("path"));
|
|
1
|
+
import convertPathToPosix from "./convert-path-to-posix.js";
|
|
2
|
+
import path, { win32 } from "path";
|
|
56
3
|
const forwardSlashPattern = /\//g;
|
|
57
4
|
const protocolPattern = /^(\w{2,}):\/\//i;
|
|
58
5
|
const jsonPointerSlash = /~1/g;
|
|
59
6
|
const jsonPointerTilde = /~0/g;
|
|
60
|
-
|
|
61
|
-
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { isWindows } from "./is-windows.js";
|
|
62
9
|
// RegExp patterns to URL-encode special characters in local filesystem paths
|
|
63
10
|
const urlEncodePatterns = [
|
|
64
11
|
[/\?/g, "%3F"],
|
|
@@ -66,31 +13,36 @@ const urlEncodePatterns = [
|
|
|
66
13
|
];
|
|
67
14
|
// RegExp patterns to URL-decode special characters for local filesystem paths
|
|
68
15
|
const urlDecodePatterns = [/%23/g, "#", /%24/g, "$", /%26/g, "&", /%2C/g, ",", /%40/g, "@"];
|
|
69
|
-
const parse = (u) => new URL(u);
|
|
70
|
-
exports.parse = parse;
|
|
16
|
+
export const parse = (u) => new URL(u);
|
|
71
17
|
/**
|
|
72
18
|
* Returns resolved target URL relative to a base URL in a manner similar to that of a Web browser resolving an anchor tag HREF.
|
|
73
19
|
*
|
|
74
20
|
* @returns
|
|
75
21
|
*/
|
|
76
|
-
function resolve(from, to) {
|
|
22
|
+
export function resolve(from, to) {
|
|
77
23
|
// we use a non-existent URL to check if its a relative URL
|
|
78
|
-
const fromUrl = new URL((
|
|
79
|
-
const resolvedUrl = new URL((
|
|
24
|
+
const fromUrl = new URL(convertPathToPosix(from), "https://aaa.nonexistanturl.com");
|
|
25
|
+
const resolvedUrl = new URL(convertPathToPosix(to), fromUrl);
|
|
80
26
|
const endSpaces = to.match(/(\s*)$/)?.[1] || "";
|
|
81
27
|
if (resolvedUrl.hostname === "aaa.nonexistanturl.com") {
|
|
82
28
|
// `from` is a relative URL.
|
|
83
29
|
const { pathname, search, hash } = resolvedUrl;
|
|
84
|
-
return pathname + search + hash + endSpaces;
|
|
30
|
+
return pathname + search + decodeURIComponent(hash) + endSpaces;
|
|
31
|
+
}
|
|
32
|
+
const resolved = resolvedUrl.toString() + endSpaces;
|
|
33
|
+
// if there is a #, we want to split on the first one only, and decode the part after
|
|
34
|
+
if (resolved.includes("#")) {
|
|
35
|
+
const [base, hash] = resolved.split("#", 2);
|
|
36
|
+
return base + "#" + decodeURIComponent(hash || "");
|
|
85
37
|
}
|
|
86
|
-
return
|
|
38
|
+
return resolved;
|
|
87
39
|
}
|
|
88
40
|
/**
|
|
89
41
|
* Returns the current working directory (in Node) or the current page URL (in browsers).
|
|
90
42
|
*
|
|
91
43
|
* @returns
|
|
92
44
|
*/
|
|
93
|
-
function cwd() {
|
|
45
|
+
export function cwd() {
|
|
94
46
|
if (typeof window !== "undefined" && window.location && window.location.href) {
|
|
95
47
|
const href = window.location.href;
|
|
96
48
|
if (!href || !href.startsWith("http")) {
|
|
@@ -123,7 +75,7 @@ function cwd() {
|
|
|
123
75
|
* @param path
|
|
124
76
|
* @returns
|
|
125
77
|
*/
|
|
126
|
-
function getProtocol(path) {
|
|
78
|
+
export function getProtocol(path) {
|
|
127
79
|
const match = protocolPattern.exec(path || "");
|
|
128
80
|
if (match) {
|
|
129
81
|
return match[1].toLowerCase();
|
|
@@ -137,7 +89,7 @@ function getProtocol(path) {
|
|
|
137
89
|
* @param path
|
|
138
90
|
* @returns
|
|
139
91
|
*/
|
|
140
|
-
function getExtension(path) {
|
|
92
|
+
export function getExtension(path) {
|
|
141
93
|
const lastDot = path.lastIndexOf(".");
|
|
142
94
|
if (lastDot >= 0) {
|
|
143
95
|
return stripQuery(path.substring(lastDot).toLowerCase());
|
|
@@ -150,7 +102,7 @@ function getExtension(path) {
|
|
|
150
102
|
* @param path
|
|
151
103
|
* @returns
|
|
152
104
|
*/
|
|
153
|
-
function stripQuery(path) {
|
|
105
|
+
export function stripQuery(path) {
|
|
154
106
|
const queryIndex = path.indexOf("?");
|
|
155
107
|
if (queryIndex >= 0) {
|
|
156
108
|
path = path.substring(0, queryIndex);
|
|
@@ -164,7 +116,7 @@ function stripQuery(path) {
|
|
|
164
116
|
* @param path
|
|
165
117
|
* @returns
|
|
166
118
|
*/
|
|
167
|
-
function getHash(path) {
|
|
119
|
+
export function getHash(path) {
|
|
168
120
|
if (!path) {
|
|
169
121
|
return "#";
|
|
170
122
|
}
|
|
@@ -180,7 +132,7 @@ function getHash(path) {
|
|
|
180
132
|
* @param path
|
|
181
133
|
* @returns
|
|
182
134
|
*/
|
|
183
|
-
function stripHash(path) {
|
|
135
|
+
export function stripHash(path) {
|
|
184
136
|
if (!path) {
|
|
185
137
|
return "";
|
|
186
138
|
}
|
|
@@ -196,7 +148,7 @@ function stripHash(path) {
|
|
|
196
148
|
* @param path
|
|
197
149
|
* @returns
|
|
198
150
|
*/
|
|
199
|
-
function isHttp(path) {
|
|
151
|
+
export function isHttp(path) {
|
|
200
152
|
const protocol = getProtocol(path);
|
|
201
153
|
if (protocol === "http" || protocol === "https") {
|
|
202
154
|
return true;
|
|
@@ -216,7 +168,7 @@ function isHttp(path) {
|
|
|
216
168
|
* @param path - The URL or path to check
|
|
217
169
|
* @returns true if the URL is unsafe/internal, false otherwise
|
|
218
170
|
*/
|
|
219
|
-
function isUnsafeUrl(path) {
|
|
171
|
+
export function isUnsafeUrl(path) {
|
|
220
172
|
if (!path || typeof path !== "string") {
|
|
221
173
|
return true;
|
|
222
174
|
}
|
|
@@ -363,7 +315,7 @@ function isInternalPort(port) {
|
|
|
363
315
|
* @param path
|
|
364
316
|
* @returns
|
|
365
317
|
*/
|
|
366
|
-
function isFileSystemPath(path) {
|
|
318
|
+
export function isFileSystemPath(path) {
|
|
367
319
|
// @ts-ignore
|
|
368
320
|
if (typeof window !== "undefined" || (typeof process !== "undefined" && process.browser)) {
|
|
369
321
|
// We're running in a browser, so assume that all paths are URLs.
|
|
@@ -389,24 +341,24 @@ function isFileSystemPath(path) {
|
|
|
389
341
|
* @param path
|
|
390
342
|
* @returns
|
|
391
343
|
*/
|
|
392
|
-
function fromFileSystemPath(path) {
|
|
344
|
+
export function fromFileSystemPath(path) {
|
|
393
345
|
// Step 1: On Windows, replace backslashes with forward slashes,
|
|
394
346
|
// rather than encoding them as "%5C"
|
|
395
|
-
if (
|
|
347
|
+
if (isWindows()) {
|
|
396
348
|
const projectDir = cwd();
|
|
397
349
|
const upperPath = path.toUpperCase();
|
|
398
|
-
const projectDirPosixPath = (
|
|
350
|
+
const projectDirPosixPath = convertPathToPosix(projectDir);
|
|
399
351
|
const posixUpper = projectDirPosixPath.toUpperCase();
|
|
400
352
|
const hasProjectDir = upperPath.includes(posixUpper);
|
|
401
353
|
const hasProjectUri = upperPath.includes(posixUpper);
|
|
402
|
-
const isAbsolutePath =
|
|
354
|
+
const isAbsolutePath = win32?.isAbsolute(path) ||
|
|
403
355
|
path.startsWith("http://") ||
|
|
404
356
|
path.startsWith("https://") ||
|
|
405
357
|
path.startsWith("file://");
|
|
406
358
|
if (!(hasProjectDir || hasProjectUri || isAbsolutePath) && !projectDir.startsWith("http")) {
|
|
407
|
-
path =
|
|
359
|
+
path = join(projectDir, path);
|
|
408
360
|
}
|
|
409
|
-
path = (
|
|
361
|
+
path = convertPathToPosix(path);
|
|
410
362
|
}
|
|
411
363
|
// Step 2: `encodeURI` will take care of MOST characters
|
|
412
364
|
path = encodeURI(path);
|
|
@@ -421,7 +373,7 @@ function fromFileSystemPath(path) {
|
|
|
421
373
|
/**
|
|
422
374
|
* Converts a URL to a local filesystem path.
|
|
423
375
|
*/
|
|
424
|
-
function toFileSystemPath(path, keepFileProtocol) {
|
|
376
|
+
export function toFileSystemPath(path, keepFileProtocol) {
|
|
425
377
|
// Step 1: `decodeURI` will decode characters such as Cyrillic characters, spaces, etc.
|
|
426
378
|
path = decodeURI(path);
|
|
427
379
|
// Step 2: Manually decode characters that are not decoded by `decodeURI`.
|
|
@@ -437,7 +389,7 @@ function toFileSystemPath(path, keepFileProtocol) {
|
|
|
437
389
|
// Strip-off the protocol, and the initial "/", if there is one
|
|
438
390
|
path = path.replace(/^file:\/\//, "").replace(/^\//, "");
|
|
439
391
|
// insert a colon (":") after the drive letter on Windows
|
|
440
|
-
if (
|
|
392
|
+
if (isWindows() && path[1] === "/") {
|
|
441
393
|
path = `${path[0]}:${path.substring(1)}`;
|
|
442
394
|
}
|
|
443
395
|
if (keepFileProtocol) {
|
|
@@ -449,11 +401,11 @@ function toFileSystemPath(path, keepFileProtocol) {
|
|
|
449
401
|
// On Windows, it will start with something like "C:/".
|
|
450
402
|
// On Posix, it will start with "/"
|
|
451
403
|
isFileUrl = false;
|
|
452
|
-
path =
|
|
404
|
+
path = isWindows() ? path : "/" + path;
|
|
453
405
|
}
|
|
454
406
|
}
|
|
455
407
|
// Step 4: Normalize Windows paths (unless it's a "file://" URL)
|
|
456
|
-
if (
|
|
408
|
+
if (isWindows() && !isFileUrl) {
|
|
457
409
|
// Replace forward slashes with backslashes
|
|
458
410
|
path = path.replace(forwardSlashPattern, "\\");
|
|
459
411
|
// Capitalize the drive letter
|
|
@@ -469,7 +421,7 @@ function toFileSystemPath(path, keepFileProtocol) {
|
|
|
469
421
|
* @param pointer
|
|
470
422
|
* @returns
|
|
471
423
|
*/
|
|
472
|
-
function safePointerToPath(pointer) {
|
|
424
|
+
export function safePointerToPath(pointer) {
|
|
473
425
|
if (pointer.length <= 1 || pointer[0] !== "#" || pointer[1] !== "/") {
|
|
474
426
|
return [];
|
|
475
427
|
}
|
|
@@ -477,15 +429,15 @@ function safePointerToPath(pointer) {
|
|
|
477
429
|
.slice(2)
|
|
478
430
|
.split("/")
|
|
479
431
|
.map((value) => {
|
|
480
|
-
return
|
|
432
|
+
return value.replace(jsonPointerSlash, "/").replace(jsonPointerTilde, "~");
|
|
481
433
|
});
|
|
482
434
|
}
|
|
483
|
-
function relative(from, to) {
|
|
435
|
+
export function relative(from, to) {
|
|
484
436
|
if (!isFileSystemPath(from) || !isFileSystemPath(to)) {
|
|
485
437
|
return resolve(from, to);
|
|
486
438
|
}
|
|
487
|
-
const fromDir =
|
|
439
|
+
const fromDir = path.dirname(stripHash(from));
|
|
488
440
|
const toPath = stripHash(to);
|
|
489
|
-
const result =
|
|
441
|
+
const result = path.relative(fromDir, toPath);
|
|
490
442
|
return result + getHash(to);
|
|
491
443
|
}
|
package/lib/bundle.ts
CHANGED
|
@@ -2,10 +2,10 @@ import $Ref from "./ref.js";
|
|
|
2
2
|
import Pointer from "./pointer.js";
|
|
3
3
|
import * as url from "./util/url.js";
|
|
4
4
|
import type $Refs from "./refs.js";
|
|
5
|
-
import type $RefParser from "./index";
|
|
6
|
-
import type { ParserOptions } from "./index";
|
|
7
|
-
import type { JSONSchema } from "./index";
|
|
8
|
-
import type { BundleOptions } from "./options";
|
|
5
|
+
import type $RefParser from "./index.js";
|
|
6
|
+
import type { ParserOptions } from "./index.js";
|
|
7
|
+
import type { JSONSchema } from "./index.js";
|
|
8
|
+
import type { BundleOptions } from "./options.js";
|
|
9
9
|
|
|
10
10
|
export interface InventoryEntry {
|
|
11
11
|
$ref: any;
|
|
@@ -40,7 +40,7 @@ function bundle<S extends object = JSONSchema, O extends ParserOptions<S> = Pars
|
|
|
40
40
|
crawl<S, O>(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options);
|
|
41
41
|
|
|
42
42
|
// Remap all $ref pointers
|
|
43
|
-
remap(inventory);
|
|
43
|
+
remap<S, O>(inventory, options);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -203,7 +203,10 @@ function inventory$Ref<S extends object = JSONSchema, O extends ParserOptions<S>
|
|
|
203
203
|
*
|
|
204
204
|
* @param inventory
|
|
205
205
|
*/
|
|
206
|
-
function remap
|
|
206
|
+
function remap<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(
|
|
207
|
+
inventory: InventoryEntry[],
|
|
208
|
+
options: O,
|
|
209
|
+
) {
|
|
207
210
|
// Group & sort all the $ref pointers, so they're in the order that we need to dereference/remap them
|
|
208
211
|
inventory.sort((a: InventoryEntry, b: InventoryEntry) => {
|
|
209
212
|
if (a.file !== b.file) {
|
|
@@ -254,10 +257,10 @@ function remap(inventory: InventoryEntry[]) {
|
|
|
254
257
|
// This $ref already resolves to the main JSON Schema file
|
|
255
258
|
entry.$ref.$ref = entry.hash;
|
|
256
259
|
} else if (entry.file === file && entry.hash === hash) {
|
|
257
|
-
// This $ref points to the same value as the
|
|
260
|
+
// This $ref points to the same value as the previous $ref, so remap it to the same path
|
|
258
261
|
entry.$ref.$ref = pathFromRoot;
|
|
259
262
|
} else if (entry.file === file && entry.hash.indexOf(hash + "/") === 0) {
|
|
260
|
-
// This $ref points to a sub-value of the
|
|
263
|
+
// This $ref points to a sub-value of the previous $ref, so remap it beneath that path
|
|
261
264
|
entry.$ref.$ref = Pointer.join(pathFromRoot, Pointer.parse(entry.hash.replace(hash, "#")));
|
|
262
265
|
} else {
|
|
263
266
|
// We've moved to a new file or new hash
|
|
@@ -267,7 +270,7 @@ function remap(inventory: InventoryEntry[]) {
|
|
|
267
270
|
|
|
268
271
|
// This is the first $ref to point to this value, so dereference the value.
|
|
269
272
|
// Any other $refs that point to the same value will point to this $ref instead
|
|
270
|
-
entry.$ref = entry.parent[entry.key] = $Ref.dereference(entry.$ref, entry.value);
|
|
273
|
+
entry.$ref = entry.parent[entry.key] = $Ref.dereference(entry.$ref, entry.value, options);
|
|
271
274
|
|
|
272
275
|
if (entry.circular) {
|
|
273
276
|
// This $ref points to itself
|
package/lib/dereference.ts
CHANGED
|
@@ -3,9 +3,8 @@ import Pointer from "./pointer.js";
|
|
|
3
3
|
import * as url from "./util/url.js";
|
|
4
4
|
import type $Refs from "./refs.js";
|
|
5
5
|
import type { DereferenceOptions, ParserOptions } from "./options.js";
|
|
6
|
-
import type
|
|
7
|
-
import
|
|
8
|
-
import { TimeoutError } from "./util/errors";
|
|
6
|
+
import { type $RefParser, type JSONSchema } from "./index.js";
|
|
7
|
+
import { TimeoutError } from "./util/errors.js";
|
|
9
8
|
|
|
10
9
|
export default dereference;
|
|
11
10
|
|
|
@@ -278,7 +277,7 @@ function dereference$Ref<S extends object = JSONSchema, O extends ParserOptions<
|
|
|
278
277
|
}
|
|
279
278
|
|
|
280
279
|
// Dereference the JSON reference
|
|
281
|
-
let dereferencedValue = $Ref.dereference($ref, pointer.value);
|
|
280
|
+
let dereferencedValue = $Ref.dereference($ref, pointer.value, options);
|
|
282
281
|
|
|
283
282
|
// Crawl the dereferenced value (unless it's circular)
|
|
284
283
|
if (!circular) {
|
package/lib/normalize-args.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Options, ParserOptions } from "./options.js";
|
|
2
2
|
import { getNewOptions } from "./options.js";
|
|
3
|
-
import type { JSONSchema, SchemaCallback } from "./
|
|
3
|
+
import type { JSONSchema, SchemaCallback } from "./index.js";
|
|
4
4
|
|
|
5
5
|
// I really dislike this function and the way it's written. It's not clear what it's doing, and it's way too flexible
|
|
6
6
|
// In the future, I'd like to deprecate the api and accept only named parameters in index.ts
|
package/lib/options.ts
CHANGED
|
@@ -82,6 +82,13 @@ export interface DereferenceOptions {
|
|
|
82
82
|
* Default: `relative`
|
|
83
83
|
*/
|
|
84
84
|
externalReferenceResolution?: "relative" | "root";
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether duplicate keys should be merged when dereferencing objects.
|
|
88
|
+
*
|
|
89
|
+
* Default: `true`
|
|
90
|
+
*/
|
|
91
|
+
mergeKeys?: boolean;
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
/**
|
|
@@ -229,6 +236,7 @@ export const getJsonSchemaRefParserDefaultOptions = () => {
|
|
|
229
236
|
*/
|
|
230
237
|
excludedPathMatcher: () => false,
|
|
231
238
|
referenceResolution: "relative",
|
|
239
|
+
mergeKeys: true,
|
|
232
240
|
},
|
|
233
241
|
|
|
234
242
|
mutateInputSchema: true,
|
package/lib/parsers/yaml.ts
CHANGED
|
@@ -42,7 +42,12 @@ export default {
|
|
|
42
42
|
try {
|
|
43
43
|
return yaml.load(data, { schema: JSON_SCHEMA });
|
|
44
44
|
} catch (e: any) {
|
|
45
|
-
|
|
45
|
+
try {
|
|
46
|
+
// fallback to non JSON_SCHEMA
|
|
47
|
+
return yaml.load(data);
|
|
48
|
+
} catch (e: any) {
|
|
49
|
+
throw new ParserError(e?.message || "Parser Error", file.url);
|
|
50
|
+
}
|
|
46
51
|
}
|
|
47
52
|
} else {
|
|
48
53
|
// data is already a JavaScript value (object, array, number, null, NaN, etc.)
|
package/lib/pointer.ts
CHANGED
|
@@ -3,7 +3,8 @@ import type { ParserOptions } from "./options.js";
|
|
|
3
3
|
import $Ref from "./ref.js";
|
|
4
4
|
import * as url from "./util/url.js";
|
|
5
5
|
import { JSONParserError, InvalidPointerError, MissingPointerError, isHandledError } from "./util/errors.js";
|
|
6
|
-
import type { JSONSchema } from "./
|
|
6
|
+
import type { JSONSchema } from "./index.js";
|
|
7
|
+
import type { JSONSchema4Type, JSONSchema6Type, JSONSchema7Type } from "json-schema";
|
|
7
8
|
|
|
8
9
|
export const nullSymbol = Symbol("null");
|
|
9
10
|
|
|
@@ -12,14 +13,6 @@ const tildes = /~/g;
|
|
|
12
13
|
const escapedSlash = /~1/g;
|
|
13
14
|
const escapedTilde = /~0/g;
|
|
14
15
|
|
|
15
|
-
const safeDecodeURIComponent = (encodedURIComponent: string): string => {
|
|
16
|
-
try {
|
|
17
|
-
return decodeURIComponent(encodedURIComponent);
|
|
18
|
-
} catch {
|
|
19
|
-
return encodedURIComponent;
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
16
|
/**
|
|
24
17
|
* This class represents a single JSON pointer and its resolved value.
|
|
25
18
|
*
|
|
@@ -90,7 +83,7 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
|
|
|
90
83
|
*/
|
|
91
84
|
resolve(obj: S, options?: O, pathFromRoot?: string) {
|
|
92
85
|
const tokens = Pointer.parse(this.path, this.originalPath);
|
|
93
|
-
const found:
|
|
86
|
+
const found: string[] = [];
|
|
94
87
|
|
|
95
88
|
// Crawl the object, one token at a time
|
|
96
89
|
this.value = unwrapOrThrow(obj);
|
|
@@ -123,7 +116,7 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
|
|
|
123
116
|
// actually instead pointing to an existing `null` value then we should use that
|
|
124
117
|
// `null` value.
|
|
125
118
|
if (token in this.value && this.value[token] === null) {
|
|
126
|
-
// We use a `null` symbol for internal tracking to
|
|
119
|
+
// We use a `null` symbol for internal tracking to differentiate between a general `null`
|
|
127
120
|
// value and our expected `null` value.
|
|
128
121
|
this.value = nullSymbol;
|
|
129
122
|
continue;
|
|
@@ -163,7 +156,7 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
|
|
|
163
156
|
* @returns
|
|
164
157
|
* Returns the modified object, or an entirely new object if the entire object is overwritten.
|
|
165
158
|
*/
|
|
166
|
-
set(obj: S, value:
|
|
159
|
+
set(obj: S, value: JSONSchema4Type | JSONSchema6Type | JSONSchema7Type, options?: O) {
|
|
167
160
|
const tokens = Pointer.parse(this.path);
|
|
168
161
|
let token;
|
|
169
162
|
|
|
@@ -190,7 +183,7 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
|
|
|
190
183
|
}
|
|
191
184
|
|
|
192
185
|
// Set the value of the final token
|
|
193
|
-
resolveIf$Ref(this, options);
|
|
186
|
+
resolveIf$Ref<S, O>(this, options);
|
|
194
187
|
token = tokens[tokens.length - 1];
|
|
195
188
|
setValue(this, token, value);
|
|
196
189
|
|
|
@@ -225,7 +218,7 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
|
|
|
225
218
|
|
|
226
219
|
// Decode each part, according to RFC 6901
|
|
227
220
|
for (let i = 0; i < split.length; i++) {
|
|
228
|
-
split[i] =
|
|
221
|
+
split[i] = split[i].replace(escapedSlash, "/").replace(escapedTilde, "~");
|
|
229
222
|
}
|
|
230
223
|
|
|
231
224
|
if (split[0] !== "") {
|
|
@@ -253,7 +246,9 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
|
|
|
253
246
|
for (let i = 0; i < tokens.length; i++) {
|
|
254
247
|
const token = tokens[i];
|
|
255
248
|
// Encode the token, according to RFC 6901
|
|
256
|
-
|
|
249
|
+
// RFC 6901 only requires encoding ~ as ~0 and / as ~1
|
|
250
|
+
// We do NOT use encodeURIComponent as it encodes characters like $ which should remain literal
|
|
251
|
+
base += "/" + token.replace(tildes, "~0").replace(slashes, "~1");
|
|
257
252
|
}
|
|
258
253
|
|
|
259
254
|
return base;
|
|
@@ -271,7 +266,11 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
|
|
|
271
266
|
* @param [pathFromRoot] - the path of place that initiated resolving
|
|
272
267
|
* @returns - Returns `true` if the resolution path changed
|
|
273
268
|
*/
|
|
274
|
-
function resolveIf$Ref
|
|
269
|
+
function resolveIf$Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(
|
|
270
|
+
pointer: Pointer,
|
|
271
|
+
options: O | undefined,
|
|
272
|
+
pathFromRoot?: string,
|
|
273
|
+
) {
|
|
275
274
|
// Is the value a JSON reference? (and allowed?)
|
|
276
275
|
|
|
277
276
|
if ($Ref.isAllowed$Ref(pointer.value, options)) {
|
|
@@ -291,7 +290,7 @@ function resolveIf$Ref(pointer: any, options: any, pathFromRoot?: any) {
|
|
|
291
290
|
if ($Ref.isExtended$Ref(pointer.value)) {
|
|
292
291
|
// This JSON reference "extends" the resolved value, rather than simply pointing to it.
|
|
293
292
|
// So the resolved path does NOT change. Just the value does.
|
|
294
|
-
pointer.value = $Ref.dereference(pointer.value, resolved.value);
|
|
293
|
+
pointer.value = $Ref.dereference(pointer.value, resolved.value, options);
|
|
295
294
|
return false;
|
|
296
295
|
} else {
|
|
297
296
|
// Resolve the reference
|
|
@@ -318,7 +317,7 @@ export default Pointer;
|
|
|
318
317
|
* @param value - The value to assign
|
|
319
318
|
* @returns - Returns the assigned value
|
|
320
319
|
*/
|
|
321
|
-
function setValue(pointer:
|
|
320
|
+
function setValue(pointer: Pointer, token: string, value: JSONSchema4Type | JSONSchema6Type | JSONSchema7Type) {
|
|
322
321
|
if (pointer.value && typeof pointer.value === "object") {
|
|
323
322
|
if (token === "-" && Array.isArray(pointer.value)) {
|
|
324
323
|
pointer.value.push(value);
|
|
@@ -333,7 +332,7 @@ function setValue(pointer: any, token: any, value: any) {
|
|
|
333
332
|
return value;
|
|
334
333
|
}
|
|
335
334
|
|
|
336
|
-
function unwrapOrThrow(value:
|
|
335
|
+
function unwrapOrThrow(value: unknown) {
|
|
337
336
|
if (isHandledError(value)) {
|
|
338
337
|
throw value;
|
|
339
338
|
}
|
|
@@ -341,6 +340,6 @@ function unwrapOrThrow(value: any) {
|
|
|
341
340
|
return value;
|
|
342
341
|
}
|
|
343
342
|
|
|
344
|
-
function isRootPath(pathFromRoot:
|
|
343
|
+
function isRootPath(pathFromRoot: string | unknown): boolean {
|
|
345
344
|
return typeof pathFromRoot == "string" && Pointer.parse(pathFromRoot).length == 0;
|
|
346
345
|
}
|
package/lib/ref.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { InvalidPointerError, isHandledError, normalizeError } from "./util/erro
|
|
|
4
4
|
import { safePointerToPath, stripHash, getHash } from "./util/url.js";
|
|
5
5
|
import type $Refs from "./refs.js";
|
|
6
6
|
import type { ParserOptions } from "./options.js";
|
|
7
|
-
import type { JSONSchema } from "./
|
|
7
|
+
import type { JSONSchema } from "./index.js";
|
|
8
|
+
import type { JSONSchema4Type, JSONSchema6Type, JSONSchema7Type } from "json-schema";
|
|
8
9
|
|
|
9
10
|
export type $RefError = JSONParserError | ResolverError | ParserError | MissingPointerError;
|
|
10
11
|
|
|
@@ -150,7 +151,7 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
|
|
|
150
151
|
* @param path - The full path of the property to set, optionally with a JSON pointer in the hash
|
|
151
152
|
* @param value - The value to assign
|
|
152
153
|
*/
|
|
153
|
-
set(path: string, value:
|
|
154
|
+
set(path: string, value: JSONSchema4Type | JSONSchema6Type | JSONSchema7Type) {
|
|
154
155
|
const pointer = new Pointer(this, path);
|
|
155
156
|
this.value = pointer.set(this.value, value);
|
|
156
157
|
if (this.value === nullSymbol) {
|
|
@@ -273,14 +274,16 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
|
|
|
273
274
|
*
|
|
274
275
|
* @param $ref - The JSON reference object (the one with the "$ref" property)
|
|
275
276
|
* @param resolvedValue - The resolved value, which can be any type
|
|
277
|
+
* @param options - The options
|
|
276
278
|
* @returns - Returns the dereferenced value
|
|
277
279
|
*/
|
|
278
280
|
static dereference<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(
|
|
279
281
|
$ref: $Ref<S, O>,
|
|
280
282
|
resolvedValue: S,
|
|
283
|
+
options?: O,
|
|
281
284
|
): S {
|
|
282
285
|
if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) {
|
|
283
|
-
const merged = {}
|
|
286
|
+
const merged = {} as Partial<S>;
|
|
284
287
|
for (const key of Object.keys($ref)) {
|
|
285
288
|
if (key !== "$ref") {
|
|
286
289
|
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
@@ -288,10 +291,24 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
|
|
|
288
291
|
}
|
|
289
292
|
}
|
|
290
293
|
|
|
291
|
-
|
|
294
|
+
const mergeKeys = options?.dereference?.mergeKeys ?? true;
|
|
295
|
+
|
|
296
|
+
for (const _key of Object.keys(resolvedValue)) {
|
|
297
|
+
const key = _key as keyof S;
|
|
292
298
|
if (!(key in merged)) {
|
|
293
|
-
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
294
299
|
merged[key] = resolvedValue[key];
|
|
300
|
+
} else {
|
|
301
|
+
// TODO: this behavior should be configurable from options on the CLI
|
|
302
|
+
// Key is already in merged, so we should merge them if both are objects
|
|
303
|
+
if (
|
|
304
|
+
mergeKeys &&
|
|
305
|
+
typeof merged[key] === "object" &&
|
|
306
|
+
merged[key] !== null &&
|
|
307
|
+
typeof resolvedValue[key] === "object" &&
|
|
308
|
+
resolvedValue[key] !== null
|
|
309
|
+
) {
|
|
310
|
+
merged[key] = deepMerge<(typeof merged)[keyof S]>(resolvedValue[key], merged[key]);
|
|
311
|
+
}
|
|
295
312
|
}
|
|
296
313
|
}
|
|
297
314
|
|
|
@@ -303,4 +320,37 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
|
|
|
303
320
|
}
|
|
304
321
|
}
|
|
305
322
|
|
|
323
|
+
function deepMerge<T>(target: Partial<T>, source: Partial<T>): T {
|
|
324
|
+
//return {...target, ...source};
|
|
325
|
+
|
|
326
|
+
// If either isn't an object, just return source (overwrite)
|
|
327
|
+
if (typeof target !== "object" || target === null) {
|
|
328
|
+
return source as T;
|
|
329
|
+
}
|
|
330
|
+
if (typeof source !== "object" || source === null) {
|
|
331
|
+
return source;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Ensure we don't mutate target directly
|
|
335
|
+
const output = Array.isArray(target) ? [...target] : { ...target };
|
|
336
|
+
|
|
337
|
+
for (const key of Object.keys(source)) {
|
|
338
|
+
// @ts-expect-error
|
|
339
|
+
if (Array.isArray(source[key])) {
|
|
340
|
+
// If it's an array, replace entirely (you can customize this to concat instead)
|
|
341
|
+
// @ts-expect-error
|
|
342
|
+
output[key] = [...source[key]];
|
|
343
|
+
// @ts-expect-error
|
|
344
|
+
} else if (typeof source[key] === "object" && source[key] !== null) {
|
|
345
|
+
// @ts-expect-error
|
|
346
|
+
output[key] = deepMerge(target[key], source[key]);
|
|
347
|
+
} else {
|
|
348
|
+
// @ts-expect-error
|
|
349
|
+
output[key] = source[key];
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return output as T;
|
|
354
|
+
}
|
|
355
|
+
|
|
306
356
|
export default $Ref;
|
package/lib/refs.ts
CHANGED
|
@@ -2,8 +2,8 @@ import $Ref from "./ref.js";
|
|
|
2
2
|
import * as url from "./util/url.js";
|
|
3
3
|
import type { JSONSchema4Type, JSONSchema6Type, JSONSchema7Type } from "json-schema";
|
|
4
4
|
import type { ParserOptions } from "./options.js";
|
|
5
|
-
import convertPathToPosix from "./util/convert-path-to-posix";
|
|
6
|
-
import type { JSONSchema } from "./
|
|
5
|
+
import convertPathToPosix from "./util/convert-path-to-posix.js";
|
|
6
|
+
import type { JSONSchema } from "./index.js";
|
|
7
7
|
|
|
8
8
|
interface $RefsMap<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>> {
|
|
9
9
|
[url: string]: $Ref<S, O>;
|
package/lib/types/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
JSONSchema7Object,
|
|
8
8
|
} from "json-schema";
|
|
9
9
|
import type $Refs from "../refs.js";
|
|
10
|
-
import type { ParserOptions } from "../options";
|
|
10
|
+
import type { ParserOptions } from "../options.js";
|
|
11
11
|
|
|
12
12
|
export type JSONSchema = JSONSchema4 | JSONSchema6 | JSONSchema7;
|
|
13
13
|
export type JSONSchemaObject = JSONSchema4Object | JSONSchema6Object | JSONSchema7Object;
|
package/lib/util/errors.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { getHash, stripHash, toFileSystemPath } from "./url.js";
|
|
|
2
2
|
import type $RefParser from "../index.js";
|
|
3
3
|
import type { ParserOptions } from "../index.js";
|
|
4
4
|
import type { JSONSchema } from "../index.js";
|
|
5
|
-
import type $Ref from "../ref";
|
|
5
|
+
import type $Ref from "../ref.js";
|
|
6
6
|
|
|
7
7
|
export type JSONParserErrorType =
|
|
8
8
|
| "EUNKNOWN"
|
|
@@ -201,7 +201,7 @@ export class InvalidPointerError extends JSONParserError {
|
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
export function isHandledError(err:
|
|
204
|
+
export function isHandledError(err: unknown): err is JSONParserError {
|
|
205
205
|
return err instanceof JSONParserError || err instanceof JSONParserErrorGroup;
|
|
206
206
|
}
|
|
207
207
|
|