@jarenjs/validate 0.9.2 → 0.34.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/src/object.js CHANGED
@@ -62,6 +62,9 @@ function compileRequiredProperties(schemaObj, jsonSchema) {
62
62
  /** @type {function(string, any, string):boolean} */
63
63
  // Use array key to get keyed error handler: addKeyedError(dataKey, data, ...meta)
64
64
  const addError = schemaObj.createErrorHandler(required, ['required']);
65
+ // When errors are recorded, every missing property must produce one; only
66
+ // the boolean-answer path may stop at the first.
67
+ const stopAtFirst = schemaObj.root.options.skipErrors;
65
68
  return function validateRequiredProperties(data = {}, dataKeys = [], dataPath = '') {
66
69
  if (!(dataKeys.length > 0))
67
70
  return false;
@@ -70,8 +73,10 @@ function compileRequiredProperties(schemaObj, jsonSchema) {
70
73
  for (let i = 0; i < rlength; ++i) {
71
74
  const key = required[i];
72
75
  const idx = dataKeys.indexOf(key);
73
- if (idx === -1)
74
- valid &&= addError(key, data, dataPath);
76
+ if (idx === -1) {
77
+ valid = addError(key, data, dataPath) && valid;
78
+ if (stopAtFirst) break;
79
+ }
75
80
  }
76
81
  return valid;
77
82
  };
@@ -84,8 +89,10 @@ function compilePropertyNames(schemaObj, jsonSchema) {
84
89
  if (propNames == null) return undefined;
85
90
 
86
91
  const propertyNamesValidator = schemaObj.createValidator(propNames, 'propertyNames');
87
- return function validatePropertyNames(dataKey) {
88
- return propertyNamesValidator(dataKey);
92
+ // The property NAME is the validated data; the object's data path is
93
+ // threaded through so name failures report a usable instancePath.
94
+ return function validatePropertyNames(dataKey, dataPath) {
95
+ return propertyNamesValidator(dataKey, dataPath);
89
96
  }
90
97
  }
91
98
 
@@ -185,7 +192,7 @@ function compileAdditionalProperties(schemaObj, jsonSchema) {
185
192
  const addError = schemaObj.createErrorHandler(false, ['additionalProperties']);
186
193
 
187
194
  return function validateNoAdditionalProperties(data, dataPath, dataRoot, dataKey) {
188
- return addError(dataKey, data);
195
+ return addError(dataKey, data, dataPath);
189
196
  };
190
197
  }
191
198
 
@@ -216,13 +223,15 @@ function compileDependentRequired(schemaObj, jsonSchema) {
216
223
  if (Object.keys(dependentRequired).length === 0)
217
224
  return undefined;
218
225
 
219
- const addError = schemaObj.createErrorHandler(false, 'dependentRequired');
226
+ // Keyed handler: addError(dataKey, data, dataPath) - dataKey names the
227
+ // triggering property, rest[0] is the instance data path.
228
+ const addError = schemaObj.createErrorHandler(false, ['dependentRequired']);
220
229
 
221
230
  return function validateDependentRequiredItem(data, dataPath, dataRoot, dataKey) {
222
231
  if (dataKey in dependentRequired) {
223
232
  const required = dependentRequired[dataKey];
224
233
  return includesAll(Object.keys(data), required)
225
- || addError(data, dataKey, dataPath);
234
+ || addError(dataKey, data, dataPath);
226
235
  }
227
236
  return true;
228
237
  };
@@ -320,7 +329,7 @@ function compileDependencies(schemaObj, jsonSchema) {
320
329
  const dataKeys = Object.keys(data);
321
330
  for (let i = 0; i < rlen; i++) {
322
331
  if (!dataKeys.includes(required[i])) {
323
- return addError(data, dataKey, dataPath);
332
+ return addError(dataKey, data, dataPath);
324
333
  }
325
334
  }
326
335
  }
@@ -350,7 +359,7 @@ function compileDependencies(schemaObj, jsonSchema) {
350
359
  if (reqDep != null) {
351
360
  const { required, addError } = reqDep;
352
361
  return includesAll(Object.keys(data), required)
353
- || addError(data, dataKey, dataPath);
362
+ || addError(dataKey, data, dataPath);
354
363
  }
355
364
  return true;
356
365
  };
@@ -382,6 +391,9 @@ export function compileObjectPrimitives(schemaObj, jsonSchema) {
382
391
  const hasMin = min > 0;
383
392
  const hasMax = max != null && max >= 0;
384
393
  const hasRequired = required != null && required.length > 0;
394
+ // When errors are recorded, every missing required property must produce
395
+ // one; only the boolean-answer path may stop at the first.
396
+ const stopAtFirst = schemaObj.root.options.skipErrors;
385
397
 
386
398
  // Pre-bind error handlers outside the returned function
387
399
  if (hasMin && !hasMax && !hasRequired) {
@@ -414,6 +426,25 @@ export function compileObjectPrimitives(schemaObj, jsonSchema) {
414
426
  if (!hasMin && !hasMax && hasRequired) {
415
427
  const rlength = required.length;
416
428
  const addError = schemaObj.createErrorHandler(required, ['required']);
429
+ // Non-string entries (invalid schemas) can never match a data key;
430
+ // they stay on the Object.keys path below.
431
+ if (required.every(key => typeof key === 'string')) {
432
+ return function validateRequiredHasOwn(data, dataPath, _dataRoot, _dataKeys) {
433
+ // Required properties only apply to objects, not arrays or other types
434
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) {
435
+ return true;
436
+ }
437
+ let valid = true;
438
+ for (let i = 0; i < rlength; ++i) {
439
+ const key = required[i];
440
+ if (!Object.hasOwn(data, key)) {
441
+ valid = addError(key, data, dataPath) && valid;
442
+ if (stopAtFirst) break;
443
+ }
444
+ }
445
+ return valid;
446
+ };
447
+ }
417
448
  return function validateRequiredOnly(data, dataPath, dataRoot, dataKeys) {
418
449
  // Required properties only apply to objects, not arrays or other types
419
450
  if (typeof data !== 'object' || data === null || Array.isArray(data)) {
@@ -423,8 +454,10 @@ export function compileObjectPrimitives(schemaObj, jsonSchema) {
423
454
  let valid = true;
424
455
  for (let i = 0; i < rlength; ++i) {
425
456
  const key = required[i];
426
- if (keys.indexOf(key) === -1)
427
- valid &&= addError(key, data, dataPath);
457
+ if (keys.indexOf(key) === -1) {
458
+ valid = addError(key, data, dataPath) && valid;
459
+ if (stopAtFirst) break;
460
+ }
428
461
  }
429
462
  return valid;
430
463
  };
@@ -441,15 +474,19 @@ export function compileObjectPrimitives(schemaObj, jsonSchema) {
441
474
  }
442
475
  const keys = dataKeys || Object.keys(data);
443
476
  const len = keys.length;
444
- if (len < min && !addMinError(len, dataPath))
445
- return false;
477
+ // minProperties and required are independent; a short count says
478
+ // nothing about WHICH members are missing, which is the useful half.
479
+ let sizeOk = len >= min || addMinError(len, dataPath);
480
+ if (stopAtFirst && !sizeOk) return false;
446
481
  let valid = true;
447
482
  for (let i = 0; i < rlength; ++i) {
448
483
  const key = required[i];
449
- if (keys.indexOf(key) === -1)
450
- valid &&= addReqError(key, data, dataPath);
484
+ if (keys.indexOf(key) === -1) {
485
+ valid = addReqError(key, data, dataPath) && valid;
486
+ if (stopAtFirst) break;
487
+ }
451
488
  }
452
- return valid;
489
+ return sizeOk && valid;
453
490
  };
454
491
  }
455
492
 
@@ -464,15 +501,18 @@ export function compileObjectPrimitives(schemaObj, jsonSchema) {
464
501
  }
465
502
  const keys = dataKeys || Object.keys(data);
466
503
  const len = keys.length;
467
- if (len > max && !addMaxError(len, dataPath))
468
- return false;
504
+ // maxProperties and required are independent (see the min case).
505
+ let sizeOk = len <= max || addMaxError(len, dataPath);
506
+ if (stopAtFirst && !sizeOk) return false;
469
507
  let valid = true;
470
508
  for (let i = 0; i < rlength; ++i) {
471
509
  const key = required[i];
472
- if (keys.indexOf(key) === -1)
473
- valid &&= addReqError(key, data, dataPath);
510
+ if (keys.indexOf(key) === -1) {
511
+ valid = addReqError(key, data, dataPath) && valid;
512
+ if (stopAtFirst) break;
513
+ }
474
514
  }
475
- return valid;
515
+ return sizeOk && valid;
476
516
  };
477
517
  }
478
518
 
@@ -484,9 +524,16 @@ export function compileObjectPrimitives(schemaObj, jsonSchema) {
484
524
  return function validateObjectPrimitives(data, dataPath, dataRoot, dataKeys) {
485
525
  const keys = dataKeys || Object.keys(data);
486
526
  const len = keys.length;
487
- return isMinProperties(len, dataPath)
488
- && isMaxProperties(len, dataPath)
489
- && hasRequiredProperties(data, keys, dataPath);
527
+ if (stopAtFirst) {
528
+ return isMinProperties(len, dataPath)
529
+ && isMaxProperties(len, dataPath)
530
+ && hasRequiredProperties(data, keys, dataPath);
531
+ }
532
+ // min/max/required are three independent assertions about the same
533
+ // object; each is the only thing that can report its own fault.
534
+ let valid = isMinProperties(len, dataPath);
535
+ valid = isMaxProperties(len, dataPath) && valid;
536
+ return hasRequiredProperties(data, keys, dataPath) && valid;
490
537
  };
491
538
  }
492
539
 
@@ -521,7 +568,7 @@ function compileObjectProperty(schemaObj, jsonSchema) {
521
568
  // Build the child dataPath by appending the property key
522
569
  const newPath = dataPath ? `${dataPath}/${dataKey}` : `/${dataKey}`;
523
570
 
524
- result.addValid(validateName(dataKey))
571
+ result.addValid(validateName(dataKey, dataPath))
525
572
  .addResult(validateProperty(data, newPath, dataRoot, dataKey))
526
573
  .addResult(validatePattern(data, newPath, dataRoot, dataKey))
527
574
  .addValid(validateDepRequired(data, newPath, dataRoot, dataKey))
@@ -584,7 +631,7 @@ function compileObjectChildrenFast(schemaObj, jsonSchema) {
584
631
  const len = dataKeys.length;
585
632
  for (let i = 0; i < len; ++i) {
586
633
  const dataKey = dataKeys[i];
587
- if (validateName != null && validateName(dataKey) === false)
634
+ if (validateName != null && validateName(dataKey, dataPath) === false)
588
635
  return false;
589
636
 
590
637
  let matched = false;
@@ -675,9 +722,10 @@ export function compileObjectSchema(schemaObj, jsonSchema) {
675
722
  if (isOfSchemaType(jsonSchema, 'map'))
676
723
  return undefined;
677
724
 
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.
725
+ // Fast path: properties(+required)-only schema in skipErrors mode.
726
+ // Iterate the (fixed) schema keys with direct property access instead of
727
+ // allocating Object.keys(data) and doing a map lookup per data key;
728
+ // required membership is a per-key Object.hasOwn probe.
681
729
  if (schemaObj.options.skipErrors
682
730
  && jsonSchema.patternProperties == null
683
731
  && jsonSchema.additionalProperties == null
@@ -687,12 +735,21 @@ export function compileObjectSchema(schemaObj, jsonSchema) {
687
735
  && jsonSchema.dependentRequired == null
688
736
  && jsonSchema.minProperties == null
689
737
  && jsonSchema.maxProperties == null
690
- && jsonSchema.required == null
738
+ && (jsonSchema.required == null
739
+ || (isArrayClass(jsonSchema.required)
740
+ && jsonSchema.required.every(key => typeof key === 'string')))
691
741
  && getObjectType(jsonSchema.properties) != null) {
692
742
  const propsMap = buildPropertyValidators(schemaObj, jsonSchema);
693
743
  if (propsMap == null)
694
744
  return undefined;
695
745
 
746
+ // required belongs to the validation vocabulary; assert nothing when
747
+ // the metaschema disables it.
748
+ const requiredKeys = schemaObj.options.vocabValidation !== false
749
+ ? getArrayClassMinItems(jsonSchema.required, 1) || null
750
+ : null;
751
+ const requiredCount = requiredKeys === null ? 0 : requiredKeys.length;
752
+
696
753
  const propKeys = Array.from(propsMap.keys());
697
754
  const propValidators = Array.from(propsMap.values());
698
755
  const propCount = propKeys.length;
@@ -706,6 +763,10 @@ export function compileObjectSchema(schemaObj, jsonSchema) {
706
763
  const extendPaths = root.usesDollarData;
707
764
  return function validateObjectPropertiesOnlyTracked(data, dataPath, dataRoot) {
708
765
  if (!isObjectType(data)) return true;
766
+ for (let i = 0; i < requiredCount; ++i) {
767
+ if (!Object.hasOwn(data, requiredKeys[i]))
768
+ return false;
769
+ }
709
770
  for (let i = 0; i < propCount; ++i) {
710
771
  const key = propKeys[i];
711
772
  // Object.hasOwn: avoid picking up inherited members like toString
@@ -722,6 +783,10 @@ export function compileObjectSchema(schemaObj, jsonSchema) {
722
783
 
723
784
  return function validateObjectPropertiesOnly(data, dataPath, dataRoot) {
724
785
  if (!isObjectType(data)) return true;
786
+ for (let i = 0; i < requiredCount; ++i) {
787
+ if (!Object.hasOwn(data, requiredKeys[i]))
788
+ return false;
789
+ }
725
790
  for (let i = 0; i < propCount; ++i) {
726
791
  const key = propKeys[i];
727
792
  // Object.hasOwn: avoid picking up inherited members like toString
@@ -743,11 +808,39 @@ export function compileObjectSchema(schemaObj, jsonSchema) {
743
808
  const validatePrimitives = objectPrimitives || trueThat;
744
809
  const validateChildren = objectChildren || trueThat;
745
810
 
746
- return function validateObjectSchema(data, dataPath, dataRoot) {
811
+ // Without child validators the data keys are only consumed by the
812
+ // min/max length checks, which compute them on demand; skip the
813
+ // per-validation Object.keys allocation.
814
+ if (objectChildren == null) {
815
+ return function validateObjectPrimitivesSchema(data, dataPath, dataRoot) {
816
+ if (isObjectType(data)) {
817
+ return validatePrimitives(data, dataPath, dataRoot, undefined);
818
+ }
819
+ return true;
820
+ };
821
+ }
822
+
823
+ if (schemaObj.options.skipErrors) {
824
+ return function validateObjectSchema(data, dataPath, dataRoot) {
825
+ if (isObjectType(data)) {
826
+ const dataKeys = Object.keys(data);
827
+ return validatePrimitives(data, dataPath, dataRoot, dataKeys)
828
+ && validateChildren(data, dataPath, dataRoot, dataKeys);
829
+ }
830
+ return true;
831
+ };
832
+ }
833
+
834
+ // `required`/`minProperties` and the per-property schemas are independent:
835
+ // the child walk re-derives everything it needs from the data and cannot
836
+ // fault on a missing key. Stopping after a missing `required` would report
837
+ // the absent property and hide every fault in the properties that ARE
838
+ // present, which is the difference between one issue and a usable list.
839
+ return function validateObjectSchemaAll(data, dataPath, dataRoot) {
747
840
  if (isObjectType(data)) {
748
841
  const dataKeys = Object.keys(data);
749
- return validatePrimitives(data, dataPath, dataRoot, dataKeys)
750
- && validateChildren(data, dataPath, dataRoot, dataKeys);
842
+ const primitives = validatePrimitives(data, dataPath, dataRoot, dataKeys);
843
+ return validateChildren(data, dataPath, dataRoot, dataKeys) && primitives;
751
844
  }
752
845
  return true;
753
846
  };
package/src/query.js CHANGED
@@ -12,6 +12,18 @@
12
12
 
13
13
  import { JarenValidator } from './index.js';
14
14
 
15
+ /**
16
+ * Project a diagnostic string from whatever the validator threw, without
17
+ * reading `.message` off a raw value or coercing it.
18
+ * @param {unknown} e
19
+ * @returns {string}
20
+ */
21
+ function failureText(e) {
22
+ if (e instanceof Error && typeof e.message === 'string')
23
+ return e.message;
24
+ return typeof e === 'string' ? e : 'schema compilation failed';
25
+ }
26
+
15
27
  /**
16
28
  * Create a `compileTypeTest` hook for `compileJsonQuery` (see
17
29
  * `@jarenjs/json/query`), backed by a `JarenValidator`.
@@ -44,8 +56,22 @@ export function createTypeTestCompiler(validator = undefined) {
44
56
  const instance = validator == null
45
57
  ? new JarenValidator()
46
58
  : (typeof validator === 'function' ? validator() : validator);
47
- return function compileTypeTest(schemaJson) {
48
- const validate = instance.compile(schemaJson);
59
+ return function compileTypeTest(schemaJson, docPath) {
60
+ let validate;
61
+ try {
62
+ validate = instance.compile(schemaJson);
63
+ }
64
+ catch (err) {
65
+ // The engine's JQ0009 names the operator that owns the schema; this
66
+ // names the schema literal itself, which is what distinguishes one
67
+ // failing schema from the others in the same query document.
68
+ const where = typeof docPath === 'string' && docPath !== '' ? docPath : '';
69
+ throw new Error(
70
+ where === ''
71
+ ? failureText(err)
72
+ : `schema literal at '${where}': ${failureText(err)}`,
73
+ { cause: err });
74
+ }
49
75
  // The default validator options are boolean mode (skipErrors on,
50
76
  // collectErrors off): the compiled function IS the predicate. An
51
77
  // error-collecting instance returns { valid, errors } objects
package/src/schema.js CHANGED
@@ -5,6 +5,10 @@ import {
5
5
  getStringType,
6
6
  isObjectClass,
7
7
  } from '@jarenjs/core';
8
+ import {
9
+ NUMERIC_CONSTRAINTS, STRING_CONSTRAINTS,
10
+ ARRAY_CONSTRAINTS, OBJECT_CONSTRAINTS,
11
+ } from '@jarenjs/core/schema';
8
12
 
9
13
  import {
10
14
  getUniqueArray,
@@ -15,20 +19,23 @@ import {
15
19
  } from '@jarenjs/core/number';
16
20
 
17
21
  import {
18
- falseThat,
19
22
  trueThat,
20
23
  addFunctionToArray,
21
24
  } from '@jarenjs/core/function';
22
25
 
23
26
  import {
27
+ combineIndependent,
24
28
  createIsSchemaTypeHandler,
29
+ hasSchemaRef,
30
+ hasSchemaRecursiveRef,
31
+ hasSchemaDynamicRef,
25
32
  } from './tools.js';
26
33
 
27
34
  import {
28
35
  getStringLength,
29
- getSegmenter,
30
36
  } from '@jarenjs/core/string';
31
37
 
38
+ import { compileErrorMessageSpec } from './messages.js';
32
39
  import { compileFormatBasic } from './format.js';
33
40
  import { compileEnumBasic } from './enum.js';
34
41
  import { compileNumberBasic } from './number.js';
@@ -41,9 +48,8 @@ import { compileCombineSchema } from './combine.js';
41
48
  import { compileConditionSchema } from './condition.js';
42
49
  import { compileDataSchema } from './data.js';
43
50
  import { compileQuerySchema } from './query-keyword.js';
44
- import { compileDollarDataSchema, hasDollarDataReferences } from './dollar-data.js';
51
+ import { compileDollarDataSchema } from './dollar-data.js';
45
52
  import { wrapUnevaluated } from './unevaluated.js';
46
- import { hasSchemaRef, hasSchemaRecursiveRef, hasSchemaDynamicRef } from './tools.js';
47
53
  import { createJsonPointer } from './traverse.js';
48
54
 
49
55
  /**
@@ -168,7 +174,7 @@ function compileDynamicAnchorRef(schemaObj, jsonSchema) {
168
174
  let targetObj;
169
175
  try {
170
176
  targetObj = root.resolveObject(resolvedRef, baseUri, { $ref: resolvedRef });
171
- } catch (e) {
177
+ } catch (_e) {
172
178
  return addError(data, dataPath);
173
179
  }
174
180
  if (targetObj) {
@@ -414,6 +420,20 @@ export function compileSchemaObject(schemaObj, jsonSchema) {
414
420
  throw new Error('JSON Schema MUST be a boolean or Object Type');
415
421
 
416
422
  const keys = Object.keys(jsonSchema);
423
+
424
+ // 'errorMessage' is report-time metadata: compile its spec once and
425
+ // register it on the root - NO validator closure is emitted (the keyword
426
+ // contributes zero validation-time work), and the key is excluded from
427
+ // the single-keyword counts so it cannot knock a node off the fast
428
+ // paths below.
429
+ let keyCount = keys.length;
430
+ if (jsonSchema.errorMessage !== undefined) {
431
+ schemaObj.root.registerErrorMessage(
432
+ schemaObj.path,
433
+ compileErrorMessageSpec(jsonSchema.errorMessage, schemaObj.path));
434
+ keyCount -= 1;
435
+ }
436
+
417
437
  if (keys.length === 0)
418
438
  return trueThat;
419
439
 
@@ -423,7 +443,9 @@ export function compileSchemaObject(schemaObj, jsonSchema) {
423
443
  // We need to check the actual behavior based on schema context.
424
444
  // If schema has ONLY $ref (and meta keywords), use the ref-only path.
425
445
  // If schema has $ref with validation siblings, process them together (2019-09+ only).
426
- const draftVersion = schemaObj.options.draftVersion || 7;
446
+ // The resource's own declared draft decides $ref-sibling behavior; see the
447
+ // note in ValidationObject.compileValidator.
448
+ const draftVersion = schemaObj.declaredDraft ?? schemaObj.options.draftVersion ?? 7;
427
449
  // When compiling the sibling keywords of a $ref schema, the unevaluated*
428
450
  // wrapper is applied by ValidationObject.compileValidator around the
429
451
  // combined (ref + siblings) validator instead of here, so that the
@@ -431,13 +453,16 @@ export function compileSchemaObject(schemaObj, jsonSchema) {
431
453
  let refWithSiblings = false;
432
454
  if (hasSchemaRef(jsonSchema) && !hasSchemaRecursiveRef(jsonSchema)) {
433
455
  // Check if there are any validation-related sibling keywords
434
- // In draft 2019-09+, if there are validation siblings, we process them together
435
- const validationKeywords = ['type', 'const', 'enum', 'multipleOf', 'maximum', 'exclusiveMaximum',
436
- 'minimum', 'exclusiveMinimum', 'maxLength', 'minLength', 'pattern', 'maxItems', 'minItems',
437
- 'uniqueItems', 'maxContains', 'minContains', 'maxProperties', 'minProperties', 'required',
456
+ // In draft 2019-09+, if there are validation siblings, we process
457
+ // them together. Membership-only (order-insensitive): the shared
458
+ // constraint groups plus the applicators and extras.
459
+ const validationKeywords = ['type', 'const', 'enum',
460
+ ...NUMERIC_CONSTRAINTS, ...STRING_CONSTRAINTS,
461
+ ...ARRAY_CONSTRAINTS, 'maxContains', 'minContains',
462
+ ...OBJECT_CONSTRAINTS, 'required',
438
463
  'dependentRequired', 'properties', 'patternProperties', 'additionalProperties', 'items',
439
464
  'prefixItems', 'additionalItems', 'contains', 'allOf', 'anyOf', 'oneOf', 'not', 'if',
440
- 'then', 'else', 'propertyNames', 'format', 'contentEncoding', 'contentMediaType',
465
+ 'then', 'else', 'propertyNames', 'contentEncoding', 'contentMediaType',
441
466
  'unevaluatedProperties', 'unevaluatedItems', '$query'];
442
467
  const hasValidationSiblings = keys.some(k => validationKeywords.includes(k));
443
468
  // In draft 7 and earlier, $ref always overrides siblings regardless
@@ -449,10 +474,6 @@ export function compileSchemaObject(schemaObj, jsonSchema) {
449
474
  refWithSiblings = true;
450
475
  }
451
476
 
452
- // Check if schema has any $data references
453
- // If so, we need to use the $data-aware compilation path
454
- const hasDollarData = hasDollarDataReferences(jsonSchema);
455
-
456
477
  // When the metaschema's $vocabulary omits the validation vocabulary,
457
478
  // keywords like type/enum/minimum/minLength assert nothing.
458
479
  const vocabValidation = schemaObj.options.vocabValidation !== false;
@@ -461,7 +482,7 @@ export function compileSchemaObject(schemaObj, jsonSchema) {
461
482
  // These inline the validation to reduce function call overhead
462
483
 
463
484
  // Fast path: type-only schema (most common case: {"type": "string"})
464
- if (vocabValidation && keys.length === 1 && jsonSchema.type !== undefined) {
485
+ if (vocabValidation && keyCount === 1 && jsonSchema.type !== undefined) {
465
486
  const type = jsonSchema.type;
466
487
  // Only handle single type strings here (not arrays of types)
467
488
  if (typeof type === 'string') {
@@ -502,29 +523,45 @@ export function compileSchemaObject(schemaObj, jsonSchema) {
502
523
  }
503
524
 
504
525
  // Fast path: required-only schema (common case: {"required": ["foo", "bar"]})
505
- if (vocabValidation && keys.length === 1 && jsonSchema.required !== undefined) {
526
+ if (vocabValidation && keyCount === 1 && jsonSchema.required !== undefined) {
506
527
  const required = jsonSchema.required;
507
- if (Array.isArray(required) && required.length > 0) {
528
+ // Non-string entries (invalid schemas) can never match a data key;
529
+ // they stay on the generic Object.keys path.
530
+ if (Array.isArray(required) && required.length > 0
531
+ && required.every(key => typeof key === 'string')) {
508
532
  const addError = schemaObj.createErrorHandler(required, ['required']);
509
533
  const rlen = required.length;
510
534
 
511
- return function validateRequiredOnly(data, dataPath) {
512
- // Required only applies to objects, not arrays or primitives
535
+ if (schemaObj.options.skipErrors) {
536
+ return function validateRequiredOnly(data, dataPath) {
537
+ // Required only applies to objects, not arrays or primitives
538
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) return true;
539
+ for (let i = 0; i < rlen; i++) {
540
+ if (!Object.hasOwn(data, required[i])) {
541
+ return addError(required[i], data, dataPath);
542
+ }
543
+ }
544
+ return true;
545
+ };
546
+ }
547
+
548
+ // Every absent property is its own fault to report; the fast path must
549
+ // not be the reason a caller only learns about the first one.
550
+ return function validateRequiredOnlyAll(data, dataPath) {
513
551
  if (typeof data !== 'object' || data === null || Array.isArray(data)) return true;
514
- const dataKeys = Object.keys(data);
552
+ let valid = true;
515
553
  for (let i = 0; i < rlen; i++) {
516
- if (dataKeys.indexOf(required[i]) === -1) {
517
- return addError(required[i], data, dataPath);
518
- }
554
+ if (!Object.hasOwn(data, required[i]))
555
+ valid = addError(required[i], data, dataPath) && valid;
519
556
  }
520
- return true;
557
+ return valid;
521
558
  };
522
559
  }
523
560
  }
524
561
 
525
562
  // Fast path: minLength-only schema (common case: {"minLength": 2})
526
563
  // This avoids the overhead of compileStringBasic for simple cases
527
- if (vocabValidation && keys.length === 1 && jsonSchema.minLength !== undefined) {
564
+ if (vocabValidation && keyCount === 1 && jsonSchema.minLength !== undefined) {
528
565
  const min = jsonSchema.minLength;
529
566
  if (typeof min === 'number' && min > 0 && Number.isFinite(min)) {
530
567
  const addError = schemaObj.createErrorHandler(min, 'minLength');
@@ -548,7 +585,7 @@ export function compileSchemaObject(schemaObj, jsonSchema) {
548
585
  }
549
586
 
550
587
  // Fast path: maxLength-only schema (common case: {"maxLength": 10})
551
- if (vocabValidation && keys.length === 1 && jsonSchema.maxLength !== undefined) {
588
+ if (vocabValidation && keyCount === 1 && jsonSchema.maxLength !== undefined) {
552
589
  const max = jsonSchema.maxLength;
553
590
  if (typeof max === 'number' && max >= 0 && Number.isFinite(max)) {
554
591
  const addError = schemaObj.createErrorHandler(max, 'maxLength');
@@ -613,6 +650,14 @@ export function compileSchemaObject(schemaObj, jsonSchema) {
613
650
  if (validators.length === 1)
614
651
  return finalize(validators[0]);
615
652
 
653
+ // These are the node's KEYWORD GROUPS (type, string, number, object,
654
+ // array, combine, ...) and they are independent of one another: each
655
+ // re-guards the data type it applies to, so continuing past a failed group
656
+ // is safe. Short-circuiting them is a boolean-mode optimization; when
657
+ // errors are recorded it lets one group's failure hide every other group's.
658
+ if (!schemaObj.options.skipErrors)
659
+ return finalize(combineIndependent(validators));
660
+
616
661
  if (validators.length === 2) {
617
662
  const first = validators[0];
618
663
  const second = validators[1];
package/src/string.js CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  import {
4
4
  isStringType,
5
- getStringType,
6
5
  } from '@jarenjs/core';
7
6
 
8
7
  import {
@@ -12,7 +11,6 @@ import {
12
11
  import {
13
12
  createRegExp,
14
13
  getStringLength,
15
- getSegmenter,
16
14
  } from '@jarenjs/core/string';
17
15
 
18
16
  import {
@@ -70,11 +68,25 @@ function compileStringIntern(schemaObj, jsonSchema) {
70
68
  const isMatch = pattern || trueThat;
71
69
  const useGrapheme = schemaObj.options.useGrapheme;
72
70
 
73
- return function validateStringIntern(data, dataPath) {
71
+ if (schemaObj.options.skipErrors) {
72
+ return function validateStringIntern(data, dataPath) {
73
+ const len = getStringLength(data, useGrapheme);
74
+ return isMinLength(len, dataPath)
75
+ && isMaxLength(len, dataPath)
76
+ && isMatch(data, dataPath);
77
+ };
78
+ }
79
+
80
+ // Length and pattern are independent assertions over the same string:
81
+ // `len` is computed before any of them and `isMatch` reads the raw data,
82
+ // so a failed `minLength` says nothing about whether `pattern` holds.
83
+ // Short-circuiting them is a boolean-mode optimization; when errors are
84
+ // recorded it would hide half the reasons the value is wrong.
85
+ return function validateStringInternAll(data, dataPath) {
74
86
  const len = getStringLength(data, useGrapheme);
75
- return isMinLength(len, dataPath)
76
- && isMaxLength(len, dataPath)
77
- && isMatch(data, dataPath);
87
+ let valid = isMinLength(len, dataPath);
88
+ valid = isMaxLength(len, dataPath) && valid;
89
+ return isMatch(data, dataPath) && valid;
78
90
  };
79
91
  }
80
92