z-schema 10.0.0 → 12.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 +35 -17
- package/cjs/index.d.ts +345 -34
- package/cjs/index.js +4446 -1685
- package/dist/errors.js +5 -0
- package/dist/format-validators.js +131 -107
- package/dist/json-schema-versions.js +4 -1
- package/dist/json-schema.js +50 -16
- package/dist/json-validation.js +524 -669
- package/dist/report.js +37 -16
- package/dist/schema-cache.js +76 -18
- package/dist/schema-compiler.js +72 -47
- package/dist/schema-validator.js +117 -52
- package/dist/schemas/draft-07-schema.json +172 -0
- package/dist/schemas/draft-2019-09-meta-applicator.json +52 -0
- package/dist/schemas/draft-2019-09-meta-content.json +12 -0
- package/dist/schemas/draft-2019-09-meta-core.json +53 -0
- package/dist/schemas/draft-2019-09-meta-format.json +10 -0
- package/dist/schemas/draft-2019-09-meta-meta-data.json +32 -0
- package/dist/schemas/draft-2019-09-meta-validation.json +94 -0
- package/dist/schemas/draft-2019-09-schema.json +41 -0
- package/dist/schemas/draft-2020-12-meta-applicator.json +44 -0
- package/dist/schemas/draft-2020-12-meta-content.json +12 -0
- package/dist/schemas/draft-2020-12-meta-core.json +47 -0
- package/dist/schemas/draft-2020-12-meta-format-annotation.json +10 -0
- package/dist/schemas/draft-2020-12-meta-format-assertion.json +10 -0
- package/dist/schemas/draft-2020-12-meta-meta-data.json +32 -0
- package/dist/schemas/draft-2020-12-meta-unevaluated.json +11 -0
- package/dist/schemas/draft-2020-12-meta-validation.json +94 -0
- package/dist/schemas/draft-2020-12-schema.json +57 -0
- package/dist/types/errors.d.ts +4 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/json-schema-versions.d.ts +128 -9
- package/dist/types/json-schema.d.ts +28 -11
- package/dist/types/json-validation.d.ts +2 -3
- package/dist/types/report.d.ts +14 -4
- package/dist/types/schema-cache.d.ts +7 -0
- package/dist/types/schema-compiler.d.ts +5 -3
- package/dist/types/schema-validator.d.ts +2 -2
- package/dist/types/utils/array.d.ts +8 -1
- package/dist/types/utils/base64.d.ts +2 -0
- package/dist/types/utils/clone.d.ts +1 -1
- package/dist/types/utils/date.d.ts +1 -0
- package/dist/types/utils/hostname.d.ts +2 -0
- package/dist/types/utils/json.d.ts +2 -1
- package/dist/types/utils/properties.d.ts +0 -1
- package/dist/types/utils/time.d.ts +12 -0
- package/dist/types/utils/unicode.d.ts +3 -12
- package/dist/types/validation/array.d.ts +12 -0
- package/dist/types/validation/combinators.d.ts +10 -0
- package/dist/types/validation/numeric.d.ts +8 -0
- package/dist/types/validation/object.d.ts +13 -0
- package/dist/types/validation/ref.d.ts +11 -0
- package/dist/types/validation/shared.d.ts +26 -0
- package/dist/types/validation/string.d.ts +9 -0
- package/dist/types/validation/type.d.ts +6 -0
- package/dist/types/z-schema-base.d.ts +39 -1
- package/dist/types/z-schema-options.d.ts +3 -0
- package/dist/types/z-schema.d.ts +144 -8
- package/dist/utils/array.js +49 -7
- package/dist/utils/base64.js +29 -0
- package/dist/utils/clone.js +13 -12
- package/dist/utils/date.js +21 -0
- package/dist/utils/hostname.js +146 -0
- package/dist/utils/json.js +11 -6
- package/dist/utils/properties.js +1 -6
- package/dist/utils/time.js +50 -0
- package/dist/utils/unicode.js +8 -41
- package/dist/utils/uri.js +1 -1
- package/dist/validation/array.js +128 -0
- package/dist/validation/combinators.js +107 -0
- package/dist/validation/numeric.js +97 -0
- package/dist/validation/object.js +238 -0
- package/dist/validation/ref.js +70 -0
- package/dist/validation/shared.js +136 -0
- package/dist/validation/string.js +178 -0
- package/dist/validation/type.js +55 -0
- package/dist/z-schema-base.js +52 -32
- package/dist/z-schema-options.js +12 -8
- package/dist/z-schema-versions.js +92 -9
- package/dist/z-schema.js +135 -38
- package/package.json +22 -8
- package/src/errors.ts +8 -0
- package/src/format-validators.ts +146 -105
- package/src/index.ts +10 -1
- package/src/json-schema-versions.ts +181 -11
- package/src/json-schema.ts +102 -35
- package/src/json-validation.ts +653 -724
- package/src/report.ts +42 -20
- package/src/schema-cache.ts +94 -18
- package/src/schema-compiler.ts +94 -51
- package/src/schema-validator.ts +132 -56
- package/src/schemas/draft-07-schema.json +172 -0
- package/src/schemas/draft-2019-09-meta-applicator.json +53 -0
- package/src/schemas/draft-2019-09-meta-content.json +14 -0
- package/src/schemas/draft-2019-09-meta-core.json +54 -0
- package/src/schemas/draft-2019-09-meta-format.json +11 -0
- package/src/schemas/draft-2019-09-meta-meta-data.json +34 -0
- package/src/schemas/draft-2019-09-meta-validation.json +95 -0
- package/src/schemas/draft-2019-09-schema.json +42 -0
- package/src/schemas/draft-2020-12-meta-applicator.json +45 -0
- package/src/schemas/draft-2020-12-meta-content.json +14 -0
- package/src/schemas/draft-2020-12-meta-core.json +48 -0
- package/src/schemas/draft-2020-12-meta-format-annotation.json +11 -0
- package/src/schemas/draft-2020-12-meta-format-assertion.json +11 -0
- package/src/schemas/draft-2020-12-meta-meta-data.json +34 -0
- package/src/schemas/draft-2020-12-meta-unevaluated.json +12 -0
- package/src/schemas/draft-2020-12-meta-validation.json +95 -0
- package/src/schemas/draft-2020-12-schema.json +58 -0
- package/src/utils/array.ts +51 -7
- package/src/utils/base64.ts +32 -0
- package/src/utils/clone.ts +16 -12
- package/src/utils/date.ts +23 -0
- package/src/utils/hostname.ts +174 -0
- package/src/utils/json.ts +15 -6
- package/src/utils/properties.ts +1 -7
- package/src/utils/time.ts +73 -0
- package/src/utils/unicode.ts +8 -39
- package/src/utils/uri.ts +1 -1
- package/src/validation/array.ts +158 -0
- package/src/validation/combinators.ts +132 -0
- package/src/validation/numeric.ts +120 -0
- package/src/validation/object.ts +318 -0
- package/src/validation/ref.ts +85 -0
- package/src/validation/shared.ts +191 -0
- package/src/validation/string.ts +224 -0
- package/src/validation/type.ts +66 -0
- package/src/z-schema-base.ts +54 -36
- package/src/z-schema-options.ts +15 -8
- package/src/z-schema-versions.ts +107 -12
- package/src/z-schema.ts +158 -42
- package/umd/ZSchema.js +4446 -1685
- package/umd/ZSchema.min.js +1 -1
- package/dist/schemas/draft-04-hyper-schema.json +0 -135
- package/dist/schemas/draft-06-hyper-schema.json +0 -132
- package/dist/schemas/draft-06-links.json +0 -43
- package/src/schemas/draft-04-hyper-schema.json +0 -136
- package/src/schemas/draft-06-hyper-schema.json +0 -133
- package/src/schemas/draft-06-links.json +0 -43
package/dist/json-validation.js
CHANGED
|
@@ -1,698 +1,461 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getId } from './json-schema.js';
|
|
2
2
|
import { Report } from './report.js';
|
|
3
|
-
import { difference, isUniqueArray } from './utils/array.js';
|
|
4
|
-
import { shallowClone } from './utils/clone.js';
|
|
5
|
-
import { areEqual } from './utils/json.js';
|
|
6
|
-
import { hasOwn } from './utils/properties.js';
|
|
7
3
|
import { compileSchemaRegex } from './utils/schema-regex.js';
|
|
8
|
-
import { ucs2decode } from './utils/unicode.js';
|
|
9
4
|
import { isObject, whatIs } from './utils/what-is.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const result = json / schema.multipleOf;
|
|
34
|
-
if (!Number.isFinite(result) || Math.abs(result - Math.round(result)) >= 1e-10) {
|
|
35
|
-
report.addError('MULTIPLE_OF', [json, schema.multipleOf], undefined, schema, 'multipleOf');
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
maximum: function (report, schema, json) {
|
|
39
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.1.2.2
|
|
40
|
-
if (shouldSkipValidate(this.validateOptions, ['MAXIMUM', 'MAXIMUM_EXCLUSIVE'])) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (typeof json !== 'number') {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
if (schema.exclusiveMaximum !== true) {
|
|
47
|
-
if (json > schema.maximum) {
|
|
48
|
-
report.addError('MAXIMUM', [json, schema.maximum], undefined, schema, 'maximum');
|
|
49
|
-
}
|
|
5
|
+
import { additionalItemsValidator, containsValidator, itemsValidator, maxContainsValidator, maxItemsValidator, minContainsValidator, minItemsValidator, prefixItemsValidator, uniqueItemsValidator, } from './validation/array.js';
|
|
6
|
+
import { allOfValidator, anyOfValidator, elseValidator, ifValidator, notValidator, oneOfValidator, thenValidator, } from './validation/combinators.js';
|
|
7
|
+
import { exclusiveMaximumValidator, exclusiveMinimumValidator, maximumValidator, minimumValidator, multipleOfValidator, } from './validation/numeric.js';
|
|
8
|
+
import { additionalPropertiesValidator, dependenciesValidator, dependentRequiredValidator, dependentSchemasValidator, maxPropertiesValidator, minPropertiesValidator, patternPropertiesValidator, propertiesValidator, propertyNamesValidator, requiredValidator, } from './validation/object.js';
|
|
9
|
+
import { resolveDynamicRef, resolveRecursiveRef } from './validation/ref.js';
|
|
10
|
+
import { getCachedValidationResult, isValidationVocabularyEnabled, VALIDATION_VOCAB_KEYWORDS, } from './validation/shared.js';
|
|
11
|
+
import { contentEncodingValidator, contentMediaTypeValidator, formatValidator, maxLengthValidator, minLengthValidator, patternValidator, } from './validation/string.js';
|
|
12
|
+
import { constValidator, enumValidator, typeValidator } from './validation/type.js';
|
|
13
|
+
function collectEvaluated(args) {
|
|
14
|
+
const { report, currentSchema, json, mode, depth } = args;
|
|
15
|
+
if (!currentSchema || typeof currentSchema === 'boolean') {
|
|
16
|
+
return new Set();
|
|
17
|
+
}
|
|
18
|
+
if (depth > (this.options.maxRecursionDepth ?? 100)) {
|
|
19
|
+
report.addError('COLLECT_EVALUATED_DEPTH_EXCEEDED', [depth]);
|
|
20
|
+
return new Set();
|
|
21
|
+
}
|
|
22
|
+
const evaluated = new Set();
|
|
23
|
+
const merge = (other) => {
|
|
24
|
+
if (other === 'all')
|
|
25
|
+
return true;
|
|
26
|
+
for (const v of other) {
|
|
27
|
+
evaluated.add(v);
|
|
50
28
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
const recurse = (subSchema) => {
|
|
32
|
+
if (mode === 'items') {
|
|
33
|
+
return collectEvaluated.call(this, {
|
|
34
|
+
report,
|
|
35
|
+
currentSchema: subSchema,
|
|
36
|
+
json,
|
|
37
|
+
mode: 'items',
|
|
38
|
+
jsonArr: args.jsonArr,
|
|
39
|
+
depth: depth + 1,
|
|
40
|
+
});
|
|
55
41
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
42
|
+
return collectEvaluated.call(this, {
|
|
43
|
+
report,
|
|
44
|
+
currentSchema: subSchema,
|
|
45
|
+
json,
|
|
46
|
+
mode: 'properties',
|
|
47
|
+
jsonData: args.jsonData,
|
|
48
|
+
depth: depth + 1,
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
// --- Mode-specific leaf collection ---
|
|
52
|
+
if (mode === 'items') {
|
|
53
|
+
const jsonArr = args.jsonArr;
|
|
54
|
+
// prefixItems (2020-12 tuple)
|
|
55
|
+
if (Array.isArray(currentSchema.prefixItems)) {
|
|
56
|
+
const len = Math.min(currentSchema.prefixItems.length, jsonArr.length);
|
|
57
|
+
for (let i = 0; i < len; i++) {
|
|
58
|
+
evaluated.add(i);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// items - can be array (2019-09 tuple) or schema (evaluates all)
|
|
62
|
+
if (currentSchema.items !== undefined) {
|
|
63
|
+
if (Array.isArray(currentSchema.items)) {
|
|
64
|
+
const len = Math.min(currentSchema.items.length, jsonArr.length);
|
|
65
|
+
for (let i = 0; i < len; i++) {
|
|
66
|
+
evaluated.add(i);
|
|
67
|
+
}
|
|
65
68
|
}
|
|
66
|
-
if (
|
|
67
|
-
|
|
69
|
+
else if (currentSchema.items !== false) {
|
|
70
|
+
return 'all';
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
if (typeof json !== 'number') {
|
|
78
|
-
return;
|
|
73
|
+
// additionalItems (2019-09) - when items is array form and additionalItems is present and not false
|
|
74
|
+
if (currentSchema.additionalItems !== undefined &&
|
|
75
|
+
currentSchema.additionalItems !== false &&
|
|
76
|
+
Array.isArray(currentSchema.items)) {
|
|
77
|
+
return 'all';
|
|
79
78
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// In draft-06+, exclusiveMinimum is a standalone number
|
|
93
|
-
if (typeof schema.exclusiveMinimum === 'number') {
|
|
94
|
-
if (shouldSkipValidate(this.validateOptions, ['MINIMUM_EXCLUSIVE'])) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (typeof json !== 'number') {
|
|
98
|
-
return;
|
|
79
|
+
// contains - evaluates specific indices that match the schema
|
|
80
|
+
if (currentSchema.contains !== undefined) {
|
|
81
|
+
for (let i = 0; i < jsonArr.length; i++) {
|
|
82
|
+
let passed = getCachedValidationResult(report, currentSchema.contains, jsonArr[i]);
|
|
83
|
+
if (passed === undefined) {
|
|
84
|
+
const subReport = new Report(report);
|
|
85
|
+
validate.call(this, subReport, currentSchema.contains, jsonArr[i]);
|
|
86
|
+
passed = subReport.errors.length === 0;
|
|
87
|
+
}
|
|
88
|
+
if (passed) {
|
|
89
|
+
evaluated.add(i);
|
|
90
|
+
}
|
|
99
91
|
}
|
|
100
|
-
if (json <= schema.exclusiveMinimum) {
|
|
101
|
-
report.addError('MINIMUM_EXCLUSIVE', [json, schema.exclusiveMinimum], undefined, schema, 'exclusiveMinimum');
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// In draft-04, exclusiveMinimum is a boolean handled inside the `minimum` validator
|
|
105
|
-
},
|
|
106
|
-
maxLength: function (report, schema, json) {
|
|
107
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.2.1.2
|
|
108
|
-
if (shouldSkipValidate(this.validateOptions, ['MAX_LENGTH'])) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
if (typeof json !== 'string') {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (ucs2decode(json).length > schema.maxLength) {
|
|
115
|
-
report.addError('MAX_LENGTH', [json.length, schema.maxLength], undefined, schema, 'maxLength');
|
|
116
92
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (shouldSkipValidate(this.validateOptions, ['MIN_LENGTH'])) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
if (typeof json !== 'string') {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
if (ucs2decode(json).length < schema.minLength) {
|
|
127
|
-
report.addError('MIN_LENGTH', [json.length, schema.minLength], undefined, schema, 'minLength');
|
|
93
|
+
// unevaluatedItems: true means all items evaluated
|
|
94
|
+
if (currentSchema.unevaluatedItems === true) {
|
|
95
|
+
return 'all';
|
|
128
96
|
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const result = compileSchemaRegex(schema.pattern);
|
|
139
|
-
if (!result.ok) {
|
|
140
|
-
// Should not happen: schema should have been validated already
|
|
141
|
-
report.addError('PATTERN', [schema.pattern, json, result.error.message], undefined, schema, 'pattern');
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (!result.value.test(json)) {
|
|
145
|
-
report.addError('PATTERN', [schema.pattern, json], undefined, schema, 'pattern');
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
additionalItems: function (report, schema, json) {
|
|
149
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.1.2
|
|
150
|
-
if (shouldSkipValidate(this.validateOptions, ['ARRAY_ADDITIONAL_ITEMS'])) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (!Array.isArray(json)) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
// if the value of "additionalItems" is boolean value false and the value of "items" is an array,
|
|
157
|
-
// the json is valid if its size is less than, or equal to, the size of "items".
|
|
158
|
-
if (schema.additionalItems === false && Array.isArray(schema.items)) {
|
|
159
|
-
if (json.length > schema.items.length) {
|
|
160
|
-
report.addError('ARRAY_ADDITIONAL_ITEMS', undefined, undefined, schema, 'additionalItems');
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
items: function () {
|
|
165
|
-
/*report: Report, schema: JsonSchemaInternal, json: unknown*/
|
|
166
|
-
// covered in additionalItems
|
|
167
|
-
},
|
|
168
|
-
maxItems: function (report, schema, json) {
|
|
169
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.2.2
|
|
170
|
-
if (shouldSkipValidate(this.validateOptions, ['ARRAY_LENGTH_LONG'])) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (!Array.isArray(json)) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (json.length > schema.maxItems) {
|
|
177
|
-
report.addError('ARRAY_LENGTH_LONG', [json.length, schema.maxItems], undefined, schema, 'maxItems');
|
|
178
|
-
}
|
|
179
|
-
},
|
|
180
|
-
minItems: function (report, schema, json) {
|
|
181
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.3.2
|
|
182
|
-
if (shouldSkipValidate(this.validateOptions, ['ARRAY_LENGTH_SHORT'])) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
if (!Array.isArray(json)) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
if (json.length < schema.minItems) {
|
|
189
|
-
report.addError('ARRAY_LENGTH_SHORT', [json.length, schema.minItems], undefined, schema, 'minItems');
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
uniqueItems: function (report, schema, json) {
|
|
193
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.4.2
|
|
194
|
-
if (shouldSkipValidate(this.validateOptions, ['ARRAY_UNIQUE'])) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (!Array.isArray(json)) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
if (schema.uniqueItems === true) {
|
|
201
|
-
const matches = [];
|
|
202
|
-
if (isUniqueArray(json, matches) === false) {
|
|
203
|
-
report.addError('ARRAY_UNIQUE', matches, undefined, schema, 'uniqueItems');
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
},
|
|
207
|
-
maxProperties: function (report, schema, json) {
|
|
208
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.1.2
|
|
209
|
-
if (shouldSkipValidate(this.validateOptions, ['OBJECT_PROPERTIES_MAXIMUM'])) {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
if (!isObject(json)) {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
const keysCount = Object.keys(json).length;
|
|
216
|
-
if (keysCount > schema.maxProperties) {
|
|
217
|
-
report.addError('OBJECT_PROPERTIES_MAXIMUM', [keysCount, schema.maxProperties], undefined, schema, 'maxProperties');
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
minProperties: function (report, schema, json) {
|
|
221
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.2.2
|
|
222
|
-
if (shouldSkipValidate(this.validateOptions, ['OBJECT_PROPERTIES_MINIMUM'])) {
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
if (!isObject(json)) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
const keysCount = Object.keys(json).length;
|
|
229
|
-
if (keysCount < schema.minProperties) {
|
|
230
|
-
report.addError('OBJECT_PROPERTIES_MINIMUM', [keysCount, schema.minProperties], undefined, schema, 'minProperties');
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
required: function (report, schema, json) {
|
|
234
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.3.2
|
|
235
|
-
if (shouldSkipValidate(this.validateOptions, ['OBJECT_MISSING_REQUIRED_PROPERTY'])) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
if (!isObject(json)) {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
let idx = schema.required.length;
|
|
242
|
-
while (idx--) {
|
|
243
|
-
const requiredPropertyName = schema.required[idx];
|
|
244
|
-
if (!hasOwn(json, requiredPropertyName)) {
|
|
245
|
-
report.addError('OBJECT_MISSING_REQUIRED_PROPERTY', [requiredPropertyName], undefined, schema, 'required');
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
},
|
|
249
|
-
additionalProperties: function (report, schema, json) {
|
|
250
|
-
// covered in properties and patternProperties
|
|
251
|
-
if (schema.properties === undefined && schema.patternProperties === undefined) {
|
|
252
|
-
return JsonValidators.properties.call(this, report, schema, json);
|
|
253
|
-
}
|
|
254
|
-
},
|
|
255
|
-
patternProperties: function (report, schema, json) {
|
|
256
|
-
// covered in properties
|
|
257
|
-
if (schema.properties === undefined) {
|
|
258
|
-
return JsonValidators.properties.call(this, report, schema, json);
|
|
259
|
-
}
|
|
260
|
-
},
|
|
261
|
-
properties: function (report, schema, json) {
|
|
262
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.4.2
|
|
263
|
-
if (shouldSkipValidate(this.validateOptions, ['OBJECT_ADDITIONAL_PROPERTIES'])) {
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
if (!isObject(json)) {
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
const properties = schema.properties !== undefined ? schema.properties : {};
|
|
270
|
-
const patternProperties = schema.patternProperties !== undefined ? schema.patternProperties : {};
|
|
271
|
-
if (schema.additionalProperties === false) {
|
|
272
|
-
// The property set of the json to validate.
|
|
273
|
-
let s = Object.keys(json);
|
|
274
|
-
// The property set from "properties".
|
|
275
|
-
const p = Object.keys(properties);
|
|
276
|
-
// The property set from "patternProperties".
|
|
277
|
-
const pp = Object.keys(patternProperties);
|
|
278
|
-
// remove from "s" all elements of "p", if any;
|
|
279
|
-
s = difference(s, p);
|
|
280
|
-
// for each regex in "pp", remove all elements of "s" which this regex matches.
|
|
281
|
-
let idx = pp.length;
|
|
282
|
-
while (idx--) {
|
|
283
|
-
const result = compileSchemaRegex(pp[idx]);
|
|
284
|
-
if (!result.ok) {
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
const regExp = result.value;
|
|
288
|
-
let idx2 = s.length;
|
|
289
|
-
while (idx2--) {
|
|
290
|
-
if (regExp.test(s[idx2]) === true) {
|
|
291
|
-
s.splice(idx2, 1);
|
|
292
|
-
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// mode === 'properties'
|
|
100
|
+
const jsonData = args.jsonData;
|
|
101
|
+
// properties
|
|
102
|
+
if (isObject(currentSchema.properties)) {
|
|
103
|
+
for (const key of Object.keys(currentSchema.properties)) {
|
|
104
|
+
if (Object.hasOwn(jsonData, key)) {
|
|
105
|
+
evaluated.add(key);
|
|
293
106
|
}
|
|
294
107
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
s.splice(io, 1);
|
|
305
|
-
}
|
|
108
|
+
}
|
|
109
|
+
// patternProperties
|
|
110
|
+
if (isObject(currentSchema.patternProperties)) {
|
|
111
|
+
for (const pattern of Object.keys(currentSchema.patternProperties)) {
|
|
112
|
+
const result = compileSchemaRegex(pattern);
|
|
113
|
+
if (result.ok) {
|
|
114
|
+
for (const key of Object.keys(jsonData)) {
|
|
115
|
+
if (result.value.test(key)) {
|
|
116
|
+
evaluated.add(key);
|
|
306
117
|
}
|
|
307
118
|
}
|
|
308
119
|
}
|
|
309
|
-
let idx4 = s.length;
|
|
310
|
-
if (idx4) {
|
|
311
|
-
while (idx4--) {
|
|
312
|
-
report.addError('OBJECT_ADDITIONAL_PROPERTIES', [s[idx4]], undefined, schema, 'properties');
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
120
|
}
|
|
316
121
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const keys = Object.keys(schema.dependencies);
|
|
327
|
-
let idx = keys.length;
|
|
328
|
-
while (idx--) {
|
|
329
|
-
// iterate all dependencies
|
|
330
|
-
const dependencyName = keys[idx];
|
|
331
|
-
if (hasOwn(json, dependencyName)) {
|
|
332
|
-
const dependencyDefinition = schema.dependencies[dependencyName];
|
|
333
|
-
if (Array.isArray(dependencyDefinition)) {
|
|
334
|
-
// Array
|
|
335
|
-
// if dependency is an array, object needs to have all properties in this array
|
|
336
|
-
let idx2 = dependencyDefinition.length;
|
|
337
|
-
while (idx2--) {
|
|
338
|
-
const requiredPropertyName = dependencyDefinition[idx2];
|
|
339
|
-
if (!hasOwn(json, requiredPropertyName)) {
|
|
340
|
-
report.addError('OBJECT_DEPENDENCY_KEY', [requiredPropertyName, dependencyName], undefined, schema, 'dependencies');
|
|
341
|
-
}
|
|
122
|
+
// additionalProperties - evaluates all non-properties/non-patternProperties keys
|
|
123
|
+
if (currentSchema.additionalProperties !== undefined) {
|
|
124
|
+
const propKeys = isObject(currentSchema.properties) ? Object.keys(currentSchema.properties) : [];
|
|
125
|
+
const patternRegexes = [];
|
|
126
|
+
if (isObject(currentSchema.patternProperties)) {
|
|
127
|
+
for (const pattern of Object.keys(currentSchema.patternProperties)) {
|
|
128
|
+
const result = compileSchemaRegex(pattern);
|
|
129
|
+
if (result.ok) {
|
|
130
|
+
patternRegexes.push(result.value);
|
|
342
131
|
}
|
|
343
132
|
}
|
|
344
|
-
else {
|
|
345
|
-
// if dependency is a schema, validate against this schema
|
|
346
|
-
validate.call(this, report, dependencyDefinition, json);
|
|
347
|
-
}
|
|
348
133
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
let match = false, caseInsensitiveMatch = false, idx = schema.enum.length;
|
|
357
|
-
while (idx--) {
|
|
358
|
-
if (areEqual(json, schema.enum[idx])) {
|
|
359
|
-
match = true;
|
|
360
|
-
break;
|
|
361
|
-
}
|
|
362
|
-
else if (areEqual(json, schema.enum[idx], { caseInsensitiveComparison: true })) {
|
|
363
|
-
caseInsensitiveMatch = true;
|
|
134
|
+
for (const key of Object.keys(jsonData)) {
|
|
135
|
+
if (propKeys.includes(key))
|
|
136
|
+
continue;
|
|
137
|
+
if (patternRegexes.some((re) => re.test(key)))
|
|
138
|
+
continue;
|
|
139
|
+
evaluated.add(key);
|
|
364
140
|
}
|
|
365
141
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
const jsonType = whatIs(json);
|
|
377
|
-
if (typeof schema.type === 'string') {
|
|
378
|
-
if (jsonType !== schema.type && (jsonType !== 'integer' || schema.type !== 'number')) {
|
|
379
|
-
report.addError('INVALID_TYPE', [schema.type, jsonType], undefined, schema, 'type');
|
|
142
|
+
// dependentSchemas - only applies when the dependency key is present in the data
|
|
143
|
+
if (isObject(currentSchema.dependentSchemas)) {
|
|
144
|
+
for (const [depKey, depSchema] of Object.entries(currentSchema.dependentSchemas)) {
|
|
145
|
+
if (Object.hasOwn(jsonData, depKey)) {
|
|
146
|
+
if (merge(recurse(depSchema))) {
|
|
147
|
+
return 'all';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
380
150
|
}
|
|
381
151
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
152
|
+
// unevaluatedProperties: true in a sub-schema means all props are evaluated
|
|
153
|
+
if (currentSchema.unevaluatedProperties === true) {
|
|
154
|
+
return 'all';
|
|
386
155
|
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
break;
|
|
156
|
+
}
|
|
157
|
+
// --- Shared combinator traversal ---
|
|
158
|
+
// allOf
|
|
159
|
+
if (Array.isArray(currentSchema.allOf)) {
|
|
160
|
+
for (const subSchema of currentSchema.allOf) {
|
|
161
|
+
if (merge(recurse(subSchema))) {
|
|
162
|
+
return 'all';
|
|
395
163
|
}
|
|
396
164
|
}
|
|
397
|
-
}
|
|
398
|
-
anyOf
|
|
399
|
-
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
report.asyncTasks.push(...subReport.asyncTasks);
|
|
411
|
-
}
|
|
412
|
-
const hasAsyncTasks = report.asyncTasks.length > asyncTasksBefore;
|
|
413
|
-
if (hasAsyncTasks) {
|
|
414
|
-
// Defer the decision until async tasks complete
|
|
415
|
-
const pathBeforeAsync = shallowClone(report.path);
|
|
416
|
-
report.addAsyncTask((callback) => {
|
|
417
|
-
setTimeout(() => callback(null), 0);
|
|
418
|
-
}, [], () => {
|
|
419
|
-
const backup = report.path;
|
|
420
|
-
report.path = pathBeforeAsync;
|
|
421
|
-
let passed = false;
|
|
422
|
-
for (const subReport of subReports) {
|
|
423
|
-
if (subReport.errors.length === 0) {
|
|
424
|
-
passed = true;
|
|
425
|
-
break;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (passed === false) {
|
|
429
|
-
report.addError('ANY_OF_MISSING', undefined, subReports, schema, 'anyOf');
|
|
165
|
+
}
|
|
166
|
+
// anyOf - only matching branches contribute
|
|
167
|
+
if (Array.isArray(currentSchema.anyOf)) {
|
|
168
|
+
for (const subSchema of currentSchema.anyOf) {
|
|
169
|
+
let passed = getCachedValidationResult(report, subSchema, json);
|
|
170
|
+
if (passed === undefined) {
|
|
171
|
+
const subReport = new Report(report);
|
|
172
|
+
validate.call(this, subReport, subSchema, json);
|
|
173
|
+
passed = subReport.errors.length === 0;
|
|
174
|
+
}
|
|
175
|
+
if (passed) {
|
|
176
|
+
if (merge(recurse(subSchema))) {
|
|
177
|
+
return 'all';
|
|
430
178
|
}
|
|
431
|
-
|
|
432
|
-
});
|
|
179
|
+
}
|
|
433
180
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
181
|
+
}
|
|
182
|
+
// oneOf - only matching branches contribute
|
|
183
|
+
if (Array.isArray(currentSchema.oneOf)) {
|
|
184
|
+
for (const subSchema of currentSchema.oneOf) {
|
|
185
|
+
let passed = getCachedValidationResult(report, subSchema, json);
|
|
186
|
+
if (passed === undefined) {
|
|
187
|
+
const subReport = new Report(report);
|
|
188
|
+
validate.call(this, subReport, subSchema, json);
|
|
189
|
+
passed = subReport.errors.length === 0;
|
|
190
|
+
}
|
|
191
|
+
if (passed) {
|
|
192
|
+
if (merge(recurse(subSchema))) {
|
|
193
|
+
return 'all';
|
|
441
194
|
}
|
|
442
195
|
}
|
|
443
|
-
if (passed === false) {
|
|
444
|
-
report.addError('ANY_OF_MISSING', undefined, subReports, schema, 'anyOf');
|
|
445
|
-
}
|
|
446
196
|
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if (hasAsyncTasks) {
|
|
464
|
-
// Defer the decision until async tasks complete
|
|
465
|
-
const pathBeforeAsync = shallowClone(report.path);
|
|
466
|
-
report.addAsyncTask((callback) => {
|
|
467
|
-
// This task runs after all async tasks, so we can check final state
|
|
468
|
-
setTimeout(() => callback(null), 0);
|
|
469
|
-
}, [], () => {
|
|
470
|
-
const backup = report.path;
|
|
471
|
-
report.path = pathBeforeAsync;
|
|
472
|
-
let passes = 0;
|
|
473
|
-
for (const subReport of subReports) {
|
|
474
|
-
if (subReport.errors.length === 0) {
|
|
475
|
-
passes++;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
if (passes === 0) {
|
|
479
|
-
report.addError('ONE_OF_MISSING', undefined, subReports, schema, 'oneOf');
|
|
480
|
-
}
|
|
481
|
-
else if (passes > 1) {
|
|
482
|
-
report.addError('ONE_OF_MULTIPLE', undefined, undefined, schema, 'oneOf');
|
|
197
|
+
}
|
|
198
|
+
// if/then/else
|
|
199
|
+
if (currentSchema.if !== undefined) {
|
|
200
|
+
let condPassed = getCachedValidationResult(report, currentSchema.if, json);
|
|
201
|
+
if (condPassed === undefined) {
|
|
202
|
+
const condReport = new Report(report);
|
|
203
|
+
validate.call(this, condReport, currentSchema.if, json);
|
|
204
|
+
condPassed = condReport.errors.length === 0;
|
|
205
|
+
}
|
|
206
|
+
if (condPassed) {
|
|
207
|
+
if (merge(recurse(currentSchema.if))) {
|
|
208
|
+
return 'all';
|
|
209
|
+
}
|
|
210
|
+
if (currentSchema.then !== undefined) {
|
|
211
|
+
if (merge(recurse(currentSchema.then))) {
|
|
212
|
+
return 'all';
|
|
483
213
|
}
|
|
484
|
-
|
|
485
|
-
});
|
|
214
|
+
}
|
|
486
215
|
}
|
|
487
216
|
else {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (subReport.errors.length === 0) {
|
|
492
|
-
passes++;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
if (passes === 0) {
|
|
496
|
-
report.addError('ONE_OF_MISSING', undefined, subReports, schema, 'oneOf');
|
|
497
|
-
}
|
|
498
|
-
else if (passes > 1) {
|
|
499
|
-
report.addError('ONE_OF_MULTIPLE', undefined, undefined, schema, 'oneOf');
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
},
|
|
503
|
-
not: function (report, schema, json) {
|
|
504
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.6.2
|
|
505
|
-
const subReport = new Report(report);
|
|
506
|
-
if (validate.call(this, subReport, schema.not, json) === true) {
|
|
507
|
-
report.addError('NOT_PASSED', undefined, undefined, schema, 'not');
|
|
508
|
-
}
|
|
509
|
-
},
|
|
510
|
-
definitions: function () {
|
|
511
|
-
/*report: Report, schema: JsonSchemaInternal, json: unknown*/
|
|
512
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.7.2
|
|
513
|
-
// nothing to do here
|
|
514
|
-
},
|
|
515
|
-
format: function (report, schema, json) {
|
|
516
|
-
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.7.2
|
|
517
|
-
const formatValidators = getFormatValidators(this.options);
|
|
518
|
-
const formatValidatorFn = formatValidators[schema.format];
|
|
519
|
-
if (typeof formatValidatorFn === 'function') {
|
|
520
|
-
if (shouldSkipValidate(this.validateOptions, ['INVALID_FORMAT'])) {
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
if (report.hasError('INVALID_TYPE', [schema.type, whatIs(json)])) {
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
if (formatValidatorFn.length === 2) {
|
|
527
|
-
// callback-based async - need to clone the path here, because it will change by the time async function reports back
|
|
528
|
-
const pathBeforeAsync = shallowClone(report.path);
|
|
529
|
-
report.addAsyncTask(formatValidatorFn, [json], function (result) {
|
|
530
|
-
if (result !== true) {
|
|
531
|
-
const backup = report.path;
|
|
532
|
-
report.path = pathBeforeAsync;
|
|
533
|
-
report.addError('INVALID_FORMAT', [schema.format, JSON.stringify(json)], undefined, schema, 'format');
|
|
534
|
-
report.path = backup;
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
else {
|
|
539
|
-
const result = formatValidatorFn.call(this, json);
|
|
540
|
-
if (result instanceof Promise) {
|
|
541
|
-
// Promise-based async
|
|
542
|
-
const pathBeforeAsync = shallowClone(report.path);
|
|
543
|
-
const timeoutMs = this.options.asyncTimeout || 2000;
|
|
544
|
-
report.addAsyncTask(async (callback) => {
|
|
545
|
-
try {
|
|
546
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
547
|
-
setTimeout(() => reject(new Error('Async timeout')), timeoutMs);
|
|
548
|
-
});
|
|
549
|
-
const resolved = await Promise.race([result, timeoutPromise]);
|
|
550
|
-
callback(resolved);
|
|
551
|
-
}
|
|
552
|
-
catch (error) {
|
|
553
|
-
if (error.message === 'Async timeout') {
|
|
554
|
-
// Don't call callback, let global timeout handle it
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
callback(false);
|
|
558
|
-
}
|
|
559
|
-
}, [], function (resolvedResult) {
|
|
560
|
-
if (resolvedResult !== true) {
|
|
561
|
-
const backup = report.path;
|
|
562
|
-
report.path = pathBeforeAsync;
|
|
563
|
-
report.addError('INVALID_FORMAT', [schema.format, JSON.stringify(json)], undefined, schema, 'format');
|
|
564
|
-
report.path = backup;
|
|
565
|
-
}
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
else {
|
|
569
|
-
// sync
|
|
570
|
-
if (result !== true) {
|
|
571
|
-
report.addError('INVALID_FORMAT', [schema.format, JSON.stringify(json)], undefined, schema, 'format');
|
|
572
|
-
}
|
|
217
|
+
if (currentSchema.else !== undefined) {
|
|
218
|
+
if (merge(recurse(currentSchema.else))) {
|
|
219
|
+
return 'all';
|
|
573
220
|
}
|
|
574
221
|
}
|
|
575
222
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
$id: () => {
|
|
582
|
-
// TODO: implement
|
|
583
|
-
},
|
|
584
|
-
const: function (report, schema, json) {
|
|
585
|
-
const constValue = schema.const;
|
|
586
|
-
if (areEqual(json, constValue) === false) {
|
|
587
|
-
report.addError('CONST', [JSON.stringify(constValue)], undefined, schema, undefined);
|
|
223
|
+
}
|
|
224
|
+
// $ref resolved
|
|
225
|
+
if (currentSchema.__$refResolved && currentSchema.__$refResolved !== currentSchema) {
|
|
226
|
+
if (merge(recurse(currentSchema.__$refResolved))) {
|
|
227
|
+
return 'all';
|
|
588
228
|
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
229
|
+
}
|
|
230
|
+
// $recursiveRef
|
|
231
|
+
const recursiveTarget = resolveRecursiveRef(currentSchema, report.__$recursiveAnchorStack);
|
|
232
|
+
if (recursiveTarget && recursiveTarget !== currentSchema) {
|
|
233
|
+
if (merge(recurse(recursiveTarget))) {
|
|
234
|
+
return 'all';
|
|
593
235
|
}
|
|
594
|
-
|
|
595
|
-
|
|
236
|
+
}
|
|
237
|
+
// $dynamicRef
|
|
238
|
+
const dynamicTarget = resolveDynamicRef(currentSchema, report.__$dynamicScopeStack);
|
|
239
|
+
if (dynamicTarget && dynamicTarget !== currentSchema) {
|
|
240
|
+
if (merge(recurse(dynamicTarget))) {
|
|
241
|
+
return 'all';
|
|
596
242
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
243
|
+
}
|
|
244
|
+
return evaluated;
|
|
245
|
+
}
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// unevaluatedItems
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
function unevaluatedItemsValidator(report, schema, json) {
|
|
250
|
+
if (!Array.isArray(json)) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
// unevaluatedItems: true means all items are valid
|
|
254
|
+
if (schema.unevaluatedItems === true) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const unevalSchema = schema.unevaluatedItems;
|
|
258
|
+
if (unevalSchema === undefined) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (json.length === 0) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const evaluatedItems = collectEvaluated.call(this, {
|
|
265
|
+
report,
|
|
266
|
+
currentSchema: schema,
|
|
267
|
+
json,
|
|
268
|
+
mode: 'items',
|
|
269
|
+
jsonArr: json,
|
|
270
|
+
depth: 0,
|
|
271
|
+
});
|
|
272
|
+
if (evaluatedItems === 'all') {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const unevaluatedIndices = [];
|
|
276
|
+
for (let i = 0; i < json.length; i++) {
|
|
277
|
+
if (!evaluatedItems.has(i)) {
|
|
278
|
+
unevaluatedIndices.push(i);
|
|
600
279
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
280
|
+
}
|
|
281
|
+
if (unevaluatedIndices.length === 0) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (unevalSchema === false) {
|
|
285
|
+
report.addError('ARRAY_UNEVALUATED_ITEMS', undefined, undefined, schema, 'unevaluatedItems');
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
// unevaluatedItems as a schema — validate each unevaluated item against it
|
|
289
|
+
for (const idx of unevaluatedIndices) {
|
|
604
290
|
const subReport = new Report(report);
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
for (const subReport of subReports) {
|
|
610
|
-
report.asyncTasks.push(...subReport.asyncTasks);
|
|
611
|
-
}
|
|
612
|
-
const hasAsyncTasks = report.asyncTasks.length > asyncTasksBefore;
|
|
613
|
-
const addContainsErrorIfNeeded = () => {
|
|
614
|
-
let hasValidItem = false;
|
|
615
|
-
for (const subReport of subReports) {
|
|
616
|
-
if (subReport.errors.length === 0) {
|
|
617
|
-
hasValidItem = true;
|
|
618
|
-
break;
|
|
619
|
-
}
|
|
291
|
+
validate.call(this, subReport, unevalSchema, json[idx]);
|
|
292
|
+
if (subReport.errors.length > 0) {
|
|
293
|
+
report.addError('ARRAY_UNEVALUATED_ITEMS', undefined, undefined, schema, 'unevaluatedItems');
|
|
294
|
+
break;
|
|
620
295
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// unevaluatedProperties
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
function unevaluatedPropertiesValidator(report, schema, json) {
|
|
303
|
+
if (!isObject(json)) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// unevaluatedProperties: true means all properties are valid
|
|
307
|
+
if (schema.unevaluatedProperties === true) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
// unevaluatedProperties: false or unevaluatedProperties: {schema} both need evaluation
|
|
311
|
+
const unevalSchema = schema.unevaluatedProperties;
|
|
312
|
+
if (unevalSchema === undefined) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const allKeys = Object.keys(json);
|
|
316
|
+
if (allKeys.length === 0) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const evaluatedProperties = collectEvaluated.call(this, {
|
|
320
|
+
report,
|
|
321
|
+
currentSchema: schema,
|
|
322
|
+
json,
|
|
323
|
+
mode: 'properties',
|
|
324
|
+
jsonData: json,
|
|
325
|
+
depth: 0,
|
|
326
|
+
});
|
|
327
|
+
if (evaluatedProperties === 'all') {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const unevaluatedKeys = allKeys.filter((key) => !evaluatedProperties.has(key));
|
|
331
|
+
if (unevaluatedKeys.length === 0) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (unevalSchema === false) {
|
|
335
|
+
report.addError('OBJECT_UNEVALUATED_PROPERTIES', [unevaluatedKeys.join(', ')], undefined, schema, 'unevaluatedProperties');
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
// unevaluatedProperties as a schema — validate each unevaluated key against it
|
|
339
|
+
for (const key of unevaluatedKeys) {
|
|
656
340
|
const subReport = new Report(report);
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
const asyncTasksBefore = report.asyncTasks.length;
|
|
661
|
-
for (const subReport of subReports) {
|
|
662
|
-
report.asyncTasks.push(...subReport.asyncTasks);
|
|
663
|
-
}
|
|
664
|
-
const hasAsyncTasks = report.asyncTasks.length > asyncTasksBefore;
|
|
665
|
-
const addPropertyNameErrors = () => {
|
|
666
|
-
for (let idx = 0; idx < keys.length; idx++) {
|
|
667
|
-
if (subReports[idx].errors.length > 0) {
|
|
668
|
-
report.addError('PROPERTY_NAMES', [keys[idx]], subReports[idx], schema, undefined);
|
|
669
|
-
}
|
|
341
|
+
validate.call(this, subReport, unevalSchema, json[key]);
|
|
342
|
+
if (subReport.errors.length > 0) {
|
|
343
|
+
report.addError('OBJECT_UNEVALUATED_PROPERTIES', [key], undefined, schema, 'unevaluatedProperties');
|
|
670
344
|
}
|
|
671
|
-
};
|
|
672
|
-
if (hasAsyncTasks) {
|
|
673
|
-
const pathBeforeAsync = shallowClone(report.path);
|
|
674
|
-
report.addAsyncTask((callback) => {
|
|
675
|
-
setTimeout(() => callback(null), 0);
|
|
676
|
-
}, [], () => {
|
|
677
|
-
const backup = report.path;
|
|
678
|
-
report.path = pathBeforeAsync;
|
|
679
|
-
addPropertyNameErrors();
|
|
680
|
-
report.path = backup;
|
|
681
|
-
});
|
|
682
|
-
return;
|
|
683
345
|
}
|
|
684
|
-
|
|
685
|
-
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// ---------------------------------------------------------------------------
|
|
349
|
+
// definitions (no-op)
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
function definitionsValidator() {
|
|
352
|
+
/*report: Report, schema: JsonSchemaInternal, json: unknown*/
|
|
353
|
+
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.7.2
|
|
354
|
+
// nothing to do here
|
|
355
|
+
}
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
// JsonValidators — keyword dispatch table
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
export const JsonValidators = {
|
|
360
|
+
// no-op validators (metadata / handled elsewhere)
|
|
361
|
+
id: () => { },
|
|
362
|
+
$id: () => { },
|
|
363
|
+
$ref: () => { },
|
|
364
|
+
$schema: () => { },
|
|
365
|
+
$dynamicAnchor: () => { },
|
|
366
|
+
$dynamicRef: () => { },
|
|
367
|
+
$anchor: () => { },
|
|
368
|
+
$defs: () => { },
|
|
369
|
+
$vocabulary: () => { },
|
|
370
|
+
$recursiveAnchor: () => { },
|
|
371
|
+
$recursiveRef: () => { },
|
|
372
|
+
examples: () => { },
|
|
373
|
+
title: () => { },
|
|
374
|
+
description: () => { },
|
|
375
|
+
default: () => { },
|
|
376
|
+
// type validators
|
|
377
|
+
type: typeValidator,
|
|
378
|
+
enum: enumValidator,
|
|
379
|
+
const: constValidator,
|
|
380
|
+
// numeric validators
|
|
381
|
+
multipleOf: multipleOfValidator,
|
|
382
|
+
maximum: maximumValidator,
|
|
383
|
+
exclusiveMaximum: exclusiveMaximumValidator,
|
|
384
|
+
minimum: minimumValidator,
|
|
385
|
+
exclusiveMinimum: exclusiveMinimumValidator,
|
|
386
|
+
// string validators
|
|
387
|
+
maxLength: maxLengthValidator,
|
|
388
|
+
minLength: minLengthValidator,
|
|
389
|
+
pattern: patternValidator,
|
|
390
|
+
format: formatValidator,
|
|
391
|
+
contentEncoding: contentEncodingValidator,
|
|
392
|
+
contentMediaType: contentMediaTypeValidator,
|
|
393
|
+
// array validators
|
|
394
|
+
additionalItems: additionalItemsValidator,
|
|
395
|
+
items: itemsValidator,
|
|
396
|
+
prefixItems: prefixItemsValidator,
|
|
397
|
+
maxItems: maxItemsValidator,
|
|
398
|
+
minItems: minItemsValidator,
|
|
399
|
+
uniqueItems: uniqueItemsValidator,
|
|
400
|
+
contains: containsValidator,
|
|
401
|
+
maxContains: maxContainsValidator,
|
|
402
|
+
minContains: minContainsValidator,
|
|
403
|
+
unevaluatedItems: unevaluatedItemsValidator,
|
|
404
|
+
// object validators
|
|
405
|
+
maxProperties: maxPropertiesValidator,
|
|
406
|
+
minProperties: minPropertiesValidator,
|
|
407
|
+
required: requiredValidator,
|
|
408
|
+
additionalProperties: additionalPropertiesValidator,
|
|
409
|
+
patternProperties: patternPropertiesValidator,
|
|
410
|
+
properties: propertiesValidator,
|
|
411
|
+
dependencies: dependenciesValidator,
|
|
412
|
+
dependentSchemas: dependentSchemasValidator,
|
|
413
|
+
dependentRequired: dependentRequiredValidator,
|
|
414
|
+
propertyNames: propertyNamesValidator,
|
|
415
|
+
unevaluatedProperties: unevaluatedPropertiesValidator,
|
|
416
|
+
// combinator validators
|
|
417
|
+
allOf: allOfValidator,
|
|
418
|
+
anyOf: anyOfValidator,
|
|
419
|
+
oneOf: oneOfValidator,
|
|
420
|
+
not: notValidator,
|
|
421
|
+
if: ifValidator,
|
|
422
|
+
then: thenValidator,
|
|
423
|
+
else: elseValidator,
|
|
424
|
+
// misc
|
|
425
|
+
definitions: definitionsValidator,
|
|
686
426
|
};
|
|
427
|
+
// ---------------------------------------------------------------------------
|
|
428
|
+
// recurseArray
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
687
430
|
const recurseArray = function (report, schema, json) {
|
|
688
431
|
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.8.2
|
|
689
|
-
|
|
432
|
+
const schemaUri = typeof schema.$schema === 'string' ? schema.$schema : undefined;
|
|
433
|
+
const isDraft202012Schema = schemaUri === 'https://json-schema.org/draft/2020-12/schema' ||
|
|
434
|
+
(!schemaUri && this.options.version === 'draft2020-12');
|
|
435
|
+
const prefixItems = isDraft202012Schema && Array.isArray(schema.prefixItems) ? schema.prefixItems : undefined;
|
|
436
|
+
if (prefixItems) {
|
|
437
|
+
for (let idx = 0; idx < json.length; idx++) {
|
|
438
|
+
if (idx < prefixItems.length) {
|
|
439
|
+
report.path.push(idx);
|
|
440
|
+
validate.call(this, report, prefixItems[idx], json[idx]);
|
|
441
|
+
report.path.pop();
|
|
442
|
+
}
|
|
443
|
+
else if (schema.items !== undefined && !Array.isArray(schema.items)) {
|
|
444
|
+
report.path.push(idx);
|
|
445
|
+
report.schemaPath.push('items');
|
|
446
|
+
validate.call(this, report, schema.items, json[idx]);
|
|
447
|
+
report.schemaPath.pop();
|
|
448
|
+
report.path.pop();
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
690
453
|
// If "items" is an array, this situation, the schema depends on the index:
|
|
691
454
|
// if the index is less than, or equal to, the size of "items",
|
|
692
455
|
// the child instance must be valid against the corresponding schema in the "items" array;
|
|
693
456
|
// otherwise, it must be valid against the schema defined by "additionalItems".
|
|
694
457
|
if (Array.isArray(schema.items)) {
|
|
695
|
-
|
|
458
|
+
for (let idx = 0; idx < json.length; idx++) {
|
|
696
459
|
// equal to doesn't make sense here
|
|
697
460
|
if (idx < schema.items.length) {
|
|
698
461
|
report.path.push(idx);
|
|
@@ -712,7 +475,7 @@ const recurseArray = function (report, schema, json) {
|
|
|
712
475
|
else if (typeof schema.items === 'object' || typeof schema.items === 'boolean') {
|
|
713
476
|
// If items is a schema, then the child instance must be valid against this schema,
|
|
714
477
|
// regardless of its index, and regardless of the value of "additionalItems".
|
|
715
|
-
|
|
478
|
+
for (let idx = 0; idx < json.length; idx++) {
|
|
716
479
|
report.path.push(idx);
|
|
717
480
|
// Track schema path for array items validation
|
|
718
481
|
report.schemaPath.push('items');
|
|
@@ -722,6 +485,9 @@ const recurseArray = function (report, schema, json) {
|
|
|
722
485
|
}
|
|
723
486
|
}
|
|
724
487
|
};
|
|
488
|
+
// ---------------------------------------------------------------------------
|
|
489
|
+
// recurseObject
|
|
490
|
+
// ---------------------------------------------------------------------------
|
|
725
491
|
const recurseObject = function (report, schema, json) {
|
|
726
492
|
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.8.3
|
|
727
493
|
// If "additionalProperties" is absent, it is considered present with an empty schema as a value.
|
|
@@ -736,19 +502,16 @@ const recurseObject = function (report, schema, json) {
|
|
|
736
502
|
const pp = schema.patternProperties ? Object.keys(schema.patternProperties) : [];
|
|
737
503
|
// m - The property name of the child.
|
|
738
504
|
const keys = Object.keys(json);
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const m = keys[idx], propertyValue = json[m];
|
|
505
|
+
for (const m of keys) {
|
|
506
|
+
const propertyValue = json[m];
|
|
742
507
|
// s - The set of schemas for the child instance.
|
|
743
508
|
const s = [];
|
|
744
509
|
// 1. If set "p" contains value "m", then the corresponding schema in "properties" is added to "s".
|
|
745
|
-
if (p.
|
|
510
|
+
if (p.includes(m)) {
|
|
746
511
|
s.push(schema.properties[m]);
|
|
747
512
|
}
|
|
748
513
|
// 2. For each regex in "pp", if it matches "m" successfully, the corresponding schema in "patternProperties" is added to "s".
|
|
749
|
-
|
|
750
|
-
while (idx2--) {
|
|
751
|
-
const regexString = pp[idx2];
|
|
514
|
+
for (const regexString of pp) {
|
|
752
515
|
const result = compileSchemaRegex(regexString);
|
|
753
516
|
if (result.ok && result.value.test(m) === true) {
|
|
754
517
|
s.push(schema.patternProperties[regexString]);
|
|
@@ -762,11 +525,10 @@ const recurseObject = function (report, schema, json) {
|
|
|
762
525
|
// if s is empty in this stage, no additionalProperties are allowed
|
|
763
526
|
// report.expect(s.length !== 0, 'E001', m);
|
|
764
527
|
// Instance property value must pass all schemas from s
|
|
765
|
-
|
|
766
|
-
while (idx2--) {
|
|
528
|
+
for (const schema_s of s) {
|
|
767
529
|
report.path.push(m);
|
|
768
530
|
// Track schema path for properties validation
|
|
769
|
-
if (p.
|
|
531
|
+
if (p.includes(m)) {
|
|
770
532
|
// This is a defined property
|
|
771
533
|
report.schemaPath.push('properties');
|
|
772
534
|
report.schemaPath.push(m);
|
|
@@ -775,15 +537,18 @@ const recurseObject = function (report, schema, json) {
|
|
|
775
537
|
// This is additionalProperties or patternProperties
|
|
776
538
|
report.schemaPath.push('additionalProperties');
|
|
777
539
|
}
|
|
778
|
-
validate.call(this, report,
|
|
540
|
+
validate.call(this, report, schema_s, propertyValue);
|
|
779
541
|
report.path.pop();
|
|
780
542
|
report.schemaPath.pop();
|
|
781
|
-
if (p.
|
|
543
|
+
if (p.includes(m)) {
|
|
782
544
|
report.schemaPath.pop(); // pop the property name for defined properties
|
|
783
545
|
}
|
|
784
546
|
}
|
|
785
547
|
}
|
|
786
548
|
};
|
|
549
|
+
// ---------------------------------------------------------------------------
|
|
550
|
+
// validate — main entry point
|
|
551
|
+
// ---------------------------------------------------------------------------
|
|
787
552
|
export function validate(report, schema, json) {
|
|
788
553
|
report.commonErrorMessage = 'JSON_OBJECT_VALIDATION_FAILED';
|
|
789
554
|
if (schema === true) {
|
|
@@ -809,50 +574,134 @@ export function validate(report, schema, json) {
|
|
|
809
574
|
report.rootSchema = schema;
|
|
810
575
|
isRoot = true;
|
|
811
576
|
}
|
|
577
|
+
const recursiveAnchorStack = report.__$recursiveAnchorStack;
|
|
578
|
+
const dynamicScopeStack = report.__$dynamicScopeStack;
|
|
579
|
+
let pushedRecursiveAnchor = false;
|
|
580
|
+
let pushedDynamicScope = false;
|
|
581
|
+
const schemaId = getId(schema);
|
|
582
|
+
const schemaResourceRoot = schema.__$resourceRoot;
|
|
583
|
+
const dynamicScopeEntry = schemaResourceRoot || (isRoot || typeof schemaId === 'string' ? schema : undefined);
|
|
584
|
+
if (dynamicScopeEntry && dynamicScopeStack[dynamicScopeStack.length - 1] !== dynamicScopeEntry) {
|
|
585
|
+
dynamicScopeStack.push(dynamicScopeEntry);
|
|
586
|
+
pushedDynamicScope = true;
|
|
587
|
+
}
|
|
588
|
+
if (schema.$recursiveAnchor === true) {
|
|
589
|
+
recursiveAnchorStack.push(schema);
|
|
590
|
+
pushedRecursiveAnchor = true;
|
|
591
|
+
}
|
|
812
592
|
// follow schema.$ref keys
|
|
813
593
|
if (schema.$ref !== undefined) {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
while (schema.$ref && maxRefs > 0) {
|
|
594
|
+
const applySiblingKeywordsWithRef = this.options.version === 'draft2019-09' || this.options.version === 'draft2020-12';
|
|
595
|
+
if (applySiblingKeywordsWithRef) {
|
|
817
596
|
if (!schema.__$refResolved) {
|
|
818
597
|
report.addError('REF_UNRESOLVED', [schema.$ref], undefined, schema);
|
|
819
|
-
break;
|
|
820
598
|
}
|
|
821
|
-
else
|
|
822
|
-
|
|
599
|
+
else {
|
|
600
|
+
validate.call(this, report, schema.__$refResolved, json);
|
|
601
|
+
}
|
|
602
|
+
keys = keys.filter((key) => key !== '$ref');
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
// avoid infinite loop with maxRefs
|
|
606
|
+
let maxRefs = 99;
|
|
607
|
+
while (schema.$ref && maxRefs > 0) {
|
|
608
|
+
if (!schema.__$refResolved) {
|
|
609
|
+
report.addError('REF_UNRESOLVED', [schema.$ref], undefined, schema);
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
else if (schema.__$refResolved === schema) {
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
schema = schema.__$refResolved;
|
|
617
|
+
keys = Object.keys(schema);
|
|
618
|
+
}
|
|
619
|
+
maxRefs--;
|
|
620
|
+
}
|
|
621
|
+
if (maxRefs === 0) {
|
|
622
|
+
throw new Error('Circular dependency by $ref references!');
|
|
623
|
+
}
|
|
624
|
+
// Reset schema path for referenced schema - paths are relative to the referenced schema
|
|
625
|
+
report.schemaPath = [];
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
// follow schema.$recursiveRef keys
|
|
629
|
+
if (schema.$recursiveRef !== undefined) {
|
|
630
|
+
const applySiblingKeywordsWithRecursiveRef = this.options.version === 'draft2019-09' || this.options.version === 'draft2020-12';
|
|
631
|
+
if (applySiblingKeywordsWithRecursiveRef) {
|
|
632
|
+
const recursiveRefTarget = resolveRecursiveRef(schema, recursiveAnchorStack);
|
|
633
|
+
if (!recursiveRefTarget) {
|
|
634
|
+
report.addError('REF_UNRESOLVED', [schema.$recursiveRef], undefined, schema);
|
|
823
635
|
}
|
|
824
636
|
else {
|
|
825
|
-
|
|
826
|
-
keys = Object.keys(schema);
|
|
637
|
+
validate.call(this, report, recursiveRefTarget, json);
|
|
827
638
|
}
|
|
828
|
-
|
|
639
|
+
keys = keys.filter((key) => key !== '$recursiveRef');
|
|
829
640
|
}
|
|
830
|
-
|
|
831
|
-
|
|
641
|
+
}
|
|
642
|
+
// follow schema.$dynamicRef keys
|
|
643
|
+
if (schema.$dynamicRef !== undefined) {
|
|
644
|
+
const applySiblingKeywordsWithDynamicRef = this.options.version === 'draft2020-12';
|
|
645
|
+
if (applySiblingKeywordsWithDynamicRef) {
|
|
646
|
+
const dynamicRefTarget = resolveDynamicRef(schema, dynamicScopeStack);
|
|
647
|
+
if (typeof dynamicRefTarget === 'undefined') {
|
|
648
|
+
report.addError('REF_UNRESOLVED', [schema.$dynamicRef], undefined, schema);
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
validate.call(this, report, dynamicRefTarget, json);
|
|
652
|
+
}
|
|
653
|
+
keys = keys.filter((key) => key !== '$dynamicRef');
|
|
832
654
|
}
|
|
833
|
-
|
|
834
|
-
|
|
655
|
+
}
|
|
656
|
+
const validationVocabularyEnabled = isValidationVocabularyEnabled(schema, report, this.options.version);
|
|
657
|
+
if (!validationVocabularyEnabled) {
|
|
658
|
+
keys = keys.filter((key) => !VALIDATION_VOCAB_KEYWORDS.has(key));
|
|
835
659
|
}
|
|
836
660
|
// type checking first
|
|
837
|
-
if (schema.type) {
|
|
661
|
+
if (validationVocabularyEnabled && schema.type) {
|
|
838
662
|
keys.splice(keys.indexOf('type'), 1);
|
|
839
663
|
report.schemaPath.push('type');
|
|
840
664
|
JsonValidators.type.call(this, report, schema, json);
|
|
841
665
|
report.schemaPath.pop();
|
|
842
666
|
if (report.errors.length && this.options.breakOnFirstError) {
|
|
667
|
+
if (pushedRecursiveAnchor) {
|
|
668
|
+
recursiveAnchorStack.pop();
|
|
669
|
+
}
|
|
670
|
+
if (pushedDynamicScope) {
|
|
671
|
+
dynamicScopeStack.pop();
|
|
672
|
+
}
|
|
843
673
|
return false;
|
|
844
674
|
}
|
|
845
675
|
}
|
|
846
676
|
// now iterate all the keys in schema and execute validation methods
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
677
|
+
// Defer unevaluatedItems/unevaluatedProperties to run after other validators,
|
|
678
|
+
// so combinator validation results are cached and available for annotation collection
|
|
679
|
+
const deferredUnevaluatedKeys = [];
|
|
680
|
+
for (const key of keys) {
|
|
681
|
+
if (key === 'unevaluatedItems' || key === 'unevaluatedProperties') {
|
|
682
|
+
deferredUnevaluatedKeys.push(key);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
const validator = JsonValidators[key];
|
|
686
|
+
if (validator) {
|
|
687
|
+
validator.call(this, report, schema, json);
|
|
851
688
|
if (report.errors.length && this.options.breakOnFirstError) {
|
|
852
689
|
break;
|
|
853
690
|
}
|
|
854
691
|
}
|
|
855
692
|
}
|
|
693
|
+
// Run unevaluated* validators after all others have cached their combinator results
|
|
694
|
+
if (deferredUnevaluatedKeys.length > 0 && !(report.errors.length > 0 && this.options.breakOnFirstError)) {
|
|
695
|
+
for (const key of deferredUnevaluatedKeys) {
|
|
696
|
+
const validator = JsonValidators[key];
|
|
697
|
+
if (validator) {
|
|
698
|
+
validator.call(this, report, schema, json);
|
|
699
|
+
if (report.errors.length && this.options.breakOnFirstError) {
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
856
705
|
if (report.errors.length === 0 || this.options.breakOnFirstError === false) {
|
|
857
706
|
if (Array.isArray(json)) {
|
|
858
707
|
recurseArray.call(this, report, schema, json);
|
|
@@ -864,6 +713,12 @@ export function validate(report, schema, json) {
|
|
|
864
713
|
if (typeof this.options.customValidator === 'function') {
|
|
865
714
|
this.options.customValidator.call(this, report, schema, json);
|
|
866
715
|
}
|
|
716
|
+
if (pushedRecursiveAnchor) {
|
|
717
|
+
recursiveAnchorStack.pop();
|
|
718
|
+
}
|
|
719
|
+
if (pushedDynamicScope) {
|
|
720
|
+
dynamicScopeStack.pop();
|
|
721
|
+
}
|
|
867
722
|
// we don't need the root pointer anymore
|
|
868
723
|
if (isRoot) {
|
|
869
724
|
report.rootSchema = undefined;
|