@jarenjs/validate 0.8.4 → 0.34.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/ARCHITECTURE.md +1131 -0
- package/LICENSE +21 -0
- package/README.md +796 -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 +11 -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 +972 -0
- package/dist/types/messages.d.ts +142 -0
- package/dist/types/normalize.d.ts +107 -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 +109 -0
- package/dist/types/traverse.d.ts +32 -0
- package/dist/types/unevaluated.d.ts +12 -0
- package/docs/ERROR-MESSAGES.md +251 -0
- package/package.json +37 -7
- package/src/array.js +610 -0
- package/src/bigint.js +108 -0
- package/src/combine.js +276 -0
- package/src/condition.js +129 -0
- package/src/content.js +83 -0
- package/src/data.js +101 -0
- package/src/dollar-data.js +212 -0
- package/src/dynamic-ref.js +121 -0
- package/src/enum.js +147 -0
- package/src/format.js +108 -0
- package/src/index.js +1896 -0
- package/src/messages.js +497 -0
- package/src/normalize.js +585 -0
- package/src/number.js +169 -0
- package/src/object.js +848 -0
- package/src/query-keyword.js +99 -0
- package/src/query.js +85 -0
- package/src/schema.js +690 -0
- package/src/string.js +164 -0
- package/src/tools.js +397 -0
- package/src/traverse.js +442 -0
- package/src/unevaluated.js +173 -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/array.js
ADDED
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isArrayClass,
|
|
5
|
+
getObjectType,
|
|
6
|
+
} from '@jarenjs/core';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
isUniqueDeepArray,
|
|
10
|
+
} from '@jarenjs/core/object';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
getBoolishType,
|
|
14
|
+
getIntishType,
|
|
15
|
+
} from '@jarenjs/core/number';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
trueThat,
|
|
19
|
+
falseThat,
|
|
20
|
+
} from '@jarenjs/core/function';
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
getBoolOrObjectClass,
|
|
24
|
+
getArrayClassMinItems,
|
|
25
|
+
isOfSchemaType,
|
|
26
|
+
} from './tools.js';
|
|
27
|
+
|
|
28
|
+
//#region Primitives
|
|
29
|
+
function compileMinItems(schemaObj, jsonSchema) {
|
|
30
|
+
const min = getIntishType(jsonSchema.minItems) || 0;
|
|
31
|
+
if (min < 1) return undefined;
|
|
32
|
+
|
|
33
|
+
const addError = schemaObj.createErrorHandler(min, 'minItems');
|
|
34
|
+
return function validateMinItems(len = 0, dataPath = '') {
|
|
35
|
+
return len >= min || addError(len, dataPath);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function compileMaxItems(schemaObj, jsonSchema) {
|
|
40
|
+
const max = getIntishType(jsonSchema.maxItems) || -1;
|
|
41
|
+
if (max < 0) return undefined;
|
|
42
|
+
const min = getIntishType(jsonSchema.minItems) || 0;
|
|
43
|
+
if (max < min) throw new Error('maxItems must be greater then minItems');
|
|
44
|
+
|
|
45
|
+
const addError = schemaObj.createErrorHandler(max, 'maxItems');
|
|
46
|
+
return function validateMaxItems(len = 0, dataPath = '') {
|
|
47
|
+
return len <= max || addError(len, dataPath);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createBooleanValidator(schemaObj, jsonSchema, key, validationFn) {
|
|
52
|
+
const value = getBoolishType(jsonSchema[key]);
|
|
53
|
+
if (value !== true) return undefined;
|
|
54
|
+
|
|
55
|
+
const addError = schemaObj.createErrorHandler(value, key);
|
|
56
|
+
|
|
57
|
+
return function validateBooleanComparator(data, dataPath) {
|
|
58
|
+
return validationFn(data) || addError(data, dataPath);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const compileUniqueItems = (schemaObj, jsonSchema) =>
|
|
63
|
+
createBooleanValidator(schemaObj, jsonSchema, 'uniqueItems', isUniqueDeepArray);
|
|
64
|
+
//#endregion
|
|
65
|
+
|
|
66
|
+
//#region Tuple
|
|
67
|
+
|
|
68
|
+
function compileTupleInternal(schemaObj, jsonSchema, itemsKey, additionalKey) {
|
|
69
|
+
const tuple = getArrayClassMinItems(jsonSchema[itemsKey], 1);
|
|
70
|
+
if (tuple == null)
|
|
71
|
+
return undefined;
|
|
72
|
+
|
|
73
|
+
// Pre-compile all validators upfront
|
|
74
|
+
const validators = new Array(tuple.length);
|
|
75
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
76
|
+
validators[i] = compileItemValidator(schemaObj, tuple[i], itemsKey, i);
|
|
77
|
+
}
|
|
78
|
+
const vlength = validators.length;
|
|
79
|
+
|
|
80
|
+
const additional = getBoolOrObjectClass(jsonSchema[additionalKey], true);
|
|
81
|
+
if (typeof additional === 'boolean') {
|
|
82
|
+
if (additional === true) {
|
|
83
|
+
return function validateTupleBoolTrue(data, dataPath, dataRoot, i) {
|
|
84
|
+
if (i >= vlength) return true;
|
|
85
|
+
return validators[i](data, dataPath, dataRoot);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// additional === false
|
|
89
|
+
return function validateTupleBoolFalse(data, dataPath, dataRoot, i) {
|
|
90
|
+
if (i >= vlength) return false;
|
|
91
|
+
return validators[i](data, dataPath, dataRoot);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// For object additional schema, compile validator once
|
|
96
|
+
const validateAdditional = schemaObj.createValidator(additional, additionalKey);
|
|
97
|
+
return function validateTupleSchema(data, dataPath, dataRoot, i) {
|
|
98
|
+
if (i < vlength) {
|
|
99
|
+
return validators[i](data, dataPath, dataRoot);
|
|
100
|
+
}
|
|
101
|
+
return validateAdditional(data, dataPath, dataRoot);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function compilePrefixItems(schemaObj, jsonSchema) {
|
|
106
|
+
return compileTupleInternal(schemaObj, jsonSchema, 'prefixItems', 'items');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function compileTupleItems(schemaObj, jsonSchema) {
|
|
110
|
+
return compileTupleInternal(schemaObj, jsonSchema, 'items', 'additionalItems');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
|
|
115
|
+
//#region Contains
|
|
116
|
+
function compileArrayContains(schemaObj, jsonSchema) {
|
|
117
|
+
const contains = getObjectType(jsonSchema.contains);
|
|
118
|
+
if (contains == null) return undefined;
|
|
119
|
+
|
|
120
|
+
return schemaObj.createValidator(contains, 'contains');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function compileContainsMinMax(schemaObj, jsonSchema) {
|
|
124
|
+
const contains = getObjectType(jsonSchema.contains);
|
|
125
|
+
if (contains == null) return undefined;
|
|
126
|
+
|
|
127
|
+
const minContains = getIntishType(jsonSchema.minContains);
|
|
128
|
+
const maxContains = getIntishType(jsonSchema.maxContains);
|
|
129
|
+
|
|
130
|
+
const addNonError = schemaObj.createErrorHandler(0, 'contains');
|
|
131
|
+
const addMinError = schemaObj.createErrorHandler(minContains, 'minContains');
|
|
132
|
+
const addMaxError = schemaObj.createErrorHandler(maxContains, 'maxContains');
|
|
133
|
+
|
|
134
|
+
if (minContains == null && maxContains == null) {
|
|
135
|
+
return function validateContainsAtLeastOne(count, dataPath) {
|
|
136
|
+
return count > 0 || addNonError(count, dataPath);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (maxContains == null) {
|
|
141
|
+
return function validateMinContains(count, dataPath) {
|
|
142
|
+
return count >= (minContains || 0)
|
|
143
|
+
|| addMinError(count, dataPath);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (minContains == null) {
|
|
148
|
+
return function validateMaxContains(count, dataPath) {
|
|
149
|
+
return count === 0
|
|
150
|
+
? addNonError(count, dataPath)
|
|
151
|
+
: count <= maxContains
|
|
152
|
+
|| addMaxError(count, dataPath);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return function validateMinMaxContains(count, dataPath) {
|
|
157
|
+
return (count >= minContains || addMinError(count, dataPath))
|
|
158
|
+
&& (count <= maxContains || addMaxError(count, dataPath));
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function compileArrayContainsBoolean(schemaObj, jsonSchema) {
|
|
163
|
+
const contains = getBoolishType(jsonSchema.contains);
|
|
164
|
+
if (contains === true) {
|
|
165
|
+
const addError = schemaObj.createErrorHandler(true, 'contains');
|
|
166
|
+
return function validateArrayContainsTrue(data, dataPath) {
|
|
167
|
+
return data.length > 0
|
|
168
|
+
|| addError(data, dataPath);
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (contains === false) {
|
|
172
|
+
const addError = schemaObj.createErrorHandler(false, 'contains');
|
|
173
|
+
return function validateArrayContainsFalse(data, dataPath) {
|
|
174
|
+
return addError(data, dataPath);
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
|
|
181
|
+
//#region Items
|
|
182
|
+
function compileArrayItemsBoolean(schemaObj, jsonSchema) {
|
|
183
|
+
const items = getBoolishType(jsonSchema.items);
|
|
184
|
+
if (items === true) return trueThat;
|
|
185
|
+
if (items !== false) return undefined;
|
|
186
|
+
|
|
187
|
+
// With prefixItems (draft 2020-12), items:false only forbids items beyond
|
|
188
|
+
// the prefix; that is enforced by the tuple validator, not here.
|
|
189
|
+
if (getArrayClassMinItems(jsonSchema.prefixItems, 1) != null)
|
|
190
|
+
return undefined;
|
|
191
|
+
|
|
192
|
+
const addError = schemaObj.createErrorHandler(false, 'items');
|
|
193
|
+
return function validateArrayItemsFalse(data, dataPath) {
|
|
194
|
+
return data.length === 0
|
|
195
|
+
|| addError(data, dataPath);
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Compile item schema directly without intermediate wrapper
|
|
201
|
+
* This flattens the call stack by avoiding nested validator function calls
|
|
202
|
+
* @param {Object} schemaObj - The schema object
|
|
203
|
+
* @param {Object} itemSchema - Schema for individual items
|
|
204
|
+
* @param {string} key - Key for error reporting
|
|
205
|
+
* @param {number} index - Index for tuple items
|
|
206
|
+
* @returns {Function} Direct validator function
|
|
207
|
+
*/
|
|
208
|
+
function compileItemValidator(schemaObj, itemSchema, key, index) {
|
|
209
|
+
if (itemSchema === true) return trueThat;
|
|
210
|
+
if (itemSchema === false) return falseThat;
|
|
211
|
+
|
|
212
|
+
// For simple type schemas, use inline validation
|
|
213
|
+
if (typeof itemSchema === 'object' && itemSchema !== null) {
|
|
214
|
+
// Fast path: $ref-only schema - check property directly before calling Object.keys
|
|
215
|
+
// This avoids the overhead of Object.keys for the most common case
|
|
216
|
+
if (itemSchema.$ref !== undefined) {
|
|
217
|
+
const keys = Object.keys(itemSchema);
|
|
218
|
+
if (keys.length === 1) {
|
|
219
|
+
// Use the root to resolve the ref directly to the target validator
|
|
220
|
+
// This avoids the overhead of creating an intermediate ValidationObject
|
|
221
|
+
const root = schemaObj.root;
|
|
222
|
+
if (root && root.resolveObject) {
|
|
223
|
+
try {
|
|
224
|
+
const targetObj = root.resolveObject(itemSchema.$ref, schemaObj.path, itemSchema);
|
|
225
|
+
if (targetObj && targetObj.validate) {
|
|
226
|
+
return targetObj.validate;
|
|
227
|
+
}
|
|
228
|
+
} catch (_e) {
|
|
229
|
+
// Fall through to default handling
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// The two fast paths below return bare predicates with no error handler,
|
|
236
|
+
// so a failing item reports nothing of its own and the caller can only
|
|
237
|
+
// aggregate one error at the array path. When errors are recorded, fall
|
|
238
|
+
// through to full compilation so each failing item yields its own error
|
|
239
|
+
// at its own instancePath, matching the multi-keyword path.
|
|
240
|
+
const stopAtFirst = schemaObj.root.options.skipErrors;
|
|
241
|
+
|
|
242
|
+
// Fast path: type-only schema (most common case) - check property directly first
|
|
243
|
+
if (stopAtFirst && itemSchema.type !== undefined && Object.keys(itemSchema).length === 1) {
|
|
244
|
+
return compileTypeOnlyValidator(itemSchema.type);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Fast path: required-only schema - check property directly first
|
|
248
|
+
if (stopAtFirst && itemSchema.required !== undefined && Object.keys(itemSchema).length === 1) {
|
|
249
|
+
const required = itemSchema.required;
|
|
250
|
+
return function validateRequiredOnly(data, _dataPath, _dataRoot) {
|
|
251
|
+
// Required properties only apply to objects, not arrays or other types
|
|
252
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data)) return true;
|
|
253
|
+
for (let i = 0; i < required.length; i++) {
|
|
254
|
+
if (!(required[i] in data)) return false;
|
|
255
|
+
}
|
|
256
|
+
return true;
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Fall back to full schema compilation for complex cases
|
|
262
|
+
return schemaObj.createValidator(itemSchema, key, index);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Compile a validator for simple type-only schemas
|
|
267
|
+
* @param {string} type - The type to validate
|
|
268
|
+
* @returns {Function} Type validator function
|
|
269
|
+
*/
|
|
270
|
+
function compileTypeOnlyValidator(type) {
|
|
271
|
+
switch (type) {
|
|
272
|
+
case 'string':
|
|
273
|
+
return function validateString(data) {
|
|
274
|
+
return typeof data === 'string';
|
|
275
|
+
};
|
|
276
|
+
case 'number':
|
|
277
|
+
return function validateNumber(data) {
|
|
278
|
+
return typeof data === 'number' && !isNaN(data);
|
|
279
|
+
};
|
|
280
|
+
case 'integer':
|
|
281
|
+
return function validateInteger(data) {
|
|
282
|
+
return typeof data === 'number' && Number.isInteger(data);
|
|
283
|
+
};
|
|
284
|
+
case 'boolean':
|
|
285
|
+
return function validateBoolean(data) {
|
|
286
|
+
return typeof data === 'boolean';
|
|
287
|
+
};
|
|
288
|
+
case 'array':
|
|
289
|
+
return function validateArray(data) {
|
|
290
|
+
return Array.isArray(data);
|
|
291
|
+
};
|
|
292
|
+
case 'object':
|
|
293
|
+
return function validateObject(data) {
|
|
294
|
+
return typeof data === 'object' && data !== null && !Array.isArray(data);
|
|
295
|
+
};
|
|
296
|
+
case 'null':
|
|
297
|
+
return function validateNull(data) {
|
|
298
|
+
return data === null;
|
|
299
|
+
};
|
|
300
|
+
default:
|
|
301
|
+
return trueThat;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
//#endregion
|
|
305
|
+
|
|
306
|
+
//#region Main
|
|
307
|
+
export function compileArrayPrimitives(schemaObj, jsonSchema) {
|
|
308
|
+
// minItems/maxItems/uniqueItems belong to the validation vocabulary;
|
|
309
|
+
// assert nothing when the metaschema disables it.
|
|
310
|
+
if (schemaObj.options.vocabValidation === false)
|
|
311
|
+
return undefined;
|
|
312
|
+
|
|
313
|
+
const minItems = compileMinItems(schemaObj, jsonSchema);
|
|
314
|
+
const maxItems = compileMaxItems(schemaObj, jsonSchema);
|
|
315
|
+
const uniqueItems = compileUniqueItems(schemaObj, jsonSchema);
|
|
316
|
+
|
|
317
|
+
if ((minItems
|
|
318
|
+
|| maxItems
|
|
319
|
+
|| uniqueItems) == null)
|
|
320
|
+
return undefined;
|
|
321
|
+
|
|
322
|
+
// Single-constraint schemas are the common case; skip the trueThat chain.
|
|
323
|
+
if (minItems == null && maxItems == null)
|
|
324
|
+
return uniqueItems;
|
|
325
|
+
|
|
326
|
+
if (uniqueItems == null) {
|
|
327
|
+
if (maxItems == null) {
|
|
328
|
+
return function validateArrayMinItems(data, dataPath) {
|
|
329
|
+
return minItems(data.length, dataPath);
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
if (minItems == null) {
|
|
333
|
+
return function validateArrayMaxItems(data, dataPath) {
|
|
334
|
+
return maxItems(data.length, dataPath);
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return function validateArrayMinMaxItems(data, dataPath) {
|
|
338
|
+
const len = data.length;
|
|
339
|
+
return minItems(len, dataPath)
|
|
340
|
+
&& maxItems(len, dataPath);
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const isMinItems = minItems || trueThat;
|
|
345
|
+
const isMaxItems = maxItems || trueThat;
|
|
346
|
+
|
|
347
|
+
if (schemaObj.options.skipErrors) {
|
|
348
|
+
return function validateArrayPrimitives(data, dataPath) {
|
|
349
|
+
const len = data.length;
|
|
350
|
+
return isMinItems(len, dataPath)
|
|
351
|
+
&& isMaxItems(len, dataPath)
|
|
352
|
+
&& uniqueItems(data, dataPath);
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Length and uniqueness are independent: a short array can also contain
|
|
357
|
+
// duplicates, and a caller fixing one wants to hear about the other.
|
|
358
|
+
return function validateArrayPrimitivesAll(data, dataPath) {
|
|
359
|
+
const len = data.length;
|
|
360
|
+
let valid = isMinItems(len, dataPath);
|
|
361
|
+
valid = isMaxItems(len, dataPath) && valid;
|
|
362
|
+
return uniqueItems(data, dataPath) && valid;
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function compileArrayChildren(schemaObj, jsonSchema) {
|
|
367
|
+
// Check for prefixItems (draft 2020-12+) first, then items.
|
|
368
|
+
// A document that declares an older draft via $schema treats
|
|
369
|
+
// prefixItems as an unknown keyword.
|
|
370
|
+
const prefixItems = (schemaObj.declaredDraft != null && schemaObj.declaredDraft < 2020)
|
|
371
|
+
? undefined
|
|
372
|
+
: getArrayClassMinItems(jsonSchema.prefixItems, 1);
|
|
373
|
+
const items = jsonSchema.items;
|
|
374
|
+
const isTuple = getArrayClassMinItems(items, 1) != null;
|
|
375
|
+
|
|
376
|
+
const root = schemaObj.root;
|
|
377
|
+
const track = root.usesUnevaluated;
|
|
378
|
+
// In draft 2020-12 contains produces item annotations; in 2019-09 it doesn't.
|
|
379
|
+
const trackContains = track && (schemaObj.options.draftVersion || 7) >= 2020;
|
|
380
|
+
// Item paths are consumed by error reporting and $data resolution only.
|
|
381
|
+
const extendPaths = !schemaObj.options.skipErrors || root.usesDollarData;
|
|
382
|
+
|
|
383
|
+
// Indexes below this limit count as evaluated (for unevaluatedItems) when
|
|
384
|
+
// their item validation succeeds. Extra tuple items beyond the tuple length
|
|
385
|
+
// pass validation when additionalItems/items is ABSENT, but are then not
|
|
386
|
+
// evaluated and must remain visible to unevaluatedItems.
|
|
387
|
+
let evalLimit = Infinity;
|
|
388
|
+
if (track) {
|
|
389
|
+
if (prefixItems != null) {
|
|
390
|
+
if (items === undefined) evalLimit = prefixItems.length;
|
|
391
|
+
} else if (isTuple) {
|
|
392
|
+
if (jsonSchema.additionalItems === undefined) evalLimit = items.length;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let validateItem;
|
|
397
|
+
|
|
398
|
+
if (prefixItems != null) {
|
|
399
|
+
// Draft 2020-12+ style prefixItems
|
|
400
|
+
validateItem = compilePrefixItems(schemaObj, jsonSchema);
|
|
401
|
+
} else if (isTuple) {
|
|
402
|
+
// Draft 7 style tuple items
|
|
403
|
+
validateItem = compileTupleItems(schemaObj, jsonSchema);
|
|
404
|
+
} else if (items !== undefined) {
|
|
405
|
+
// Single schema for all items
|
|
406
|
+
const itemsSchema = getObjectType(items);
|
|
407
|
+
if (itemsSchema != null) {
|
|
408
|
+
// Use direct validator compilation for items
|
|
409
|
+
validateItem = compileItemValidator(schemaObj, itemsSchema, 'items', undefined);
|
|
410
|
+
|
|
411
|
+
// Wrap single-item validator with index loop
|
|
412
|
+
const itemValidator = validateItem;
|
|
413
|
+
validateItem = function validateSingleItemSchema(data, dataPath, dataRoot, _i) {
|
|
414
|
+
return itemValidator(data, dataPath, dataRoot);
|
|
415
|
+
};
|
|
416
|
+
} else if (items === true) {
|
|
417
|
+
validateItem = trueThat;
|
|
418
|
+
}
|
|
419
|
+
// items === false is fully handled by compileArrayItemsBoolean: only an
|
|
420
|
+
// empty array can pass, so a per-item validator would never be invoked.
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const validateContains = compileArrayContains(schemaObj, jsonSchema);
|
|
424
|
+
if ((validateItem || validateContains) == null)
|
|
425
|
+
return undefined;
|
|
426
|
+
|
|
427
|
+
const validateMinMax = compileContainsMinMax(schemaObj, jsonSchema) || trueThat;
|
|
428
|
+
|
|
429
|
+
const maxItems = getIntishType(jsonSchema.maxItems) || 0;
|
|
430
|
+
|
|
431
|
+
const resolveLength = len => (maxItems > 0
|
|
432
|
+
? Math.min(maxItems, len)
|
|
433
|
+
: len);
|
|
434
|
+
|
|
435
|
+
// Fast path: items only, no contains
|
|
436
|
+
if (validateContains == null && validateItem != null) {
|
|
437
|
+
// if validateItem is trueThat, just check length
|
|
438
|
+
if (validateItem === trueThat) {
|
|
439
|
+
// items: true evaluates every item, which matters when annotations
|
|
440
|
+
// are tracked for unevaluatedItems.
|
|
441
|
+
if (track) {
|
|
442
|
+
return function validateArrayItemsTrue(data, _dataPath, _dataRoot) {
|
|
443
|
+
root.evalLog.add(data, -1);
|
|
444
|
+
return true;
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
return undefined; // No actual validation needed
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const addError = schemaObj.createErrorHandler(0, 'items');
|
|
451
|
+
const validator = validateItem;
|
|
452
|
+
|
|
453
|
+
return function validateArrayItemsOnly(data, dataPath, dataRoot) {
|
|
454
|
+
const len = resolveLength(data.length);
|
|
455
|
+
const arr = data;
|
|
456
|
+
|
|
457
|
+
let invalid = 0;
|
|
458
|
+
for (let i = 0; i < len; ++i) {
|
|
459
|
+
const itemPath = extendPaths ? dataPath + '/' + i : dataPath;
|
|
460
|
+
// Direct validator call, no intermediate wrappers
|
|
461
|
+
if (validator(arr[i], itemPath, dataRoot, i) !== true) {
|
|
462
|
+
invalid++;
|
|
463
|
+
}
|
|
464
|
+
else if (track && i < evalLimit) {
|
|
465
|
+
root.evalLog.add(data, i);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return invalid === 0
|
|
469
|
+
|| addError(invalid, dataPath);
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Fast path: contains only, no items
|
|
474
|
+
if (validateItem == null && validateContains != null) {
|
|
475
|
+
const validator = validateContains;
|
|
476
|
+
|
|
477
|
+
return function validateArrayContainsOnly(data, dataPath) {
|
|
478
|
+
const len = resolveLength(data.length);
|
|
479
|
+
const arr = data;
|
|
480
|
+
// Each element is a CANDIDATE probe: the array only has to contain a
|
|
481
|
+
// match, so an element that is not one has done nothing wrong.
|
|
482
|
+
const errors = root.errorMark();
|
|
483
|
+
let contains = 0;
|
|
484
|
+
for (let i = 0; i < len; ++i) {
|
|
485
|
+
if (validator(arr[i], dataPath) === true) {
|
|
486
|
+
contains++;
|
|
487
|
+
if (trackContains) root.evalLog.add(data, i);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
root.rollbackErrors(errors);
|
|
491
|
+
return validateMinMax(contains, dataPath);
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Combined: both items and contains
|
|
496
|
+
const itemValidator = validateItem;
|
|
497
|
+
const containsValidator = validateContains;
|
|
498
|
+
|
|
499
|
+
const stopAtFirstContains = schemaObj.options.skipErrors;
|
|
500
|
+
return function validateArrayChildren(data, dataPath, dataRoot) {
|
|
501
|
+
const len = resolveLength(data.length);
|
|
502
|
+
const arr = data;
|
|
503
|
+
|
|
504
|
+
let invalid = 0;
|
|
505
|
+
let contains = 0;
|
|
506
|
+
for (let i = 0; i < len; ++i) {
|
|
507
|
+
const obj = arr[i];
|
|
508
|
+
const itemPath = extendPaths ? dataPath + '/' + i : dataPath;
|
|
509
|
+
// Direct validator calls without intermediate wrappers
|
|
510
|
+
if (itemValidator(obj, itemPath, dataRoot, i) !== true) {
|
|
511
|
+
invalid++;
|
|
512
|
+
}
|
|
513
|
+
else if (track && i < evalLimit) {
|
|
514
|
+
root.evalLog.add(data, i);
|
|
515
|
+
}
|
|
516
|
+
// A contains candidate is a probe: not matching is not a fault.
|
|
517
|
+
const containsMark = root.errorMark();
|
|
518
|
+
if (containsValidator(obj, dataPath, dataRoot) === true) {
|
|
519
|
+
contains++;
|
|
520
|
+
if (trackContains) root.evalLog.add(data, i);
|
|
521
|
+
}
|
|
522
|
+
root.rollbackErrors(containsMark);
|
|
523
|
+
}
|
|
524
|
+
// Failing items and the contains count are independent tallies.
|
|
525
|
+
const itemsOk = invalid === 0;
|
|
526
|
+
if (stopAtFirstContains && !itemsOk) return false;
|
|
527
|
+
return validateMinMax(contains, dataPath) && itemsOk;
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function compileArraySchema(schemaObj, jsonSchema) {
|
|
532
|
+
if (isOfSchemaType(jsonSchema, 'set'))
|
|
533
|
+
return undefined;
|
|
534
|
+
|
|
535
|
+
const compiledPrimitives = compileArrayPrimitives(schemaObj, jsonSchema);
|
|
536
|
+
const compiledItemsBoolean = compileArrayItemsBoolean(schemaObj, jsonSchema);
|
|
537
|
+
const compiledContainsBoolean = compileArrayContainsBoolean(schemaObj, jsonSchema);
|
|
538
|
+
const compiledArrayChildren = compileArrayChildren(schemaObj, jsonSchema);
|
|
539
|
+
|
|
540
|
+
if ((compiledPrimitives
|
|
541
|
+
|| compiledItemsBoolean
|
|
542
|
+
|| compiledContainsBoolean
|
|
543
|
+
|| compiledArrayChildren) === undefined)
|
|
544
|
+
return undefined;
|
|
545
|
+
|
|
546
|
+
// Single-validator schemas are the common case; skip the trueThat chain.
|
|
547
|
+
const parts = [];
|
|
548
|
+
if (compiledPrimitives) parts.push(compiledPrimitives);
|
|
549
|
+
if (compiledItemsBoolean && compiledItemsBoolean !== trueThat) parts.push(compiledItemsBoolean);
|
|
550
|
+
if (compiledContainsBoolean) parts.push(compiledContainsBoolean);
|
|
551
|
+
if (compiledArrayChildren) parts.push(compiledArrayChildren);
|
|
552
|
+
|
|
553
|
+
if (parts.length === 0)
|
|
554
|
+
return undefined;
|
|
555
|
+
|
|
556
|
+
if (parts.length === 1) {
|
|
557
|
+
const single = parts[0];
|
|
558
|
+
return function validateArraySchemaSingle(data, dataPath, dataRoot) {
|
|
559
|
+
return isArrayClass(data)
|
|
560
|
+
? single(data, dataPath, dataRoot)
|
|
561
|
+
: true;
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (parts.length === 2) {
|
|
566
|
+
const first = parts[0];
|
|
567
|
+
const second = parts[1];
|
|
568
|
+
if (schemaObj.options.skipErrors) {
|
|
569
|
+
return function validateArraySchemaDouble(data, dataPath, dataRoot) {
|
|
570
|
+
if (isArrayClass(data)) {
|
|
571
|
+
return first(data, dataPath, dataRoot)
|
|
572
|
+
&& second(data, dataPath, dataRoot);
|
|
573
|
+
}
|
|
574
|
+
return true;
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
// The two parts are independent array keyword groups (length/uniqueness
|
|
578
|
+
// versus the item walk); the array-class guard stays a precondition.
|
|
579
|
+
return function validateArraySchemaDoubleAll(data, dataPath, dataRoot) {
|
|
580
|
+
if (!isArrayClass(data)) return true;
|
|
581
|
+
const firstOk = first(data, dataPath, dataRoot);
|
|
582
|
+
return second(data, dataPath, dataRoot) && firstOk;
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const validatePrimitives = compiledPrimitives || trueThat;
|
|
587
|
+
const hasBooleanItems = compiledItemsBoolean || trueThat;
|
|
588
|
+
const hasBooleanContains = compiledContainsBoolean || trueThat;
|
|
589
|
+
const validateItems = compiledArrayChildren || trueThat;
|
|
590
|
+
const stopAtFirstSchema = schemaObj.options.skipErrors;
|
|
591
|
+
|
|
592
|
+
return function validateArraySchema(data, dataPath, dataRoot) {
|
|
593
|
+
if (isArrayClass(data)) {
|
|
594
|
+
if (stopAtFirstSchema) {
|
|
595
|
+
return validatePrimitives(data, dataPath)
|
|
596
|
+
&& hasBooleanItems(data, dataPath, dataRoot)
|
|
597
|
+
&& hasBooleanContains(data, dataPath, dataRoot)
|
|
598
|
+
&& validateItems(data, dataPath, dataRoot);
|
|
599
|
+
}
|
|
600
|
+
// Length/uniqueness, the boolean items/contains forms and the item
|
|
601
|
+
// walk are independent; a length failure must not hide item faults.
|
|
602
|
+
let valid = validatePrimitives(data, dataPath);
|
|
603
|
+
valid = hasBooleanItems(data, dataPath, dataRoot) && valid;
|
|
604
|
+
valid = hasBooleanContains(data, dataPath, dataRoot) && valid;
|
|
605
|
+
return validateItems(data, dataPath, dataRoot) && valid;
|
|
606
|
+
}
|
|
607
|
+
return true;
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
//#endregion
|