@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.
Files changed (53) hide show
  1. package/ARCHITECTURE.md +1131 -0
  2. package/LICENSE +21 -0
  3. package/README.md +796 -2
  4. package/dist/types/array.d.ts +2 -0
  5. package/dist/types/bigint.d.ts +1 -0
  6. package/dist/types/combine.d.ts +1 -0
  7. package/dist/types/condition.d.ts +1 -0
  8. package/dist/types/content.d.ts +3 -0
  9. package/dist/types/data.d.ts +7 -0
  10. package/dist/types/dollar-data.d.ts +11 -0
  11. package/dist/types/dynamic-ref.d.ts +44 -0
  12. package/dist/types/enum.d.ts +1 -0
  13. package/dist/types/format.d.ts +21 -0
  14. package/dist/types/index.d.ts +972 -0
  15. package/dist/types/messages.d.ts +142 -0
  16. package/dist/types/normalize.d.ts +107 -0
  17. package/dist/types/number.d.ts +1 -0
  18. package/dist/types/object.d.ts +3 -0
  19. package/dist/types/query-keyword.d.ts +19 -0
  20. package/dist/types/query.d.ts +29 -0
  21. package/dist/types/schema.d.ts +1 -0
  22. package/dist/types/string.d.ts +1 -0
  23. package/dist/types/tools.d.ts +109 -0
  24. package/dist/types/traverse.d.ts +32 -0
  25. package/dist/types/unevaluated.d.ts +12 -0
  26. package/docs/ERROR-MESSAGES.md +251 -0
  27. package/package.json +37 -7
  28. package/src/array.js +610 -0
  29. package/src/bigint.js +108 -0
  30. package/src/combine.js +276 -0
  31. package/src/condition.js +129 -0
  32. package/src/content.js +83 -0
  33. package/src/data.js +101 -0
  34. package/src/dollar-data.js +212 -0
  35. package/src/dynamic-ref.js +121 -0
  36. package/src/enum.js +147 -0
  37. package/src/format.js +108 -0
  38. package/src/index.js +1896 -0
  39. package/src/messages.js +497 -0
  40. package/src/normalize.js +585 -0
  41. package/src/number.js +169 -0
  42. package/src/object.js +848 -0
  43. package/src/query-keyword.js +99 -0
  44. package/src/query.js +85 -0
  45. package/src/schema.js +690 -0
  46. package/src/string.js +164 -0
  47. package/src/tools.js +397 -0
  48. package/src/traverse.js +442 -0
  49. package/src/unevaluated.js +173 -0
  50. package/dist/index.js +0 -1998
  51. package/dist/index.js.map +0 -7
  52. package/dist/index.min.js +0 -2
  53. 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