z-schema 12.0.0 → 12.0.2
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 +12 -5
- package/bin/z-schema +28 -1
- package/cjs/index.d.ts +8 -0
- package/cjs/index.js +330 -212
- package/dist/format-validators.js +38 -2
- package/dist/json-schema.js +1 -1
- package/dist/report.js +13 -4
- package/dist/schema-cache.js +21 -7
- package/dist/schema-compiler.js +33 -9
- package/dist/types/utils/constants.d.ts +19 -0
- package/dist/types/z-schema-base.d.ts +10 -1
- package/dist/types/z-schema-options.d.ts +2 -1
- package/dist/utils/clone.js +1 -1
- package/dist/utils/constants.js +19 -0
- package/dist/utils/json.js +1 -1
- package/dist/utils/schema-regex.js +12 -0
- package/dist/validation/array.js +1 -2
- package/dist/validation/combinators.js +6 -7
- package/dist/validation/object.js +3 -4
- package/dist/validation/string.js +2 -10
- package/dist/z-schema-base.js +10 -0
- package/dist/z-schema-options.js +6 -1
- package/package.json +3 -1
- package/src/format-validators.ts +31 -2
- package/src/json-schema.ts +1 -1
- package/src/report.ts +14 -4
- package/src/schema-cache.ts +22 -7
- package/src/schema-compiler.ts +38 -9
- package/src/utils/clone.ts +1 -1
- package/src/utils/constants.ts +21 -0
- package/src/utils/json.ts +1 -1
- package/src/utils/schema-regex.ts +14 -0
- package/src/validation/array.ts +1 -2
- package/src/validation/combinators.ts +6 -7
- package/src/validation/object.ts +3 -4
- package/src/validation/string.ts +2 -10
- package/src/z-schema-base.ts +11 -0
- package/src/z-schema-options.ts +7 -1
- package/umd/ZSchema.js +330 -212
- package/umd/ZSchema.min.js +1 -1
package/cjs/index.js
CHANGED
|
@@ -15,6 +15,26 @@ const getRemotePath = (uri) => {
|
|
|
15
15
|
};
|
|
16
16
|
const isAbsoluteUri = (uri) => /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(uri);
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Default maximum recursion depth for deeply nested schema/data traversal.
|
|
20
|
+
* Used as the default for {@link ZSchemaOptions.maxRecursionDepth} and
|
|
21
|
+
* internal helpers like `deepClone` and `collectIds`.
|
|
22
|
+
*/
|
|
23
|
+
const DEFAULT_MAX_RECURSION_DEPTH = 100;
|
|
24
|
+
/**
|
|
25
|
+
* Maximum allowed value for {@link ZSchemaOptions.asyncTimeout} in milliseconds.
|
|
26
|
+
* Values exceeding this limit are clamped during option normalization to
|
|
27
|
+
* prevent resource exhaustion (CWE-400).
|
|
28
|
+
*/
|
|
29
|
+
const MAX_ASYNC_TIMEOUT = 60_000;
|
|
30
|
+
/**
|
|
31
|
+
* Maximum allowed length for a JSON Schema `pattern` regular expression string.
|
|
32
|
+
* Patterns exceeding this limit are rejected by {@link compileSchemaRegex} to
|
|
33
|
+
* mitigate Regular Expression Denial-of-Service (CWE-1333) and regex injection
|
|
34
|
+
* (CWE-95).
|
|
35
|
+
*/
|
|
36
|
+
const MAX_SCHEMA_REGEX_LENGTH = 10_000;
|
|
37
|
+
|
|
18
38
|
const whatIs = (what) => {
|
|
19
39
|
if (typeof what === 'object') {
|
|
20
40
|
if (what === null) {
|
|
@@ -48,177 +68,6 @@ function isInteger(value) {
|
|
|
48
68
|
return typeof value === 'number' && Number.isFinite(value) && value % 1 === 0;
|
|
49
69
|
}
|
|
50
70
|
|
|
51
|
-
const CURRENT_DEFAULT_SCHEMA_VERSION = 'draft2020-12';
|
|
52
|
-
const VERSION_SCHEMA_URL_MAPPING = {
|
|
53
|
-
'draft-04': 'http://json-schema.org/draft-04/schema#',
|
|
54
|
-
'draft-06': 'http://json-schema.org/draft-06/schema#',
|
|
55
|
-
'draft-07': 'http://json-schema.org/draft-07/schema#',
|
|
56
|
-
'draft2019-09': 'https://json-schema.org/draft/2019-09/schema',
|
|
57
|
-
'draft2020-12': 'https://json-schema.org/draft/2020-12/schema',
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
function copyProp(from, to, key, fn) {
|
|
61
|
-
if (Object.hasOwn(from, key)) {
|
|
62
|
-
Object.defineProperty(to, key, {
|
|
63
|
-
value: fn ? fn(from[key]) : from[key],
|
|
64
|
-
enumerable: true,
|
|
65
|
-
writable: true,
|
|
66
|
-
configurable: true,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const shallowClone = (src) => {
|
|
72
|
-
if (src == null || typeof src !== 'object') {
|
|
73
|
-
return src;
|
|
74
|
-
}
|
|
75
|
-
let res;
|
|
76
|
-
if (Array.isArray(src)) {
|
|
77
|
-
res = [];
|
|
78
|
-
for (let i = 0; i < src.length; i++) {
|
|
79
|
-
res[i] = src[i];
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
res = {};
|
|
84
|
-
const keys = Object.keys(src).sort();
|
|
85
|
-
for (const key of keys) {
|
|
86
|
-
copyProp(src, res, key);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return res;
|
|
90
|
-
};
|
|
91
|
-
const deepClone = (src, maxDepth = DEFAULT_MAX_RECURSION_DEPTH) => {
|
|
92
|
-
let vidx = 0;
|
|
93
|
-
const visited = new Map();
|
|
94
|
-
const cloned = [];
|
|
95
|
-
const cloneDeepInner = (src, _depth) => {
|
|
96
|
-
if (typeof src !== 'object' || src === null) {
|
|
97
|
-
return src;
|
|
98
|
-
}
|
|
99
|
-
if (_depth >= maxDepth) {
|
|
100
|
-
throw new Error(`Maximum recursion depth (${maxDepth}) exceeded in deepClone. ` +
|
|
101
|
-
'If your schema or data is deeply nested and valid, increase the maxRecursionDepth option.');
|
|
102
|
-
}
|
|
103
|
-
let res;
|
|
104
|
-
const cidx = visited.get(src);
|
|
105
|
-
if (cidx !== undefined) {
|
|
106
|
-
return cloned[cidx];
|
|
107
|
-
}
|
|
108
|
-
visited.set(src, vidx++);
|
|
109
|
-
if (Array.isArray(src)) {
|
|
110
|
-
res = [];
|
|
111
|
-
cloned.push(res);
|
|
112
|
-
for (let i = 0; i < src.length; i++) {
|
|
113
|
-
res[i] = cloneDeepInner(src[i], _depth + 1);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
res = {};
|
|
118
|
-
cloned.push(res);
|
|
119
|
-
const keys = Object.keys(src).sort();
|
|
120
|
-
for (const key of keys) {
|
|
121
|
-
copyProp(src, res, key, (v) => cloneDeepInner(v, _depth + 1));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return res;
|
|
125
|
-
};
|
|
126
|
-
return cloneDeepInner(src, 0);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const DEFAULT_MAX_RECURSION_DEPTH = 100;
|
|
130
|
-
const defaultOptions = {
|
|
131
|
-
// default version to validate against
|
|
132
|
-
version: CURRENT_DEFAULT_SCHEMA_VERSION,
|
|
133
|
-
// default timeout for all async tasks
|
|
134
|
-
asyncTimeout: 2000,
|
|
135
|
-
// force additionalProperties and additionalItems to be defined on "object" and "array" types
|
|
136
|
-
forceAdditional: false,
|
|
137
|
-
// assume additionalProperties and additionalItems are defined as "false" where appropriate
|
|
138
|
-
assumeAdditional: false,
|
|
139
|
-
// do case insensitive comparison for enums
|
|
140
|
-
enumCaseInsensitiveComparison: false,
|
|
141
|
-
// force items to be defined on "array" types
|
|
142
|
-
forceItems: false,
|
|
143
|
-
// force minItems to be defined on "array" types
|
|
144
|
-
forceMinItems: false,
|
|
145
|
-
// force maxItems to be defined on "array" types
|
|
146
|
-
forceMaxItems: false,
|
|
147
|
-
// force minLength to be defined on "string" types
|
|
148
|
-
forceMinLength: false,
|
|
149
|
-
// force maxLength to be defined on "string" types
|
|
150
|
-
forceMaxLength: false,
|
|
151
|
-
// force properties or patternProperties to be defined on "object" types
|
|
152
|
-
forceProperties: false,
|
|
153
|
-
// ignore references that cannot be resolved (remote schemas)
|
|
154
|
-
ignoreUnresolvableReferences: false,
|
|
155
|
-
// disallow usage of keywords that this validator can't handle
|
|
156
|
-
noExtraKeywords: false,
|
|
157
|
-
// disallow usage of schema's without "type" defined
|
|
158
|
-
noTypeless: false,
|
|
159
|
-
// disallow zero length strings in validated objects
|
|
160
|
-
noEmptyStrings: false,
|
|
161
|
-
// disallow zero length arrays in validated objects
|
|
162
|
-
noEmptyArrays: false,
|
|
163
|
-
// forces "uri" format to be in fully rfc3986 compliant
|
|
164
|
-
strictUris: false,
|
|
165
|
-
// turn on some of the above
|
|
166
|
-
strictMode: false,
|
|
167
|
-
// report error paths as an array of path segments to get to the offending node
|
|
168
|
-
reportPathAsArray: false,
|
|
169
|
-
// stop validation as soon as an error is found
|
|
170
|
-
breakOnFirstError: false,
|
|
171
|
-
// check if schema follows best practices and common sense
|
|
172
|
-
pedanticCheck: false,
|
|
173
|
-
// ignore unknown formats (do not report them as an error)
|
|
174
|
-
ignoreUnknownFormats: false,
|
|
175
|
-
// control format assertion behavior:
|
|
176
|
-
// true - respect vocabulary: for draft 2019-09/2020-12, check meta-schema $vocabulary
|
|
177
|
-
// to determine if format is assertion or annotation-only
|
|
178
|
-
// false - format is always annotation-only (never asserts)
|
|
179
|
-
// null - legacy behavior: always assert format validation
|
|
180
|
-
formatAssertions: null,
|
|
181
|
-
// function to be called on every schema
|
|
182
|
-
customValidator: null,
|
|
183
|
-
// maximum recursion depth for deeply nested schema/data traversal (prevents stack overflow)
|
|
184
|
-
maxRecursionDepth: DEFAULT_MAX_RECURSION_DEPTH,
|
|
185
|
-
};
|
|
186
|
-
const normalizeOptions = (options) => {
|
|
187
|
-
let normalized;
|
|
188
|
-
// options
|
|
189
|
-
if (typeof options === 'object') {
|
|
190
|
-
let keys = Object.keys(options);
|
|
191
|
-
// check that the options are correctly configured
|
|
192
|
-
for (const key of keys) {
|
|
193
|
-
if (defaultOptions[key] === undefined) {
|
|
194
|
-
throw new Error('Unexpected option passed to constructor: ' + key);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
// copy the default options into passed options
|
|
198
|
-
keys = Object.keys(defaultOptions);
|
|
199
|
-
for (const key of keys) {
|
|
200
|
-
if (options[key] === undefined) {
|
|
201
|
-
options[key] = shallowClone(defaultOptions[key]);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
normalized = options;
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
normalized = shallowClone(defaultOptions);
|
|
208
|
-
}
|
|
209
|
-
if (normalized.strictMode === true) {
|
|
210
|
-
normalized.forceAdditional = true;
|
|
211
|
-
normalized.forceItems = true;
|
|
212
|
-
normalized.forceMaxLength = true;
|
|
213
|
-
normalized.forceProperties = true;
|
|
214
|
-
normalized.noExtraKeywords = true;
|
|
215
|
-
normalized.noTypeless = true;
|
|
216
|
-
normalized.noEmptyStrings = true;
|
|
217
|
-
normalized.noEmptyArrays = true;
|
|
218
|
-
}
|
|
219
|
-
return normalized;
|
|
220
|
-
};
|
|
221
|
-
|
|
222
71
|
/**
|
|
223
72
|
* Keywords whose values are not JSON Schema sub-schemas and must not be
|
|
224
73
|
* traversed during schema walking (id collection, reference collection, etc.).
|
|
@@ -374,6 +223,75 @@ function getValidateError({ message, details }) {
|
|
|
374
223
|
return new ValidateError(message || '', details);
|
|
375
224
|
}
|
|
376
225
|
|
|
226
|
+
function copyProp(from, to, key, fn) {
|
|
227
|
+
if (Object.hasOwn(from, key)) {
|
|
228
|
+
Object.defineProperty(to, key, {
|
|
229
|
+
value: fn ? fn(from[key]) : from[key],
|
|
230
|
+
enumerable: true,
|
|
231
|
+
writable: true,
|
|
232
|
+
configurable: true,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const shallowClone = (src) => {
|
|
238
|
+
if (src == null || typeof src !== 'object') {
|
|
239
|
+
return src;
|
|
240
|
+
}
|
|
241
|
+
let res;
|
|
242
|
+
if (Array.isArray(src)) {
|
|
243
|
+
res = [];
|
|
244
|
+
for (let i = 0; i < src.length; i++) {
|
|
245
|
+
res[i] = src[i];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
res = {};
|
|
250
|
+
const keys = Object.keys(src).sort();
|
|
251
|
+
for (const key of keys) {
|
|
252
|
+
copyProp(src, res, key);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return res;
|
|
256
|
+
};
|
|
257
|
+
const deepClone = (src, maxDepth = DEFAULT_MAX_RECURSION_DEPTH) => {
|
|
258
|
+
let vidx = 0;
|
|
259
|
+
const visited = new Map();
|
|
260
|
+
const cloned = [];
|
|
261
|
+
const cloneDeepInner = (src, _depth) => {
|
|
262
|
+
if (typeof src !== 'object' || src === null) {
|
|
263
|
+
return src;
|
|
264
|
+
}
|
|
265
|
+
if (_depth >= maxDepth) {
|
|
266
|
+
throw new Error(`Maximum recursion depth (${maxDepth}) exceeded in deepClone. ` +
|
|
267
|
+
'If your schema or data is deeply nested and valid, increase the maxRecursionDepth option.');
|
|
268
|
+
}
|
|
269
|
+
let res;
|
|
270
|
+
const cidx = visited.get(src);
|
|
271
|
+
if (cidx !== undefined) {
|
|
272
|
+
return cloned[cidx];
|
|
273
|
+
}
|
|
274
|
+
visited.set(src, vidx++);
|
|
275
|
+
if (Array.isArray(src)) {
|
|
276
|
+
res = [];
|
|
277
|
+
cloned.push(res);
|
|
278
|
+
for (let i = 0; i < src.length; i++) {
|
|
279
|
+
res[i] = cloneDeepInner(src[i], _depth + 1);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
res = {};
|
|
284
|
+
cloned.push(res);
|
|
285
|
+
const keys = Object.keys(src).sort();
|
|
286
|
+
for (const key of keys) {
|
|
287
|
+
copyProp(src, res, key, (v) => cloneDeepInner(v, _depth + 1));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return res;
|
|
291
|
+
};
|
|
292
|
+
return cloneDeepInner(src, 0);
|
|
293
|
+
};
|
|
294
|
+
|
|
377
295
|
const areEqual = (json1, json2, options, _depth = 0) => {
|
|
378
296
|
const caseInsensitiveComparison = options?.caseInsensitiveComparison || false;
|
|
379
297
|
const maxDepth = options?.maxDepth ?? DEFAULT_MAX_RECURSION_DEPTH;
|
|
@@ -446,6 +364,7 @@ const get = (obj, path) => {
|
|
|
446
364
|
const jsonSymbol = Symbol.for('z-schema/json');
|
|
447
365
|
const schemaSymbol = Symbol.for('z-schema/schema');
|
|
448
366
|
|
|
367
|
+
const ASYNC_TIMEOUT_POLL_MS = 10;
|
|
449
368
|
class Report {
|
|
450
369
|
asyncTasks = [];
|
|
451
370
|
commonErrorMessage;
|
|
@@ -519,7 +438,8 @@ class Report {
|
|
|
519
438
|
return this.parentReport.getAncestor(id);
|
|
520
439
|
}
|
|
521
440
|
processAsyncTasks(timeout, callback) {
|
|
522
|
-
const validationTimeout = timeout || 2000;
|
|
441
|
+
const validationTimeout = Math.min(Math.max(timeout || 2000, 0), MAX_ASYNC_TIMEOUT);
|
|
442
|
+
const timeoutAt = Date.now() + validationTimeout;
|
|
523
443
|
let tasksCount = this.asyncTasks.length;
|
|
524
444
|
let timedOut = false;
|
|
525
445
|
const finish = () => {
|
|
@@ -548,13 +468,19 @@ class Report {
|
|
|
548
468
|
const respondCallback = respond(processFn);
|
|
549
469
|
fn(...fnArgs, respondCallback);
|
|
550
470
|
}
|
|
551
|
-
|
|
552
|
-
if (tasksCount
|
|
471
|
+
const checkTimeout = () => {
|
|
472
|
+
if (timedOut || tasksCount <= 0) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (Date.now() >= timeoutAt) {
|
|
553
476
|
timedOut = true;
|
|
554
477
|
this.addError('ASYNC_TIMEOUT', [tasksCount, validationTimeout]);
|
|
555
478
|
callback(getValidateError({ details: this.errors }), false);
|
|
479
|
+
return;
|
|
556
480
|
}
|
|
557
|
-
|
|
481
|
+
setTimeout(checkTimeout, ASYNC_TIMEOUT_POLL_MS);
|
|
482
|
+
};
|
|
483
|
+
setTimeout(checkTimeout, ASYNC_TIMEOUT_POLL_MS);
|
|
558
484
|
}
|
|
559
485
|
getPath(returnPathAsString) {
|
|
560
486
|
let path = [];
|
|
@@ -697,6 +623,122 @@ class Report {
|
|
|
697
623
|
}
|
|
698
624
|
}
|
|
699
625
|
|
|
626
|
+
const CURRENT_DEFAULT_SCHEMA_VERSION = 'draft2020-12';
|
|
627
|
+
const VERSION_SCHEMA_URL_MAPPING = {
|
|
628
|
+
'draft-04': 'http://json-schema.org/draft-04/schema#',
|
|
629
|
+
'draft-06': 'http://json-schema.org/draft-06/schema#',
|
|
630
|
+
'draft-07': 'http://json-schema.org/draft-07/schema#',
|
|
631
|
+
'draft2019-09': 'https://json-schema.org/draft/2019-09/schema',
|
|
632
|
+
'draft2020-12': 'https://json-schema.org/draft/2020-12/schema',
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
const defaultOptions = {
|
|
636
|
+
// default version to validate against
|
|
637
|
+
version: CURRENT_DEFAULT_SCHEMA_VERSION,
|
|
638
|
+
// default timeout for all async tasks
|
|
639
|
+
asyncTimeout: 2000,
|
|
640
|
+
// force additionalProperties and additionalItems to be defined on "object" and "array" types
|
|
641
|
+
forceAdditional: false,
|
|
642
|
+
// assume additionalProperties and additionalItems are defined as "false" where appropriate
|
|
643
|
+
assumeAdditional: false,
|
|
644
|
+
// do case insensitive comparison for enums
|
|
645
|
+
enumCaseInsensitiveComparison: false,
|
|
646
|
+
// force items to be defined on "array" types
|
|
647
|
+
forceItems: false,
|
|
648
|
+
// force minItems to be defined on "array" types
|
|
649
|
+
forceMinItems: false,
|
|
650
|
+
// force maxItems to be defined on "array" types
|
|
651
|
+
forceMaxItems: false,
|
|
652
|
+
// force minLength to be defined on "string" types
|
|
653
|
+
forceMinLength: false,
|
|
654
|
+
// force maxLength to be defined on "string" types
|
|
655
|
+
forceMaxLength: false,
|
|
656
|
+
// force properties or patternProperties to be defined on "object" types
|
|
657
|
+
forceProperties: false,
|
|
658
|
+
// ignore references that cannot be resolved (remote schemas)
|
|
659
|
+
ignoreUnresolvableReferences: false,
|
|
660
|
+
// disallow usage of keywords that this validator can't handle
|
|
661
|
+
noExtraKeywords: false,
|
|
662
|
+
// disallow usage of schema's without "type" defined
|
|
663
|
+
noTypeless: false,
|
|
664
|
+
// disallow zero length strings in validated objects
|
|
665
|
+
noEmptyStrings: false,
|
|
666
|
+
// disallow zero length arrays in validated objects
|
|
667
|
+
noEmptyArrays: false,
|
|
668
|
+
// forces "uri" format to be in fully rfc3986 compliant
|
|
669
|
+
strictUris: false,
|
|
670
|
+
// turn on some of the above
|
|
671
|
+
strictMode: false,
|
|
672
|
+
// report error paths as an array of path segments to get to the offending node
|
|
673
|
+
reportPathAsArray: false,
|
|
674
|
+
// stop validation as soon as an error is found
|
|
675
|
+
breakOnFirstError: false,
|
|
676
|
+
// check if schema follows best practices and common sense
|
|
677
|
+
pedanticCheck: false,
|
|
678
|
+
// ignore unknown formats (do not report them as an error)
|
|
679
|
+
ignoreUnknownFormats: false,
|
|
680
|
+
// control format assertion behavior:
|
|
681
|
+
// true - respect vocabulary: for draft 2019-09/2020-12, check meta-schema $vocabulary
|
|
682
|
+
// to determine if format is assertion or annotation-only
|
|
683
|
+
// false - format is always annotation-only (never asserts)
|
|
684
|
+
// null - legacy behavior: always assert format validation
|
|
685
|
+
formatAssertions: null,
|
|
686
|
+
// function to be called on every schema
|
|
687
|
+
customValidator: null,
|
|
688
|
+
// maximum recursion depth for deeply nested schema/data traversal (prevents stack overflow)
|
|
689
|
+
maxRecursionDepth: DEFAULT_MAX_RECURSION_DEPTH,
|
|
690
|
+
};
|
|
691
|
+
const normalizeOptions = (options) => {
|
|
692
|
+
let normalized;
|
|
693
|
+
// options
|
|
694
|
+
if (typeof options === 'object') {
|
|
695
|
+
let keys = Object.keys(options);
|
|
696
|
+
// check that the options are correctly configured
|
|
697
|
+
for (const key of keys) {
|
|
698
|
+
if (defaultOptions[key] === undefined) {
|
|
699
|
+
throw new Error('Unexpected option passed to constructor: ' + key);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
// copy the default options into passed options
|
|
703
|
+
keys = Object.keys(defaultOptions);
|
|
704
|
+
for (const key of keys) {
|
|
705
|
+
if (options[key] === undefined) {
|
|
706
|
+
options[key] = shallowClone(defaultOptions[key]);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
normalized = options;
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
normalized = shallowClone(defaultOptions);
|
|
713
|
+
}
|
|
714
|
+
// Clamp asyncTimeout to prevent resource exhaustion (CWE-400)
|
|
715
|
+
if (normalized.asyncTimeout != null) {
|
|
716
|
+
normalized.asyncTimeout = Math.min(Math.max(normalized.asyncTimeout, 0), MAX_ASYNC_TIMEOUT);
|
|
717
|
+
}
|
|
718
|
+
if (normalized.strictMode === true) {
|
|
719
|
+
normalized.forceAdditional = true;
|
|
720
|
+
normalized.forceItems = true;
|
|
721
|
+
normalized.forceMaxLength = true;
|
|
722
|
+
normalized.forceProperties = true;
|
|
723
|
+
normalized.noExtraKeywords = true;
|
|
724
|
+
normalized.noTypeless = true;
|
|
725
|
+
normalized.noEmptyStrings = true;
|
|
726
|
+
normalized.noEmptyArrays = true;
|
|
727
|
+
}
|
|
728
|
+
return normalized;
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
// Normalize a URI into a cache key, rejecting keys that could pollute Object.prototype.
|
|
732
|
+
function getSafeRemotePath(uri) {
|
|
733
|
+
const remotePath = getRemotePath(uri);
|
|
734
|
+
if (!remotePath) {
|
|
735
|
+
return undefined;
|
|
736
|
+
}
|
|
737
|
+
if (remotePath === '__proto__' || remotePath === 'constructor' || remotePath === 'prototype') {
|
|
738
|
+
return undefined;
|
|
739
|
+
}
|
|
740
|
+
return remotePath;
|
|
741
|
+
}
|
|
700
742
|
const getEffectiveId = (schema) => {
|
|
701
743
|
let id = getId(schema);
|
|
702
744
|
if ((!id || !isAbsoluteUri(id)) && typeof schema.id === 'string' && isAbsoluteUri(schema.id)) {
|
|
@@ -727,31 +769,31 @@ function prepareRemoteSchema(schema, uri, validationOptions, maxCloneDepth) {
|
|
|
727
769
|
}
|
|
728
770
|
class SchemaCache {
|
|
729
771
|
validator;
|
|
730
|
-
static global_cache =
|
|
731
|
-
cache =
|
|
772
|
+
static global_cache = Object.create(null);
|
|
773
|
+
cache = Object.create(null);
|
|
732
774
|
constructor(validator) {
|
|
733
775
|
this.validator = validator;
|
|
734
776
|
}
|
|
735
777
|
static cacheSchemaByUri(uri, schema) {
|
|
736
|
-
const remotePath =
|
|
778
|
+
const remotePath = getSafeRemotePath(uri);
|
|
737
779
|
if (remotePath) {
|
|
738
780
|
this.global_cache[remotePath] = schema;
|
|
739
781
|
}
|
|
740
782
|
}
|
|
741
783
|
cacheSchemaByUri(uri, schema) {
|
|
742
|
-
const remotePath =
|
|
784
|
+
const remotePath = getSafeRemotePath(uri);
|
|
743
785
|
if (remotePath) {
|
|
744
786
|
this.cache[remotePath] = schema;
|
|
745
787
|
}
|
|
746
788
|
}
|
|
747
789
|
removeFromCacheByUri(uri) {
|
|
748
|
-
const remotePath =
|
|
790
|
+
const remotePath = getSafeRemotePath(uri);
|
|
749
791
|
if (remotePath) {
|
|
750
792
|
delete this.cache[remotePath];
|
|
751
793
|
}
|
|
752
794
|
}
|
|
753
795
|
checkCacheForUri(uri) {
|
|
754
|
-
const remotePath =
|
|
796
|
+
const remotePath = getSafeRemotePath(uri);
|
|
755
797
|
return remotePath ? this.cache[remotePath] != null : false;
|
|
756
798
|
}
|
|
757
799
|
getSchema(report, refOrSchema) {
|
|
@@ -766,6 +808,9 @@ class SchemaCache {
|
|
|
766
808
|
return deepClone(refOrSchema, this.validator.options.maxRecursionDepth);
|
|
767
809
|
}
|
|
768
810
|
fromCache(path) {
|
|
811
|
+
if (path === '__proto__' || path === 'constructor' || path === 'prototype') {
|
|
812
|
+
return undefined;
|
|
813
|
+
}
|
|
769
814
|
let found = this.cache[path];
|
|
770
815
|
if (found) {
|
|
771
816
|
return found;
|
|
@@ -797,7 +842,7 @@ class SchemaCache {
|
|
|
797
842
|
}
|
|
798
843
|
}
|
|
799
844
|
}
|
|
800
|
-
const remotePath =
|
|
845
|
+
const remotePath = getSafeRemotePath(uri);
|
|
801
846
|
const queryPath = getQueryPath(uri);
|
|
802
847
|
let result;
|
|
803
848
|
let resolvedFromAncestor = false;
|
|
@@ -4331,19 +4376,55 @@ const uriTemplateValidator = (uri) => {
|
|
|
4331
4376
|
}
|
|
4332
4377
|
return !inExpression;
|
|
4333
4378
|
};
|
|
4379
|
+
const hasValidTildeEscapes = (segment) => {
|
|
4380
|
+
for (let i = 0; i < segment.length; i++) {
|
|
4381
|
+
if (segment[i] === '~') {
|
|
4382
|
+
const next = segment[i + 1];
|
|
4383
|
+
if (next !== '0' && next !== '1') {
|
|
4384
|
+
return false;
|
|
4385
|
+
}
|
|
4386
|
+
i++; // skip the escape character
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
return true;
|
|
4390
|
+
};
|
|
4334
4391
|
const jsonPointerValidator = (pointer) => {
|
|
4335
4392
|
if (typeof pointer !== 'string')
|
|
4336
4393
|
return true;
|
|
4337
4394
|
// JSON Pointer: empty, or a sequence of '/'-prefixed reference tokens.
|
|
4338
4395
|
// In each token, '~' must be escaped as '~0' or '~1'.
|
|
4339
|
-
|
|
4396
|
+
if (pointer === '')
|
|
4397
|
+
return true;
|
|
4398
|
+
if (!/^(?:\/[^/]*)+$/.test(pointer))
|
|
4399
|
+
return false;
|
|
4400
|
+
const tokens = pointer.split('/').slice(1); // first element is empty before leading '/'
|
|
4401
|
+
for (const token of tokens) {
|
|
4402
|
+
if (!hasValidTildeEscapes(token))
|
|
4403
|
+
return false;
|
|
4404
|
+
}
|
|
4405
|
+
return true;
|
|
4340
4406
|
};
|
|
4341
4407
|
const relativeJsonPointerValidator = (pointer) => {
|
|
4342
4408
|
if (typeof pointer !== 'string')
|
|
4343
4409
|
return true;
|
|
4344
4410
|
// Relative JSON Pointer: non-negative integer prefix (no leading zeros unless zero),
|
|
4345
4411
|
// followed by either '#', a JSON Pointer, or nothing.
|
|
4346
|
-
|
|
4412
|
+
const match = pointer.match(/^(0|[1-9]\d*)(.*)$/);
|
|
4413
|
+
if (!match)
|
|
4414
|
+
return false;
|
|
4415
|
+
const suffix = match[2];
|
|
4416
|
+
if (suffix === '' || suffix === '#')
|
|
4417
|
+
return true;
|
|
4418
|
+
if (!suffix.startsWith('/'))
|
|
4419
|
+
return false;
|
|
4420
|
+
if (!/^(?:\/[^/]*)+$/.test(suffix))
|
|
4421
|
+
return false;
|
|
4422
|
+
const tokens = suffix.split('/').slice(1);
|
|
4423
|
+
for (const token of tokens) {
|
|
4424
|
+
if (!hasValidTildeEscapes(token))
|
|
4425
|
+
return false;
|
|
4426
|
+
}
|
|
4427
|
+
return true;
|
|
4347
4428
|
};
|
|
4348
4429
|
const timeValidator = (time) => {
|
|
4349
4430
|
if (typeof time !== 'string')
|
|
@@ -4447,6 +4528,15 @@ function getRegisteredFormats() {
|
|
|
4447
4528
|
// Shared regex compilation helper for JSON Schema patterns
|
|
4448
4529
|
// Returns { ok: true, value: RegExp } or { ok: false, error: { pattern, message } }
|
|
4449
4530
|
function compileSchemaRegex(pattern) {
|
|
4531
|
+
if (pattern.length > MAX_SCHEMA_REGEX_LENGTH) {
|
|
4532
|
+
return {
|
|
4533
|
+
ok: false,
|
|
4534
|
+
error: {
|
|
4535
|
+
pattern,
|
|
4536
|
+
message: `Pattern length ${pattern.length} exceeds maximum allowed length of ${MAX_SCHEMA_REGEX_LENGTH}`,
|
|
4537
|
+
},
|
|
4538
|
+
};
|
|
4539
|
+
}
|
|
4450
4540
|
const unicodePropertyEscape = /\\[pP]{/;
|
|
4451
4541
|
const nonBmpCharacter = /[\u{10000}-\u{10FFFF}]/u;
|
|
4452
4542
|
const surrogatePairEscape = /\\uD[89AB][0-9A-Fa-f]{2}\\uD[CDEF][0-9A-Fa-f]{2}/;
|
|
@@ -4455,6 +4545,7 @@ function compileSchemaRegex(pattern) {
|
|
|
4455
4545
|
if (needsUnicode) {
|
|
4456
4546
|
// Try compiling with 'u' flag only
|
|
4457
4547
|
try {
|
|
4548
|
+
// lgtm[js/regex-injection] JSON Schema `pattern` is intentionally regex syntax and constrained by MAX_SCHEMA_REGEX_LENGTH.
|
|
4458
4549
|
const re = new RegExp(pattern, 'u');
|
|
4459
4550
|
return { ok: true, value: re };
|
|
4460
4551
|
}
|
|
@@ -4470,6 +4561,7 @@ function compileSchemaRegex(pattern) {
|
|
|
4470
4561
|
}
|
|
4471
4562
|
else {
|
|
4472
4563
|
try {
|
|
4564
|
+
// lgtm[js/regex-injection] JSON Schema `pattern` is intentionally regex syntax and constrained by MAX_SCHEMA_REGEX_LENGTH.
|
|
4473
4565
|
const re = new RegExp(pattern);
|
|
4474
4566
|
return { ok: true, value: re };
|
|
4475
4567
|
}
|
|
@@ -4789,7 +4881,7 @@ function containsValidator(report, schema, json) {
|
|
|
4789
4881
|
for (let idx = 0; idx < json.length; idx++) {
|
|
4790
4882
|
const subReport = new Report_(report);
|
|
4791
4883
|
subReports.push(subReport);
|
|
4792
|
-
|
|
4884
|
+
this._jsonValidate(subReport, containsSchema, json[idx]);
|
|
4793
4885
|
cacheValidationResult(report, containsSchema, json[idx], subReport.errors.length === 0);
|
|
4794
4886
|
}
|
|
4795
4887
|
const addContainsErrorIfNeeded = () => {
|
|
@@ -4822,7 +4914,7 @@ function minContainsValidator() { }
|
|
|
4822
4914
|
function allOfValidator(report, schema, json) {
|
|
4823
4915
|
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.3.2
|
|
4824
4916
|
for (let i = 0; i < schema.allOf.length; i++) {
|
|
4825
|
-
const validateResult =
|
|
4917
|
+
const validateResult = this._jsonValidate(report, schema.allOf[i], json);
|
|
4826
4918
|
if (this.options.breakOnFirstError && validateResult === false) {
|
|
4827
4919
|
break;
|
|
4828
4920
|
}
|
|
@@ -4837,7 +4929,7 @@ function anyOfValidator(report, schema, json) {
|
|
|
4837
4929
|
for (let i = 0; i < schema.anyOf.length; i++) {
|
|
4838
4930
|
const subReport = new Report(report);
|
|
4839
4931
|
subReports.push(subReport);
|
|
4840
|
-
|
|
4932
|
+
this._jsonValidate(subReport, schema.anyOf[i], json);
|
|
4841
4933
|
cacheValidationResult(report, schema.anyOf[i], json, subReport.errors.length === 0);
|
|
4842
4934
|
}
|
|
4843
4935
|
// Aggregate async tasks and decide when ready
|
|
@@ -4863,7 +4955,7 @@ function oneOfValidator(report, schema, json) {
|
|
|
4863
4955
|
for (let i = 0; i < schema.oneOf.length; i++) {
|
|
4864
4956
|
const subReport = new Report(report);
|
|
4865
4957
|
subReports.push(subReport);
|
|
4866
|
-
|
|
4958
|
+
this._jsonValidate(subReport, schema.oneOf[i], json);
|
|
4867
4959
|
cacheValidationResult(report, schema.oneOf[i], json, subReport.errors.length === 0);
|
|
4868
4960
|
}
|
|
4869
4961
|
// Aggregate async tasks and decide when ready
|
|
@@ -4888,7 +4980,7 @@ function oneOfValidator(report, schema, json) {
|
|
|
4888
4980
|
function notValidator(report, schema, json) {
|
|
4889
4981
|
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.6.2
|
|
4890
4982
|
const subReport = new Report(report);
|
|
4891
|
-
if (
|
|
4983
|
+
if (this._jsonValidate(subReport, schema.not, json) === true) {
|
|
4892
4984
|
report.addError('NOT_PASSED', undefined, undefined, schema, 'not');
|
|
4893
4985
|
}
|
|
4894
4986
|
}
|
|
@@ -4906,13 +4998,13 @@ function ifValidator(report, schema, json) {
|
|
|
4906
4998
|
return;
|
|
4907
4999
|
}
|
|
4908
5000
|
const conditionReport = new Report(report);
|
|
4909
|
-
|
|
5001
|
+
this._jsonValidate(conditionReport, conditionSchema, json);
|
|
4910
5002
|
cacheValidationResult(report, conditionSchema, json, conditionReport.errors.length === 0);
|
|
4911
5003
|
const branchSchema = conditionReport.errors.length === 0 ? thenSchema : elseSchema;
|
|
4912
5004
|
if (branchSchema === undefined) {
|
|
4913
5005
|
return;
|
|
4914
5006
|
}
|
|
4915
|
-
|
|
5007
|
+
this._jsonValidate(report, branchSchema, json);
|
|
4916
5008
|
}
|
|
4917
5009
|
function thenValidator() {
|
|
4918
5010
|
// handled by if
|
|
@@ -5168,7 +5260,7 @@ function dependenciesValidator(report, schema, json) {
|
|
|
5168
5260
|
}
|
|
5169
5261
|
else {
|
|
5170
5262
|
// if dependency is a schema, validate against this schema
|
|
5171
|
-
|
|
5263
|
+
this._jsonValidate(report, dependencyDefinition, json);
|
|
5172
5264
|
}
|
|
5173
5265
|
}
|
|
5174
5266
|
}
|
|
@@ -5187,7 +5279,7 @@ function dependentSchemasValidator(report, schema, json) {
|
|
|
5187
5279
|
for (const dependencyName of keys) {
|
|
5188
5280
|
if (Object.hasOwn(json, dependencyName)) {
|
|
5189
5281
|
const dependencySchema = schema.dependentSchemas[dependencyName];
|
|
5190
|
-
|
|
5282
|
+
this._jsonValidate(report, dependencySchema, json);
|
|
5191
5283
|
}
|
|
5192
5284
|
}
|
|
5193
5285
|
}
|
|
@@ -5240,7 +5332,7 @@ function propertyNamesValidator(report, schema, json) {
|
|
|
5240
5332
|
for (const key of keys) {
|
|
5241
5333
|
const subReport = new Report_(report);
|
|
5242
5334
|
subReports.push(subReport);
|
|
5243
|
-
|
|
5335
|
+
this._jsonValidate(subReport, propertyNamesSchema, key);
|
|
5244
5336
|
}
|
|
5245
5337
|
const addPropertyNameErrors = () => {
|
|
5246
5338
|
for (let idx = 0; idx < keys.length; idx++) {
|
|
@@ -5452,21 +5544,13 @@ function formatValidator(report, schema, json) {
|
|
|
5452
5544
|
const result = formatValidatorFn.call(this, json);
|
|
5453
5545
|
if (result instanceof Promise) {
|
|
5454
5546
|
// Promise-based async
|
|
5455
|
-
const timeoutMs = this.options.asyncTimeout || 2000;
|
|
5456
5547
|
const promiseResult = result;
|
|
5457
5548
|
report.addAsyncTaskWithPath(async (callback) => {
|
|
5458
5549
|
try {
|
|
5459
|
-
const
|
|
5460
|
-
setTimeout(() => reject(new Error('Async timeout')), timeoutMs);
|
|
5461
|
-
});
|
|
5462
|
-
const resolved = await Promise.race([promiseResult, timeoutPromise]);
|
|
5550
|
+
const resolved = await promiseResult;
|
|
5463
5551
|
callback(resolved);
|
|
5464
5552
|
}
|
|
5465
|
-
catch (
|
|
5466
|
-
if (error.message === 'Async timeout') {
|
|
5467
|
-
// Don't call callback, let global timeout handle it
|
|
5468
|
-
return;
|
|
5469
|
-
}
|
|
5553
|
+
catch (_error) {
|
|
5470
5554
|
callback(false);
|
|
5471
5555
|
}
|
|
5472
5556
|
}, [], function (resolvedResult) {
|
|
@@ -6314,6 +6398,21 @@ function setSchemaReader(schemaReader) {
|
|
|
6314
6398
|
_schemaReader = schemaReader;
|
|
6315
6399
|
}
|
|
6316
6400
|
|
|
6401
|
+
/** Safely assign a property on `obj`, refusing prototype-polluting keys. */
|
|
6402
|
+
function safeSetProperty(obj, key, value) {
|
|
6403
|
+
const unsafeTargets = [
|
|
6404
|
+
Object.prototype,
|
|
6405
|
+
Function.prototype,
|
|
6406
|
+
Array.prototype,
|
|
6407
|
+
];
|
|
6408
|
+
if (unsafeTargets.includes(obj)) {
|
|
6409
|
+
return;
|
|
6410
|
+
}
|
|
6411
|
+
/** Reject property names that could pollute Object.prototype (CWE-1321). */
|
|
6412
|
+
if (key !== '__proto__' && key !== 'constructor' && key !== 'prototype') {
|
|
6413
|
+
obj[key] = value;
|
|
6414
|
+
}
|
|
6415
|
+
}
|
|
6317
6416
|
const collectIds = (obj, maxDepth = DEFAULT_MAX_RECURSION_DEPTH) => {
|
|
6318
6417
|
const ids = [];
|
|
6319
6418
|
function walk(node, scope, _depth = 0) {
|
|
@@ -6479,7 +6578,8 @@ const resolveReference = (base, ref) => {
|
|
|
6479
6578
|
}
|
|
6480
6579
|
let baseDir = baseNoFrag;
|
|
6481
6580
|
if (!baseDir.endsWith('/')) {
|
|
6482
|
-
|
|
6581
|
+
const lastSlash = baseDir.lastIndexOf('/');
|
|
6582
|
+
baseDir = lastSlash === -1 ? '' : baseDir.slice(0, lastSlash + 1);
|
|
6483
6583
|
}
|
|
6484
6584
|
return baseDir + ref;
|
|
6485
6585
|
};
|
|
@@ -6550,8 +6650,14 @@ class SchemaCompiler {
|
|
|
6550
6650
|
this.collectAndCacheIds(schema);
|
|
6551
6651
|
}
|
|
6552
6652
|
}
|
|
6653
|
+
const canMutateSchemaObject = schema !== Object.prototype &&
|
|
6654
|
+
schema !== Function.prototype &&
|
|
6655
|
+
schema !== Array.prototype;
|
|
6553
6656
|
// if we have an id than it should be cached already (if this instance has compiled it)
|
|
6554
|
-
if (
|
|
6657
|
+
if (canMutateSchemaObject &&
|
|
6658
|
+
schema.__$compiled &&
|
|
6659
|
+
schema.id &&
|
|
6660
|
+
this.validator.scache.checkCacheForUri(schema.id) === false) {
|
|
6555
6661
|
schema.__$compiled = undefined;
|
|
6556
6662
|
}
|
|
6557
6663
|
// do not re-compile schemas
|
|
@@ -6559,7 +6665,7 @@ class SchemaCompiler {
|
|
|
6559
6665
|
return true;
|
|
6560
6666
|
}
|
|
6561
6667
|
// v8 - if $schema is not present, set $schema to default
|
|
6562
|
-
if (!schema.$schema && this.validator.options.version !== 'none') {
|
|
6668
|
+
if (canMutateSchemaObject && !schema.$schema && this.validator.options.version !== 'none') {
|
|
6563
6669
|
schema.$schema = this.validator.getDefaultSchemaId();
|
|
6564
6670
|
}
|
|
6565
6671
|
if (schema.id && typeof schema.id === 'string' && !options?.noCache) {
|
|
@@ -6574,7 +6680,9 @@ class SchemaCompiler {
|
|
|
6574
6680
|
}
|
|
6575
6681
|
// delete all __$missingReferences from previous compilation attempts
|
|
6576
6682
|
const isValidExceptReferences = report.isValid();
|
|
6577
|
-
|
|
6683
|
+
if (canMutateSchemaObject) {
|
|
6684
|
+
delete schema.__$missingReferences;
|
|
6685
|
+
}
|
|
6578
6686
|
// collect all references that need to be resolved - $ref and $schema
|
|
6579
6687
|
const useRefObjectScope = this.validator.options.version === 'draft2019-09' || this.validator.options.version === 'draft2020-12';
|
|
6580
6688
|
const refs = collectReferences(schema, undefined, undefined, undefined, { useRefObjectScope }, this.validator.options.maxRecursionDepth);
|
|
@@ -6622,17 +6730,17 @@ class SchemaCompiler {
|
|
|
6622
6730
|
report.addError('UNRESOLVABLE_REFERENCE', [refObj.ref]);
|
|
6623
6731
|
report.path = report.path.slice(0, -refObj.path.length);
|
|
6624
6732
|
// pusblish unresolved references out
|
|
6625
|
-
if (isValidExceptReferences) {
|
|
6733
|
+
if (isValidExceptReferences && canMutateSchemaObject) {
|
|
6626
6734
|
schema.__$missingReferences = schema.__$missingReferences || [];
|
|
6627
6735
|
schema.__$missingReferences.push(refObj);
|
|
6628
6736
|
}
|
|
6629
6737
|
}
|
|
6630
6738
|
}
|
|
6631
6739
|
// this might create circular references
|
|
6632
|
-
refObj.obj
|
|
6740
|
+
safeSetProperty(refObj.obj, `__${refObj.key}Resolved`, response);
|
|
6633
6741
|
}
|
|
6634
6742
|
const isValid = report.isValid();
|
|
6635
|
-
if (isValid) {
|
|
6743
|
+
if (isValid && canMutateSchemaObject) {
|
|
6636
6744
|
schema.__$compiled = true;
|
|
6637
6745
|
}
|
|
6638
6746
|
// else {
|
|
@@ -6665,7 +6773,7 @@ class SchemaCompiler {
|
|
|
6665
6773
|
const response = arr.find((x) => x.id === refObj.ref);
|
|
6666
6774
|
if (response) {
|
|
6667
6775
|
// this might create circular references
|
|
6668
|
-
refObj.obj
|
|
6776
|
+
safeSetProperty(refObj.obj, `__${refObj.key}Resolved`, response);
|
|
6669
6777
|
// it's resolved now so delete it
|
|
6670
6778
|
sch.__$missingReferences.splice(idx2, 1);
|
|
6671
6779
|
}
|
|
@@ -7429,6 +7537,16 @@ class ZSchemaBase {
|
|
|
7429
7537
|
this.sv = new SchemaValidator(this);
|
|
7430
7538
|
this.options = normalizeOptions(options);
|
|
7431
7539
|
}
|
|
7540
|
+
/**
|
|
7541
|
+
* Internal recursive JSON validation — delegates to the `validate` function
|
|
7542
|
+
* in `json-validation.ts`. Exposed as a method so that per-keyword validator
|
|
7543
|
+
* modules (array, combinators, object) can call back into the core validator
|
|
7544
|
+
* via `this` without importing `json-validation.ts` directly (which would
|
|
7545
|
+
* create a circular dependency).
|
|
7546
|
+
*/
|
|
7547
|
+
_jsonValidate(report, schema, json) {
|
|
7548
|
+
return validate.call(this, report, schema, json);
|
|
7549
|
+
}
|
|
7432
7550
|
getDefaultSchemaId() {
|
|
7433
7551
|
return this.options.version && this.options.version !== 'none'
|
|
7434
7552
|
? VERSION_SCHEMA_URL_MAPPING[this.options.version]
|