@jarenjs/validate 0.8.4 → 0.9.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/ARCHITECTURE.md +1067 -0
- package/LICENSE +21 -0
- package/README.md +339 -2
- package/dist/types/array.d.ts +2 -0
- package/dist/types/bigint.d.ts +1 -0
- package/dist/types/combine.d.ts +1 -0
- package/dist/types/condition.d.ts +1 -0
- package/dist/types/content.d.ts +3 -0
- package/dist/types/data.d.ts +7 -0
- package/dist/types/dollar-data.d.ts +20 -0
- package/dist/types/dynamic-ref.d.ts +44 -0
- package/dist/types/enum.d.ts +1 -0
- package/dist/types/format.d.ts +21 -0
- package/dist/types/index.d.ts +874 -0
- package/dist/types/number.d.ts +1 -0
- package/dist/types/object.d.ts +3 -0
- package/dist/types/query-keyword.d.ts +19 -0
- package/dist/types/query.d.ts +29 -0
- package/dist/types/schema.d.ts +1 -0
- package/dist/types/string.d.ts +1 -0
- package/dist/types/tools.d.ts +51 -0
- package/dist/types/traverse.d.ts +32 -0
- package/dist/types/unevaluated.d.ts +12 -0
- package/package.json +32 -7
- package/src/array.js +565 -0
- package/src/bigint.js +97 -0
- package/src/combine.js +226 -0
- package/src/condition.js +109 -0
- package/src/content.js +83 -0
- package/src/data.js +477 -0
- package/src/dollar-data.js +629 -0
- package/src/dynamic-ref.js +121 -0
- package/src/enum.js +148 -0
- package/src/format.js +66 -0
- package/src/index.js +1854 -0
- package/src/number.js +159 -0
- package/src/object.js +755 -0
- package/src/query-keyword.js +99 -0
- package/src/query.js +59 -0
- package/src/schema.js +645 -0
- package/src/string.js +152 -0
- package/src/tools.js +205 -0
- package/src/traverse.js +433 -0
- package/src/unevaluated.js +151 -0
- package/dist/index.js +0 -1998
- package/dist/index.js.map +0 -7
- package/dist/index.min.js +0 -2
- package/dist/index.min.js.map +0 -7
package/src/object.js
ADDED
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
// isObjectClass, // to specific
|
|
5
|
+
isObjectType, // slowerc
|
|
6
|
+
isArrayClass,
|
|
7
|
+
getObjectType,
|
|
8
|
+
} from '@jarenjs/core';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
getIntishType,
|
|
12
|
+
} from '@jarenjs/core/number';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
createRegExp,
|
|
16
|
+
} from '@jarenjs/core/string';
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
trueThat,
|
|
20
|
+
} from '@jarenjs/core/function';
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
isBoolOrObjectClass,
|
|
24
|
+
getBoolOrObjectClass,
|
|
25
|
+
getArrayClassMinItems,
|
|
26
|
+
isOfSchemaType,
|
|
27
|
+
ValidationResult,
|
|
28
|
+
} from './tools.js';
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
includesAll,
|
|
32
|
+
} from '@jarenjs/core/array';
|
|
33
|
+
|
|
34
|
+
//#region Primitives
|
|
35
|
+
function compileMinProperties(schemaObj, jsonSchema) {
|
|
36
|
+
const min = getIntishType(jsonSchema.minProperties) || 0;
|
|
37
|
+
if (min < 1) return undefined;
|
|
38
|
+
|
|
39
|
+
const addError = schemaObj.createErrorHandler(min, 'minProperties');
|
|
40
|
+
return function validateMinProperties(len = 0, dataPath = '') {
|
|
41
|
+
return len >= min || addError(len, dataPath);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function compileMaxProperties(schemaObj, jsonSchema) {
|
|
46
|
+
const max = getIntishType(jsonSchema.maxProperties);
|
|
47
|
+
if (max == null || max < 0) return undefined;
|
|
48
|
+
const min = getIntishType(jsonSchema.minProperties) || 0;
|
|
49
|
+
if (max < min) throw new Error('maxProperties must be greater then minProperties');
|
|
50
|
+
|
|
51
|
+
const addError = schemaObj.createErrorHandler(max, 'maxProperties');
|
|
52
|
+
return function validateMaxProperties(len = 0, dataPath = '') {
|
|
53
|
+
return len <= max || addError(len, dataPath);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function compileRequiredProperties(schemaObj, jsonSchema) {
|
|
58
|
+
const required = getArrayClassMinItems(jsonSchema.required, 1);
|
|
59
|
+
if (required == null) return undefined;
|
|
60
|
+
|
|
61
|
+
const rlength = required.length;
|
|
62
|
+
/** @type {function(string, any, string):boolean} */
|
|
63
|
+
// Use array key to get keyed error handler: addKeyedError(dataKey, data, ...meta)
|
|
64
|
+
const addError = schemaObj.createErrorHandler(required, ['required']);
|
|
65
|
+
return function validateRequiredProperties(data = {}, dataKeys = [], dataPath = '') {
|
|
66
|
+
if (!(dataKeys.length > 0))
|
|
67
|
+
return false;
|
|
68
|
+
|
|
69
|
+
let valid = true;
|
|
70
|
+
for (let i = 0; i < rlength; ++i) {
|
|
71
|
+
const key = required[i];
|
|
72
|
+
const idx = dataKeys.indexOf(key);
|
|
73
|
+
if (idx === -1)
|
|
74
|
+
valid &&= addError(key, data, dataPath);
|
|
75
|
+
}
|
|
76
|
+
return valid;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
|
|
81
|
+
//#region Constraints
|
|
82
|
+
function compilePropertyNames(schemaObj, jsonSchema) {
|
|
83
|
+
const propNames = getBoolOrObjectClass(jsonSchema.propertyNames);
|
|
84
|
+
if (propNames == null) return undefined;
|
|
85
|
+
|
|
86
|
+
const propertyNamesValidator = schemaObj.createValidator(propNames, 'propertyNames');
|
|
87
|
+
return function validatePropertyNames(dataKey) {
|
|
88
|
+
return propertyNamesValidator(dataKey);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildPropertyValidators(schemaObj, jsonSchema) {
|
|
93
|
+
const properties = getObjectType(jsonSchema.properties);
|
|
94
|
+
if (properties == null) return undefined;
|
|
95
|
+
|
|
96
|
+
// Use Object.getOwnPropertyNames to handle __proto__ correctly
|
|
97
|
+
// Object.keys() doesn't return __proto__ when defined via { __proto__: value }
|
|
98
|
+
const keys = Object.getOwnPropertyNames(properties);
|
|
99
|
+
if (keys.length === 0) return undefined;
|
|
100
|
+
|
|
101
|
+
const validators = new Map();
|
|
102
|
+
for (let i = 0; i < keys.length; i++) {
|
|
103
|
+
const key = keys[i];
|
|
104
|
+
const schemas = properties[key];
|
|
105
|
+
const validator = schemaObj.createValidator(schemas, 'properties', key);
|
|
106
|
+
if (validator != null)
|
|
107
|
+
validators.set(key, validator);
|
|
108
|
+
}
|
|
109
|
+
if (validators.size === 0)
|
|
110
|
+
return undefined;
|
|
111
|
+
|
|
112
|
+
return validators;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function compileProperties(schemaObj, jsonSchema) {
|
|
116
|
+
const validators = buildPropertyValidators(schemaObj, jsonSchema);
|
|
117
|
+
if (validators == null) return undefined;
|
|
118
|
+
|
|
119
|
+
const root = schemaObj.root;
|
|
120
|
+
const track = root.usesUnevaluated;
|
|
121
|
+
|
|
122
|
+
return function validatePropertyItem(data, dataPath, dataRoot, dataKey) {
|
|
123
|
+
const result = new ValidationResult();
|
|
124
|
+
const validator = validators.get(dataKey);
|
|
125
|
+
if (validator == null)
|
|
126
|
+
return result;
|
|
127
|
+
else {
|
|
128
|
+
const valid = validator(data[dataKey], dataPath, dataRoot, dataKey);
|
|
129
|
+
if (track && valid === true) root.evalLog.add(data, dataKey);
|
|
130
|
+
return result.addMatch(valid);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildPatternValidators(schemaObj, jsonSchema) {
|
|
136
|
+
const entries = getObjectType(jsonSchema.patternProperties);
|
|
137
|
+
if (entries == null) return undefined;
|
|
138
|
+
|
|
139
|
+
// Use Object.getOwnPropertyNames to handle __proto__ correctly
|
|
140
|
+
const entryKeys = Object.getOwnPropertyNames(entries);
|
|
141
|
+
if (entryKeys.length === 0) return undefined;
|
|
142
|
+
|
|
143
|
+
const list = [];
|
|
144
|
+
for (let i = 0; i < entryKeys.length; ++i) {
|
|
145
|
+
const key = entryKeys[i];
|
|
146
|
+
const pattern = createRegExp(key);
|
|
147
|
+
if (pattern == null) continue;
|
|
148
|
+
|
|
149
|
+
const validator = schemaObj.createValidator(entries[key], 'patternProperties', key);
|
|
150
|
+
if (validator != null)
|
|
151
|
+
list.push({ pattern, validator });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (list.length === 0) return undefined;
|
|
155
|
+
|
|
156
|
+
return list;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function compilePatternProperties(schemaObj, jsonSchema) {
|
|
160
|
+
const list = buildPatternValidators(schemaObj, jsonSchema);
|
|
161
|
+
if (list == null) return undefined;
|
|
162
|
+
|
|
163
|
+
const root = schemaObj.root;
|
|
164
|
+
const track = root.usesUnevaluated;
|
|
165
|
+
|
|
166
|
+
return function validatePatternPropertiesItem(data, dataPath, dataRoot, dataKey) {
|
|
167
|
+
const result = new ValidationResult();
|
|
168
|
+
for (let i = 0; i < list.length; ++i) {
|
|
169
|
+
const { pattern, validator } = list[i];
|
|
170
|
+
if (pattern.test(dataKey)) {
|
|
171
|
+
const valid = validator(data[dataKey], dataPath, dataRoot, dataKey);
|
|
172
|
+
if (track && valid === true) root.evalLog.add(data, dataKey);
|
|
173
|
+
result.addMatch(valid);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function compileAdditionalProperties(schemaObj, jsonSchema) {
|
|
181
|
+
const additional = getBoolOrObjectClass(jsonSchema.additionalProperties);
|
|
182
|
+
if (additional == null) return undefined;
|
|
183
|
+
|
|
184
|
+
if (additional === false) {
|
|
185
|
+
const addError = schemaObj.createErrorHandler(false, ['additionalProperties']);
|
|
186
|
+
|
|
187
|
+
return function validateNoAdditionalProperties(data, dataPath, dataRoot, dataKey) {
|
|
188
|
+
return addError(dataKey, data);
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const root = schemaObj.root;
|
|
193
|
+
const track = root.usesUnevaluated;
|
|
194
|
+
const validator = schemaObj.createValidator(additional, 'additionalProperties');
|
|
195
|
+
|
|
196
|
+
return function validateAdditionalPropertyItem(data, dataPath, dataRoot, dataKey) {
|
|
197
|
+
const valid = validator(data[dataKey], dataPath, dataRoot, dataKey);
|
|
198
|
+
if (track && valid === true) root.evalLog.add(data, dataKey);
|
|
199
|
+
return valid;
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
//#endregion
|
|
203
|
+
|
|
204
|
+
//#region Dependencies
|
|
205
|
+
function compileDependentRequired(schemaObj, jsonSchema) {
|
|
206
|
+
// TODO: before we go to release remove this check, since it doesn't help anyone.
|
|
207
|
+
// dependentRequired only exists since draft 2019-09; a document that
|
|
208
|
+
// declares an older draft via $schema treats it as an unknown keyword.
|
|
209
|
+
if (schemaObj.declaredDraft != null && schemaObj.declaredDraft < 2019)
|
|
210
|
+
return undefined;
|
|
211
|
+
|
|
212
|
+
const dependentRequired = getObjectType(jsonSchema.dependentRequired);
|
|
213
|
+
if (dependentRequired == null)
|
|
214
|
+
return undefined;
|
|
215
|
+
|
|
216
|
+
if (Object.keys(dependentRequired).length === 0)
|
|
217
|
+
return undefined;
|
|
218
|
+
|
|
219
|
+
const addError = schemaObj.createErrorHandler(false, 'dependentRequired');
|
|
220
|
+
|
|
221
|
+
return function validateDependentRequiredItem(data, dataPath, dataRoot, dataKey) {
|
|
222
|
+
if (dataKey in dependentRequired) {
|
|
223
|
+
const required = dependentRequired[dataKey];
|
|
224
|
+
return includesAll(Object.keys(data), required)
|
|
225
|
+
|| addError(data, dataKey, dataPath);
|
|
226
|
+
}
|
|
227
|
+
return true;
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function compileDependentSchemas(schemaObj, jsonSchema) {
|
|
232
|
+
// TODO: before we go to release remove this check, since it doesn't help anyone.
|
|
233
|
+
// dependentSchemas only exists since draft 2019-09; a document that
|
|
234
|
+
// declares an older draft via $schema treats it as an unknown keyword.
|
|
235
|
+
if (schemaObj.declaredDraft != null && schemaObj.declaredDraft < 2019)
|
|
236
|
+
return undefined;
|
|
237
|
+
|
|
238
|
+
const dependentSchemas = getObjectType(jsonSchema.dependentSchemas);
|
|
239
|
+
if (dependentSchemas == null) return undefined;
|
|
240
|
+
|
|
241
|
+
const validators = new Map();
|
|
242
|
+
for (const key in dependentSchemas) {
|
|
243
|
+
if (Object.prototype.hasOwnProperty.call(dependentSchemas, key)) {
|
|
244
|
+
const schema = dependentSchemas[key];
|
|
245
|
+
if (!isBoolOrObjectClass(schema) && !schemaObj.options.skipErrors)
|
|
246
|
+
throw new Error(`Expected Schema at '${schemaObj.path}/${key}'`);
|
|
247
|
+
|
|
248
|
+
const validator = schemaObj.createValidator(schema, 'dependentSchemas', key);
|
|
249
|
+
if (validator != null)
|
|
250
|
+
validators.set(key, validator);
|
|
251
|
+
else
|
|
252
|
+
throw new Error(`Expected Validator at '${schemaObj.path}/${key}'`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (validators.size === 0)
|
|
257
|
+
return undefined;
|
|
258
|
+
|
|
259
|
+
return function validateDependentSchemasItem(data, dataPath, dataRoot, dataKey) {
|
|
260
|
+
if (validators.has(dataKey)) {
|
|
261
|
+
const validator = validators.get(dataKey);
|
|
262
|
+
return validator(data, dataPath, dataRoot, dataKey);
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function compileDependencies(schemaObj, jsonSchema) {
|
|
269
|
+
const dependencies = getObjectType(jsonSchema.dependencies);
|
|
270
|
+
if (dependencies == null)
|
|
271
|
+
return undefined;
|
|
272
|
+
|
|
273
|
+
// Collect dependency entries into arrays for faster access
|
|
274
|
+
const depKeys = Object.keys(dependencies);
|
|
275
|
+
if (depKeys.length === 0)
|
|
276
|
+
return undefined;
|
|
277
|
+
|
|
278
|
+
// Separate schema dependencies from required dependencies for optimization
|
|
279
|
+
const schemaDeps = [];
|
|
280
|
+
const requiredDeps = [];
|
|
281
|
+
|
|
282
|
+
for (let i = 0; i < depKeys.length; i++) {
|
|
283
|
+
const key = depKeys[i];
|
|
284
|
+
const right = dependencies[key];
|
|
285
|
+
if (isBoolOrObjectClass(right)) {
|
|
286
|
+
const validator = schemaObj.createValidator(right, 'dependencies', key);
|
|
287
|
+
if (validator != null)
|
|
288
|
+
schemaDeps.push({ key, validator });
|
|
289
|
+
else if (!schemaObj.options.skipErrors)
|
|
290
|
+
throw new Error(`Expected Validator at '${schemaObj.path}/${key}'`);
|
|
291
|
+
}
|
|
292
|
+
else if (isArrayClass(right)) {
|
|
293
|
+
const addError = schemaObj.createErrorHandler(right, ['dependencies', key]);
|
|
294
|
+
requiredDeps.push({ key, required: right, addError });
|
|
295
|
+
}
|
|
296
|
+
else if (!schemaObj.options.skipErrors)
|
|
297
|
+
throw new Error(`Expected Schema or Array at '${schemaObj.path}/${key}'`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (schemaDeps.length === 0 && requiredDeps.length === 0)
|
|
301
|
+
return undefined;
|
|
302
|
+
|
|
303
|
+
// Single schema dependency - most common case
|
|
304
|
+
if (schemaDeps.length === 1 && requiredDeps.length === 0) {
|
|
305
|
+
const { key, validator } = schemaDeps[0];
|
|
306
|
+
return function validateSingleSchemaDep(data, dataPath, dataRoot, dataKey) {
|
|
307
|
+
if (dataKey === key) {
|
|
308
|
+
return validator(data, dataPath, dataRoot, dataKey);
|
|
309
|
+
}
|
|
310
|
+
return true;
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Single required dependency - common case
|
|
315
|
+
if (requiredDeps.length === 1 && schemaDeps.length === 0) {
|
|
316
|
+
const { key, required, addError } = requiredDeps[0];
|
|
317
|
+
const rlen = required.length;
|
|
318
|
+
return function validateSingleRequiredDep(data, dataPath, dataRoot, dataKey) {
|
|
319
|
+
if (dataKey === key) {
|
|
320
|
+
const dataKeys = Object.keys(data);
|
|
321
|
+
for (let i = 0; i < rlen; i++) {
|
|
322
|
+
if (!dataKeys.includes(required[i])) {
|
|
323
|
+
return addError(data, dataKey, dataPath);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return true;
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Multiple dependencies - generic case
|
|
332
|
+
// Create lookup maps for faster access
|
|
333
|
+
const schemaDepMap = new Map();
|
|
334
|
+
for (let i = 0; i < schemaDeps.length; i++) {
|
|
335
|
+
schemaDepMap.set(schemaDeps[i].key, schemaDeps[i].validator);
|
|
336
|
+
}
|
|
337
|
+
const requiredDepMap = new Map();
|
|
338
|
+
for (let i = 0; i < requiredDeps.length; i++) {
|
|
339
|
+
requiredDepMap.set(requiredDeps[i].key, requiredDeps[i]);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return function validateDependenciesItem(data, dataPath, dataRoot, dataKey) {
|
|
343
|
+
// Check schema dependencies first
|
|
344
|
+
const schemaValidator = schemaDepMap.get(dataKey);
|
|
345
|
+
if (schemaValidator != null) {
|
|
346
|
+
return schemaValidator(data, dataPath, dataRoot, dataKey);
|
|
347
|
+
}
|
|
348
|
+
// Check required dependencies
|
|
349
|
+
const reqDep = requiredDepMap.get(dataKey);
|
|
350
|
+
if (reqDep != null) {
|
|
351
|
+
const { required, addError } = reqDep;
|
|
352
|
+
return includesAll(Object.keys(data), required)
|
|
353
|
+
|| addError(data, dataKey, dataPath);
|
|
354
|
+
}
|
|
355
|
+
return true;
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
//#endregion
|
|
359
|
+
|
|
360
|
+
//#region Main
|
|
361
|
+
export function compileObjectPrimitives(schemaObj, jsonSchema) {
|
|
362
|
+
// TODO: figure out if we need such a check for real!
|
|
363
|
+
// minProperties/maxProperties/required belong to the validation
|
|
364
|
+
// vocabulary; assert nothing when the metaschema disables it.
|
|
365
|
+
if (schemaObj.options.vocabValidation === false)
|
|
366
|
+
return undefined;
|
|
367
|
+
|
|
368
|
+
const minProperties = compileMinProperties(schemaObj, jsonSchema);
|
|
369
|
+
const maxProperties = compileMaxProperties(schemaObj, jsonSchema);
|
|
370
|
+
const requiredProperties = compileRequiredProperties(schemaObj, jsonSchema);
|
|
371
|
+
|
|
372
|
+
if ((minProperties
|
|
373
|
+
|| maxProperties
|
|
374
|
+
|| requiredProperties) == null)
|
|
375
|
+
return undefined;
|
|
376
|
+
|
|
377
|
+
// Inline the validation to reduce function call overhead
|
|
378
|
+
const min = getIntishType(jsonSchema.minProperties) || 0;
|
|
379
|
+
const max = getIntishType(jsonSchema.maxProperties);
|
|
380
|
+
const required = getArrayClassMinItems(jsonSchema.required, 1);
|
|
381
|
+
|
|
382
|
+
const hasMin = min > 0;
|
|
383
|
+
const hasMax = max != null && max >= 0;
|
|
384
|
+
const hasRequired = required != null && required.length > 0;
|
|
385
|
+
|
|
386
|
+
// Pre-bind error handlers outside the returned function
|
|
387
|
+
if (hasMin && !hasMax && !hasRequired) {
|
|
388
|
+
const addError = schemaObj.createErrorHandler(min, 'minProperties');
|
|
389
|
+
return function validateMinPropertiesOnly(data, dataPath, dataRoot, dataKeys) {
|
|
390
|
+
const len = dataKeys ? dataKeys.length : Object.keys(data).length;
|
|
391
|
+
return len >= min || addError(len, dataPath);
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (!hasMin && hasMax && !hasRequired) {
|
|
396
|
+
const addError = schemaObj.createErrorHandler(max, 'maxProperties');
|
|
397
|
+
return function validateMaxPropertiesOnly(data, dataPath, dataRoot, dataKeys) {
|
|
398
|
+
const len = dataKeys ? dataKeys.length : Object.keys(data).length;
|
|
399
|
+
return len <= max || addError(len, dataPath);
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (hasMin && hasMax && !hasRequired) {
|
|
404
|
+
const addMinError = schemaObj.createErrorHandler(min, 'minProperties');
|
|
405
|
+
const addMaxError = schemaObj.createErrorHandler(max, 'maxProperties');
|
|
406
|
+
return function validateMinMaxProperties(data, dataPath, dataRoot, dataKeys) {
|
|
407
|
+
const len = dataKeys ? dataKeys.length : Object.keys(data).length;
|
|
408
|
+
return (len >= min || addMinError(len, dataPath))
|
|
409
|
+
&& (len <= max || addMaxError(len, dataPath));
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Specialized paths for required properties
|
|
414
|
+
if (!hasMin && !hasMax && hasRequired) {
|
|
415
|
+
const rlength = required.length;
|
|
416
|
+
const addError = schemaObj.createErrorHandler(required, ['required']);
|
|
417
|
+
return function validateRequiredOnly(data, dataPath, dataRoot, dataKeys) {
|
|
418
|
+
// Required properties only apply to objects, not arrays or other types
|
|
419
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
const keys = dataKeys || Object.keys(data);
|
|
423
|
+
let valid = true;
|
|
424
|
+
for (let i = 0; i < rlength; ++i) {
|
|
425
|
+
const key = required[i];
|
|
426
|
+
if (keys.indexOf(key) === -1)
|
|
427
|
+
valid &&= addError(key, data, dataPath);
|
|
428
|
+
}
|
|
429
|
+
return valid;
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (hasMin && !hasMax && hasRequired) {
|
|
434
|
+
const addMinError = schemaObj.createErrorHandler(min, 'minProperties');
|
|
435
|
+
const rlength = required.length;
|
|
436
|
+
const addReqError = schemaObj.createErrorHandler(required, ['required']);
|
|
437
|
+
return function validateMinAndRequired(data, dataPath, dataRoot, dataKeys) {
|
|
438
|
+
// Required/minProperties only apply to objects, not arrays or other types
|
|
439
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
const keys = dataKeys || Object.keys(data);
|
|
443
|
+
const len = keys.length;
|
|
444
|
+
if (len < min && !addMinError(len, dataPath))
|
|
445
|
+
return false;
|
|
446
|
+
let valid = true;
|
|
447
|
+
for (let i = 0; i < rlength; ++i) {
|
|
448
|
+
const key = required[i];
|
|
449
|
+
if (keys.indexOf(key) === -1)
|
|
450
|
+
valid &&= addReqError(key, data, dataPath);
|
|
451
|
+
}
|
|
452
|
+
return valid;
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (!hasMin && hasMax && hasRequired) {
|
|
457
|
+
const addMaxError = schemaObj.createErrorHandler(max, 'maxProperties');
|
|
458
|
+
const rlength = required.length;
|
|
459
|
+
const addReqError = schemaObj.createErrorHandler(required, ['required']);
|
|
460
|
+
return function validateMaxAndRequired(data, dataPath, dataRoot, dataKeys) {
|
|
461
|
+
// Required/maxProperties only apply to objects, not arrays or other types
|
|
462
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
const keys = dataKeys || Object.keys(data);
|
|
466
|
+
const len = keys.length;
|
|
467
|
+
if (len > max && !addMaxError(len, dataPath))
|
|
468
|
+
return false;
|
|
469
|
+
let valid = true;
|
|
470
|
+
for (let i = 0; i < rlength; ++i) {
|
|
471
|
+
const key = required[i];
|
|
472
|
+
if (keys.indexOf(key) === -1)
|
|
473
|
+
valid &&= addReqError(key, data, dataPath);
|
|
474
|
+
}
|
|
475
|
+
return valid;
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Generic case with all checks
|
|
480
|
+
const isMinProperties = minProperties || trueThat;
|
|
481
|
+
const isMaxProperties = maxProperties || trueThat;
|
|
482
|
+
const hasRequiredProperties = requiredProperties || trueThat;
|
|
483
|
+
|
|
484
|
+
return function validateObjectPrimitives(data, dataPath, dataRoot, dataKeys) {
|
|
485
|
+
const keys = dataKeys || Object.keys(data);
|
|
486
|
+
const len = keys.length;
|
|
487
|
+
return isMinProperties(len, dataPath)
|
|
488
|
+
&& isMaxProperties(len, dataPath)
|
|
489
|
+
&& hasRequiredProperties(data, keys, dataPath);
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function compileObjectProperty(schemaObj, jsonSchema) {
|
|
494
|
+
const namesValidator = compilePropertyNames(schemaObj, jsonSchema);
|
|
495
|
+
const propertyValidator = compileProperties(schemaObj, jsonSchema);
|
|
496
|
+
const patternValidator = compilePatternProperties(schemaObj, jsonSchema);
|
|
497
|
+
const additionalValidator = compileAdditionalProperties(schemaObj, jsonSchema);
|
|
498
|
+
const depSchemasValidator = compileDependentSchemas(schemaObj, jsonSchema);
|
|
499
|
+
const dependencyValidator = compileDependencies(schemaObj, jsonSchema);
|
|
500
|
+
const depRequiredValidator = compileDependentRequired(schemaObj, jsonSchema);
|
|
501
|
+
|
|
502
|
+
if ((patternValidator
|
|
503
|
+
|| namesValidator
|
|
504
|
+
|| propertyValidator
|
|
505
|
+
|| depRequiredValidator
|
|
506
|
+
|| depSchemasValidator
|
|
507
|
+
|| dependencyValidator
|
|
508
|
+
|| additionalValidator) == null)
|
|
509
|
+
return undefined;
|
|
510
|
+
|
|
511
|
+
const validateName = namesValidator || trueThat;
|
|
512
|
+
const validateProperty = propertyValidator || ValidationResult.undefThat;
|
|
513
|
+
const validatePattern = patternValidator || ValidationResult.undefThat;
|
|
514
|
+
|
|
515
|
+
const validateDepRequired = depRequiredValidator || trueThat;
|
|
516
|
+
const validateDepSchemas = depSchemasValidator || trueThat;
|
|
517
|
+
const validateDependency = dependencyValidator || trueThat;
|
|
518
|
+
|
|
519
|
+
return function validateObjectProperty(data, dataPath, dataRoot, dataKey) {
|
|
520
|
+
const result = new ValidationResult();
|
|
521
|
+
// Build the child dataPath by appending the property key
|
|
522
|
+
const newPath = dataPath ? `${dataPath}/${dataKey}` : `/${dataKey}`;
|
|
523
|
+
|
|
524
|
+
result.addValid(validateName(dataKey))
|
|
525
|
+
.addResult(validateProperty(data, newPath, dataRoot, dataKey))
|
|
526
|
+
.addResult(validatePattern(data, newPath, dataRoot, dataKey))
|
|
527
|
+
.addValid(validateDepRequired(data, newPath, dataRoot, dataKey))
|
|
528
|
+
.addValid(validateDepSchemas(data, newPath, dataRoot, dataKey))
|
|
529
|
+
.addValid(validateDependency(data, newPath, dataRoot, dataKey));
|
|
530
|
+
|
|
531
|
+
if (additionalValidator)
|
|
532
|
+
return !result.match
|
|
533
|
+
? result.addMatch(additionalValidator(data, newPath, dataRoot, dataKey))
|
|
534
|
+
: result;
|
|
535
|
+
|
|
536
|
+
return result;
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Fused per-key validation loop for the default skipErrors mode.
|
|
542
|
+
* Avoids the per-key ValidationResult allocations and eagerly built child
|
|
543
|
+
* paths of the generic path; returns on the first failing property.
|
|
544
|
+
* Child paths are still built (lazily) because $data validators resolve
|
|
545
|
+
* relative JSON pointers against them at validation time.
|
|
546
|
+
*/
|
|
547
|
+
function compileObjectChildrenFast(schemaObj, jsonSchema) {
|
|
548
|
+
const namesValidator = compilePropertyNames(schemaObj, jsonSchema);
|
|
549
|
+
const propsMap = buildPropertyValidators(schemaObj, jsonSchema) || null;
|
|
550
|
+
const patternList = buildPatternValidators(schemaObj, jsonSchema) || null;
|
|
551
|
+
const depSchemasValidator = compileDependentSchemas(schemaObj, jsonSchema) || null;
|
|
552
|
+
const dependencyValidator = compileDependencies(schemaObj, jsonSchema) || null;
|
|
553
|
+
const depRequiredValidator = compileDependentRequired(schemaObj, jsonSchema) || null;
|
|
554
|
+
|
|
555
|
+
const root = schemaObj.root;
|
|
556
|
+
const track = root.usesUnevaluated;
|
|
557
|
+
|
|
558
|
+
const additional = getBoolOrObjectClass(jsonSchema.additionalProperties);
|
|
559
|
+
const additionalFalse = additional === false;
|
|
560
|
+
const additionalValidator = (additional != null && additional !== false && additional !== true)
|
|
561
|
+
? schemaObj.createValidator(additional, 'additionalProperties')
|
|
562
|
+
: null;
|
|
563
|
+
// additionalProperties: true evaluates every leftover property, which
|
|
564
|
+
// matters when annotations are tracked for unevaluatedProperties.
|
|
565
|
+
const additionalTrue = additional === true && track;
|
|
566
|
+
const hasAdditional = additionalFalse || additionalTrue || additionalValidator != null;
|
|
567
|
+
|
|
568
|
+
if (namesValidator == null
|
|
569
|
+
&& propsMap == null
|
|
570
|
+
&& patternList == null
|
|
571
|
+
&& depSchemasValidator == null
|
|
572
|
+
&& dependencyValidator == null
|
|
573
|
+
&& depRequiredValidator == null
|
|
574
|
+
&& !hasAdditional)
|
|
575
|
+
return undefined;
|
|
576
|
+
|
|
577
|
+
const validateName = namesValidator || null;
|
|
578
|
+
|
|
579
|
+
// Child paths are only consumed by $data relative-pointer resolution
|
|
580
|
+
// in skipErrors mode; skip the per-property string concat otherwise.
|
|
581
|
+
const extendPaths = root.usesDollarData;
|
|
582
|
+
|
|
583
|
+
return function validateObjectChildrenFast(data, dataPath, dataRoot, dataKeys) {
|
|
584
|
+
const len = dataKeys.length;
|
|
585
|
+
for (let i = 0; i < len; ++i) {
|
|
586
|
+
const dataKey = dataKeys[i];
|
|
587
|
+
if (validateName != null && validateName(dataKey) === false)
|
|
588
|
+
return false;
|
|
589
|
+
|
|
590
|
+
let matched = false;
|
|
591
|
+
let childPath = null;
|
|
592
|
+
|
|
593
|
+
if (propsMap != null) {
|
|
594
|
+
const propValidator = propsMap.get(dataKey);
|
|
595
|
+
if (propValidator != null) {
|
|
596
|
+
matched = true;
|
|
597
|
+
childPath = extendPaths ? dataPath + '/' + dataKey : dataPath;
|
|
598
|
+
if (propValidator(data[dataKey], childPath, dataRoot, dataKey) === false)
|
|
599
|
+
return false;
|
|
600
|
+
if (track) root.evalLog.add(data, dataKey);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (patternList != null) {
|
|
605
|
+
for (let j = 0; j < patternList.length; ++j) {
|
|
606
|
+
const entry = patternList[j];
|
|
607
|
+
if (entry.pattern.test(dataKey)) {
|
|
608
|
+
matched = true;
|
|
609
|
+
if (childPath === null) childPath = extendPaths ? dataPath + '/' + dataKey : dataPath;
|
|
610
|
+
if (entry.validator(data[dataKey], childPath, dataRoot, dataKey) === false)
|
|
611
|
+
return false;
|
|
612
|
+
if (track) root.evalLog.add(data, dataKey);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (matched === false && hasAdditional) {
|
|
618
|
+
if (additionalFalse)
|
|
619
|
+
return false;
|
|
620
|
+
if (additionalValidator != null) {
|
|
621
|
+
if (childPath === null) childPath = extendPaths ? dataPath + '/' + dataKey : dataPath;
|
|
622
|
+
if (additionalValidator(data[dataKey], childPath, dataRoot, dataKey) === false)
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
if (track) root.evalLog.add(data, dataKey);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (depRequiredValidator != null || depSchemasValidator != null || dependencyValidator != null) {
|
|
629
|
+
if (childPath === null) childPath = extendPaths ? dataPath + '/' + dataKey : dataPath;
|
|
630
|
+
if (depRequiredValidator != null && depRequiredValidator(data, childPath, dataRoot, dataKey) === false)
|
|
631
|
+
return false;
|
|
632
|
+
if (depSchemasValidator != null && depSchemasValidator(data, childPath, dataRoot, dataKey) === false)
|
|
633
|
+
return false;
|
|
634
|
+
if (dependencyValidator != null && dependencyValidator(data, childPath, dataRoot, dataKey) === false)
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return true;
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export function compileObjectChildren(schemaObj, jsonSchema) {
|
|
643
|
+
// Fast path: when errors are skipped (default) we can bail on the first
|
|
644
|
+
// failure and avoid per-key result bookkeeping entirely.
|
|
645
|
+
// (unevaluatedProperties runs separately as a final-stage validator,
|
|
646
|
+
// see unevaluated.js)
|
|
647
|
+
if (schemaObj.options.skipErrors)
|
|
648
|
+
return compileObjectChildrenFast(schemaObj, jsonSchema);
|
|
649
|
+
|
|
650
|
+
const propertyValidator = compileObjectProperty(schemaObj, jsonSchema);
|
|
651
|
+
if (propertyValidator == null)
|
|
652
|
+
return undefined;
|
|
653
|
+
|
|
654
|
+
// Inline ValidationResult operations to reduce object allocations
|
|
655
|
+
return function validateObjectChildren(data, dataPath, dataRoot, dataKeys) {
|
|
656
|
+
let totalErrors = 0;
|
|
657
|
+
const len = dataKeys.length;
|
|
658
|
+
for (let i = 0; i < len; ++i) {
|
|
659
|
+
const result = propertyValidator(data, dataPath, dataRoot, dataKeys[i]);
|
|
660
|
+
if (result !== true) {
|
|
661
|
+
// result can be false or a ValidationResult-like object
|
|
662
|
+
if (result === false) {
|
|
663
|
+
totalErrors++;
|
|
664
|
+
} else {
|
|
665
|
+
totalErrors += result.errors || 0;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return totalErrors === 0;
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
export function compileObjectSchema(schemaObj, jsonSchema) {
|
|
675
|
+
if (isOfSchemaType(jsonSchema, 'map'))
|
|
676
|
+
return undefined;
|
|
677
|
+
|
|
678
|
+
// Fast path: properties-only schema in skipErrors mode. Iterate the
|
|
679
|
+
// (fixed) schema keys with direct property access instead of allocating
|
|
680
|
+
// Object.keys(data) and doing a map lookup per data key.
|
|
681
|
+
if (schemaObj.options.skipErrors
|
|
682
|
+
&& jsonSchema.patternProperties == null
|
|
683
|
+
&& jsonSchema.additionalProperties == null
|
|
684
|
+
&& jsonSchema.propertyNames == null
|
|
685
|
+
&& jsonSchema.dependencies == null
|
|
686
|
+
&& jsonSchema.dependentSchemas == null
|
|
687
|
+
&& jsonSchema.dependentRequired == null
|
|
688
|
+
&& jsonSchema.minProperties == null
|
|
689
|
+
&& jsonSchema.maxProperties == null
|
|
690
|
+
&& jsonSchema.required == null
|
|
691
|
+
&& getObjectType(jsonSchema.properties) != null) {
|
|
692
|
+
const propsMap = buildPropertyValidators(schemaObj, jsonSchema);
|
|
693
|
+
if (propsMap == null)
|
|
694
|
+
return undefined;
|
|
695
|
+
|
|
696
|
+
const propKeys = Array.from(propsMap.keys());
|
|
697
|
+
const propValidators = Array.from(propsMap.values());
|
|
698
|
+
const propCount = propKeys.length;
|
|
699
|
+
|
|
700
|
+
const root = schemaObj.root;
|
|
701
|
+
const track = root.usesUnevaluated;
|
|
702
|
+
|
|
703
|
+
// Child paths are only consumed by $data relative-pointer resolution
|
|
704
|
+
// in skipErrors mode; skip the per-property string concat otherwise.
|
|
705
|
+
if (root.usesDollarData || track) {
|
|
706
|
+
const extendPaths = root.usesDollarData;
|
|
707
|
+
return function validateObjectPropertiesOnlyTracked(data, dataPath, dataRoot) {
|
|
708
|
+
if (!isObjectType(data)) return true;
|
|
709
|
+
for (let i = 0; i < propCount; ++i) {
|
|
710
|
+
const key = propKeys[i];
|
|
711
|
+
// Object.hasOwn: avoid picking up inherited members like toString
|
|
712
|
+
if (Object.hasOwn(data, key)) {
|
|
713
|
+
const childPath = extendPaths ? dataPath + '/' + key : dataPath;
|
|
714
|
+
if (propValidators[i](data[key], childPath, dataRoot, key) === false)
|
|
715
|
+
return false;
|
|
716
|
+
if (track) root.evalLog.add(data, key);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return true;
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return function validateObjectPropertiesOnly(data, dataPath, dataRoot) {
|
|
724
|
+
if (!isObjectType(data)) return true;
|
|
725
|
+
for (let i = 0; i < propCount; ++i) {
|
|
726
|
+
const key = propKeys[i];
|
|
727
|
+
// Object.hasOwn: avoid picking up inherited members like toString
|
|
728
|
+
if (Object.hasOwn(data, key)
|
|
729
|
+
&& propValidators[i](data[key], dataPath, dataRoot, key) === false)
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
return true;
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const objectPrimitives = compileObjectPrimitives(schemaObj, jsonSchema);
|
|
737
|
+
const objectChildren = compileObjectChildren(schemaObj, jsonSchema);
|
|
738
|
+
|
|
739
|
+
if ((objectPrimitives
|
|
740
|
+
|| objectChildren) == null)
|
|
741
|
+
return undefined;
|
|
742
|
+
|
|
743
|
+
const validatePrimitives = objectPrimitives || trueThat;
|
|
744
|
+
const validateChildren = objectChildren || trueThat;
|
|
745
|
+
|
|
746
|
+
return function validateObjectSchema(data, dataPath, dataRoot) {
|
|
747
|
+
if (isObjectType(data)) {
|
|
748
|
+
const dataKeys = Object.keys(data);
|
|
749
|
+
return validatePrimitives(data, dataPath, dataRoot, dataKeys)
|
|
750
|
+
&& validateChildren(data, dataPath, dataRoot, dataKeys);
|
|
751
|
+
}
|
|
752
|
+
return true;
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
//#endregion
|