@jarenjs/validate 0.9.2 → 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.
@@ -36,8 +36,6 @@
36
36
  import {
37
37
  isObjectClass,
38
38
  isStringType,
39
- isNumberType,
40
- isArrayClass,
41
39
  isObjectType,
42
40
  } from '@jarenjs/core';
43
41
 
@@ -46,21 +44,34 @@ import {
46
44
  } from '@jarenjs/core/object';
47
45
 
48
46
  import {
49
- compileRelativeJSONPointer,
47
+ NUMERIC_CONSTRAINTS, STRING_CONSTRAINTS,
48
+ ARRAY_CONSTRAINTS, OBJECT_CONSTRAINTS,
49
+ } from '@jarenjs/core/schema';
50
+
51
+ import {
52
+ compileDataRef,
50
53
  JSONPOINTER_NOTHING,
51
54
  } from '@jarenjs/json';
52
55
 
53
- // A ref that fails the strict compile keeps the lax keyword semantics:
54
- // it resolves as not-found and the keyword asserts nothing.
55
- const resolveNothing = () => JSONPOINTER_NOTHING;
56
+ import {
57
+ createDataRefCompilers,
58
+ } from './tools.js';
56
59
 
60
+ /**
61
+ * Resolve a `$data` reference.
62
+ *
63
+ * Ajv's `$data` takes the same three forms the `data` keyword does — empty for
64
+ * the root, a Relative JSON Pointer, or an absolute JSON Pointer — and this
65
+ * used to compile only the relative form. An absolute `$data: '/limit'` threw,
66
+ * was swallowed by the catch, and became `resolveNothing`: the constraint
67
+ * silently never asserted, so a document that should have failed passed. A
68
+ * disabled constraint is worse than a rejected schema, which is why an
69
+ * uncompilable reference is now a compile-time error.
70
+ * @param {string} ref
71
+ * @returns {(dataRoot: any, dataPath: string) => any}
72
+ */
57
73
  function compileRefResolver(ref) {
58
- try {
59
- return compileRelativeJSONPointer(ref);
60
- }
61
- catch {
62
- return resolveNothing;
63
- }
74
+ return compileDataRef(ref);
64
75
  }
65
76
 
66
77
  /**
@@ -72,260 +83,12 @@ function isDollarDataRef(value) {
72
83
  return isObjectClass(value) && isStringType(value.$data);
73
84
  }
74
85
 
86
+ // The fifteen keyword validators are shared with the `data` keyword;
87
+ // only `compileRefResolver` above differs (see tools.js). uniqueItems
88
+ // and required below are $data-only and stay local.
89
+ const KEYWORD_COMPILERS = createDataRefCompilers(compileRefResolver);
75
90
 
76
- //#region Number Validators
77
-
78
- /**
79
- * Compile $data reference for minimum constraint
80
- * @param {object} schemaObj - The validation object
81
- * @param {string} ref - The $data reference (relative JSON Pointer)
82
- * @returns {function|undefined} The compiled validator function
83
- */
84
- function compileDollarDataMinimum(schemaObj, ref) {
85
- const addError = schemaObj.createErrorHandler(ref, 'minimum');
86
- const resolveRef = compileRefResolver(ref);
87
-
88
- return function validateDollarDataMinimum(data, dataPath, dataRoot) {
89
- if (!isNumberType(data)) return true;
90
-
91
- const minValue = resolveRef(dataRoot, dataPath);
92
- if (minValue === JSONPOINTER_NOTHING || !isNumberType(minValue)) return true;
93
-
94
- return data >= minValue || addError(minValue, data, dataPath);
95
- };
96
- }
97
-
98
- /**
99
- * Compile $data reference for maximum constraint
100
- * @param {object} schemaObj - The validation object
101
- * @param {string} ref - The $data reference (relative JSON Pointer)
102
- * @returns {function|undefined} The compiled validator function
103
- */
104
- function compileDollarDataMaximum(schemaObj, ref) {
105
- const addError = schemaObj.createErrorHandler(ref, 'maximum');
106
- const resolveRef = compileRefResolver(ref);
107
-
108
- return function validateDollarDataMaximum(data, dataPath, dataRoot) {
109
- if (!isNumberType(data)) return true;
110
-
111
- const maxValue = resolveRef(dataRoot, dataPath);
112
- if (maxValue === JSONPOINTER_NOTHING || !isNumberType(maxValue)) return true;
113
-
114
- return data <= maxValue || addError(maxValue, data, dataPath);
115
- };
116
- }
117
-
118
- /**
119
- * Compile $data reference for exclusiveMinimum constraint
120
- * @param {object} schemaObj - The validation object
121
- * @param {string} ref - The $data reference (relative JSON Pointer)
122
- * @returns {function|undefined} The compiled validator function
123
- */
124
- function compileDollarDataExclusiveMinimum(schemaObj, ref) {
125
- const addError = schemaObj.createErrorHandler(ref, 'exclusiveMinimum');
126
- const resolveRef = compileRefResolver(ref);
127
-
128
- return function validateDollarDataExclusiveMinimum(data, dataPath, dataRoot) {
129
- if (!isNumberType(data)) return true;
130
-
131
- const minValue = resolveRef(dataRoot, dataPath);
132
- if (minValue === JSONPOINTER_NOTHING || !isNumberType(minValue)) return true;
133
-
134
- return data > minValue || addError(minValue, data, dataPath);
135
- };
136
- }
137
-
138
- /**
139
- * Compile $data reference for exclusiveMaximum constraint
140
- * @param {object} schemaObj - The validation object
141
- * @param {string} ref - The $data reference (relative JSON Pointer)
142
- * @returns {function|undefined} The compiled validator function
143
- */
144
- function compileDollarDataExclusiveMaximum(schemaObj, ref) {
145
- const addError = schemaObj.createErrorHandler(ref, 'exclusiveMaximum');
146
- const resolveRef = compileRefResolver(ref);
147
-
148
- return function validateDollarDataExclusiveMaximum(data, dataPath, dataRoot) {
149
- if (!isNumberType(data)) return true;
150
-
151
- const maxValue = resolveRef(dataRoot, dataPath);
152
- if (maxValue === JSONPOINTER_NOTHING || !isNumberType(maxValue)) return true;
153
-
154
- return data < maxValue || addError(maxValue, data, dataPath);
155
- };
156
- }
157
-
158
- /**
159
- * Compile $data reference for multipleOf constraint
160
- * @param {object} schemaObj - The validation object
161
- * @param {string} ref - The $data reference (relative JSON Pointer)
162
- * @returns {function|undefined} The compiled validator function
163
- */
164
- function compileDollarDataMultipleOf(schemaObj, ref) {
165
- const addError = schemaObj.createErrorHandler(ref, 'multipleOf');
166
- const resolveRef = compileRefResolver(ref);
167
-
168
- return function validateDollarDataMultipleOf(data, dataPath, dataRoot) {
169
- if (!isNumberType(data)) return true;
170
-
171
- const multipleOf = resolveRef(dataRoot, dataPath);
172
- if (multipleOf === JSONPOINTER_NOTHING || !isNumberType(multipleOf)) return true;
173
-
174
- const q = data / multipleOf;
175
- return Math.abs(q - Math.round(q)) < 1e-6 || addError(multipleOf, data, dataPath);
176
- };
177
- }
178
-
179
- //#endregion
180
-
181
- //#region String Validators
182
-
183
- /**
184
- * Compile $data reference for minLength constraint
185
- * @param {object} schemaObj - The validation object
186
- * @param {string} ref - The $data reference (relative JSON Pointer)
187
- * @returns {function|undefined} The compiled validator function
188
- */
189
- function compileDollarDataMinLength(schemaObj, ref) {
190
- const addError = schemaObj.createErrorHandler(ref, 'minLength');
191
- const resolveRef = compileRefResolver(ref);
192
-
193
- return function validateDollarDataMinLength(data, dataPath, dataRoot) {
194
- if (typeof data !== 'string') return true;
195
-
196
- const minLen = resolveRef(dataRoot, dataPath);
197
- if (minLen === JSONPOINTER_NOTHING || !isNumberType(minLen)) return true;
198
-
199
- return data.length >= minLen || addError(minLen, data, dataPath);
200
- };
201
- }
202
-
203
- /**
204
- * Compile $data reference for maxLength constraint
205
- * @param {object} schemaObj - The validation object
206
- * @param {string} ref - The $data reference (relative JSON Pointer)
207
- * @returns {function|undefined} The compiled validator function
208
- */
209
- function compileDollarDataMaxLength(schemaObj, ref) {
210
- const addError = schemaObj.createErrorHandler(ref, 'maxLength');
211
- const resolveRef = compileRefResolver(ref);
212
-
213
- return function validateDollarDataMaxLength(data, dataPath, dataRoot) {
214
- if (typeof data !== 'string') return true;
215
-
216
- const maxLen = resolveRef(dataRoot, dataPath);
217
- if (maxLen === JSONPOINTER_NOTHING || !isNumberType(maxLen)) return true;
218
-
219
- return data.length <= maxLen || addError(maxLen, data, dataPath);
220
- };
221
- }
222
-
223
- /**
224
- * Compile $data reference for pattern constraint
225
- * @param {object} schemaObj - The validation object
226
- * @param {string} ref - The $data reference (relative JSON Pointer)
227
- * @returns {function|undefined} The compiled validator function
228
- */
229
- function compileDollarDataPattern(schemaObj, ref) {
230
- const addError = schemaObj.createErrorHandler(ref, 'pattern');
231
- const resolveRef = compileRefResolver(ref);
232
-
233
- return function validateDollarDataPattern(data, dataPath, dataRoot) {
234
- if (typeof data !== 'string') return true;
235
-
236
- const pattern = resolveRef(dataRoot, dataPath);
237
- if (pattern === JSONPOINTER_NOTHING || !isStringType(pattern)) return true;
238
-
239
- const regex = new RegExp(pattern, 'u');
240
- return regex.test(data) || addError(pattern, data, dataPath);
241
- };
242
- }
243
-
244
- /**
245
- * Compile $data reference for format constraint
246
- * @param {object} schemaObj - The validation object
247
- * @param {string} ref - The $data reference (relative JSON Pointer)
248
- * @returns {function|undefined} The compiled validator function
249
- */
250
- function compileDollarDataFormat(schemaObj, ref) {
251
- const formats = schemaObj.formats;
252
- if (!formats) return undefined;
253
-
254
- const addError = schemaObj.createErrorHandler(ref, 'format');
255
- const resolveRef = compileRefResolver(ref);
256
-
257
- return function validateDollarDataFormat(data, dataPath, dataRoot) {
258
- if (typeof data !== 'string') return true;
259
-
260
- const formatName = resolveRef(dataRoot, dataPath);
261
- if (formatName === JSONPOINTER_NOTHING || !isStringType(formatName)) return true;
262
-
263
- const formatCompiler = formats[formatName];
264
- if (!formatCompiler) return true;
265
-
266
- // The format compiler needs to be called to create the validator
267
- // We pass a mock schemaObj that only has createErrorHandler
268
- const mockSchemaObj = {
269
- createErrorHandler: () => () => false,
270
- options: { skipErrors: true }
271
- };
272
-
273
- try {
274
- // Get the validator function from the compiler
275
- const validator = formatCompiler(mockSchemaObj, { format: formatName });
276
- if (typeof validator !== 'function') return true;
277
-
278
- return validator(data, dataPath) || addError(formatName, data, dataPath);
279
- } catch (e) {
280
- // If compilation fails, skip validation
281
- return true;
282
- }
283
- };
284
- }
285
-
286
- //#endregion
287
-
288
- //#region Array Validators
289
-
290
- /**
291
- * Compile $data reference for minItems constraint
292
- * @param {object} schemaObj - The validation object
293
- * @param {string} ref - The $data reference (relative JSON Pointer)
294
- * @returns {function|undefined} The compiled validator function
295
- */
296
- function compileDollarDataMinItems(schemaObj, ref) {
297
- const addError = schemaObj.createErrorHandler(ref, 'minItems');
298
- const resolveRef = compileRefResolver(ref);
299
-
300
- return function validateDollarDataMinItems(data, dataPath, dataRoot) {
301
- if (!Array.isArray(data)) return true;
302
-
303
- const minItems = resolveRef(dataRoot, dataPath);
304
- if (minItems === JSONPOINTER_NOTHING || !isNumberType(minItems)) return true;
305
-
306
- return data.length >= minItems || addError(minItems, data, dataPath);
307
- };
308
- }
309
-
310
- /**
311
- * Compile $data reference for maxItems constraint
312
- * @param {object} schemaObj - The validation object
313
- * @param {string} ref - The $data reference (relative JSON Pointer)
314
- * @returns {function|undefined} The compiled validator function
315
- */
316
- function compileDollarDataMaxItems(schemaObj, ref) {
317
- const addError = schemaObj.createErrorHandler(ref, 'maxItems');
318
- const resolveRef = compileRefResolver(ref);
319
-
320
- return function validateDollarDataMaxItems(data, dataPath, dataRoot) {
321
- if (!Array.isArray(data)) return true;
322
-
323
- const maxItems = resolveRef(dataRoot, dataPath);
324
- if (maxItems === JSONPOINTER_NOTHING || !isNumberType(maxItems)) return true;
325
-
326
- return data.length <= maxItems || addError(maxItems, data, dataPath);
327
- };
328
- }
91
+ //#region $data-only Validators
329
92
 
330
93
  /**
331
94
  * Compile $data reference for uniqueItems constraint
@@ -347,7 +110,7 @@ function compileDollarDataUniqueItems(schemaObj, ref) {
347
110
  for (let i = 0; i < data.length; i++) {
348
111
  for (let j = i + 1; j < data.length; j++) {
349
112
  if (equalsDeep(data[i], data[j])) {
350
- return addError(true, data, dataPath);
113
+ return addError(data, dataPath);
351
114
  }
352
115
  }
353
116
  }
@@ -355,52 +118,6 @@ function compileDollarDataUniqueItems(schemaObj, ref) {
355
118
  };
356
119
  }
357
120
 
358
- //#endregion
359
-
360
- //#region Object Validators
361
-
362
- /**
363
- * Compile $data reference for minProperties constraint
364
- * @param {object} schemaObj - The validation object
365
- * @param {string} ref - The $data reference (relative JSON Pointer)
366
- * @returns {function|undefined} The compiled validator function
367
- */
368
- function compileDollarDataMinProperties(schemaObj, ref) {
369
- const addError = schemaObj.createErrorHandler(ref, 'minProperties');
370
- const resolveRef = compileRefResolver(ref);
371
-
372
- return function validateDollarDataMinProperties(data, dataPath, dataRoot) {
373
- if (typeof data !== 'object' || data === null || Array.isArray(data)) return true;
374
-
375
- const minProps = resolveRef(dataRoot, dataPath);
376
- if (minProps === JSONPOINTER_NOTHING || !isNumberType(minProps)) return true;
377
-
378
- const propCount = Object.keys(data).length;
379
- return propCount >= minProps || addError(minProps, data, dataPath);
380
- };
381
- }
382
-
383
- /**
384
- * Compile $data reference for maxProperties constraint
385
- * @param {object} schemaObj - The validation object
386
- * @param {string} ref - The $data reference (relative JSON Pointer)
387
- * @returns {function|undefined} The compiled validator function
388
- */
389
- function compileDollarDataMaxProperties(schemaObj, ref) {
390
- const addError = schemaObj.createErrorHandler(ref, 'maxProperties');
391
- const resolveRef = compileRefResolver(ref);
392
-
393
- return function validateDollarDataMaxProperties(data, dataPath, dataRoot) {
394
- if (typeof data !== 'object' || data === null || Array.isArray(data)) return true;
395
-
396
- const maxProps = resolveRef(dataRoot, dataPath);
397
- if (maxProps === JSONPOINTER_NOTHING || !isNumberType(maxProps)) return true;
398
-
399
- const propCount = Object.keys(data).length;
400
- return propCount <= maxProps || addError(maxProps, data, dataPath);
401
- };
402
- }
403
-
404
121
  /**
405
122
  * Compile $data reference for required constraint
406
123
  * @param {object} schemaObj - The validation object
@@ -408,7 +125,9 @@ function compileDollarDataMaxProperties(schemaObj, ref) {
408
125
  * @returns {function|undefined} The compiled validator function
409
126
  */
410
127
  function compileDollarDataRequired(schemaObj, ref) {
411
- const addError = schemaObj.createErrorHandler(ref, 'required');
128
+ // Keyed handler: the missing property name travels as the dataKey, so
129
+ // error conversion extracts params.missingProperty like static required.
130
+ const addError = schemaObj.createErrorHandler(ref, ['required']);
412
131
  const resolveRef = compileRefResolver(ref);
413
132
 
414
133
  return function validateDollarDataRequired(data, dataPath, dataRoot) {
@@ -428,52 +147,26 @@ function compileDollarDataRequired(schemaObj, ref) {
428
147
 
429
148
  //#endregion
430
149
 
431
- //#region Enum/Const Validators
432
-
433
- /**
434
- * Compile $data reference for enum constraint
435
- * @param {object} schemaObj - The validation object
436
- * @param {string} ref - The $data reference (relative JSON Pointer)
437
- * @returns {function|undefined} The compiled validator function
438
- */
439
- function compileDollarDataEnum(schemaObj, ref) {
440
- const addError = schemaObj.createErrorHandler(ref, 'enum');
441
- const resolveRef = compileRefResolver(ref);
442
-
443
- return function validateDollarDataEnum(data, dataPath, dataRoot) {
444
- if (data === undefined) return true;
445
-
446
- const enumValues = resolveRef(dataRoot, dataPath);
447
- if (enumValues === JSONPOINTER_NOTHING || !Array.isArray(enumValues)) return true;
448
-
449
- return enumValues.includes(data) || addError(enumValues, data, dataPath);
450
- };
451
- }
452
-
453
- /**
454
- * Compile $data reference for const constraint
455
- * @param {object} schemaObj - The validation object
456
- * @param {string} ref - The $data reference (relative JSON Pointer)
457
- * @returns {function|undefined} The compiled validator function
458
- */
459
- function compileDollarDataConst(schemaObj, ref) {
460
- const addError = schemaObj.createErrorHandler(ref, 'const');
461
- const resolveRef = compileRefResolver(ref);
462
-
463
- return function validateDollarDataConst(data, dataPath, dataRoot) {
464
- if (data === undefined) return true;
465
-
466
- const constValue = resolveRef(dataRoot, dataPath);
467
- if (constValue === JSONPOINTER_NOTHING) return true;
468
-
469
- return data === constValue || addError(constValue, data, dataPath);
470
- };
471
- }
472
-
473
- //#endregion
474
-
475
150
  //#region Main Compilation
476
151
 
152
+ /** Application order of the $data-capable keywords (error order is part
153
+ * of the observable behavior; the shared groups' internal order IS the
154
+ * historical per-keyword dispatch sequence — `@jarenjs/core/schema`
155
+ * documents that order as contract). */
156
+ const DOLLAR_KEYWORD_ORDER = [
157
+ ...NUMERIC_CONSTRAINTS,
158
+ ...STRING_CONSTRAINTS,
159
+ ...ARRAY_CONSTRAINTS,
160
+ ...OBJECT_CONSTRAINTS, 'required',
161
+ 'enum', 'const',
162
+ ];
163
+
164
+ const DOLLAR_ONLY_COMPILERS = {
165
+ __proto__: null,
166
+ uniqueItems: compileDollarDataUniqueItems,
167
+ required: compileDollarDataRequired,
168
+ };
169
+
477
170
  /**
478
171
  * Compile $data keyword validators for a schema object
479
172
  * This detects when keyword values are { $data: "..." } objects and creates
@@ -486,93 +179,12 @@ function compileDollarDataConst(schemaObj, ref) {
486
179
  export function compileDollarDataSchema(schemaObj, jsonSchema) {
487
180
  const validators = [];
488
181
 
489
- // Number constraints
490
- if (isDollarDataRef(jsonSchema.minimum)) {
491
- const validator = compileDollarDataMinimum(schemaObj, jsonSchema.minimum.$data);
492
- if (validator) validators.push(validator);
493
- }
494
-
495
- if (isDollarDataRef(jsonSchema.maximum)) {
496
- const validator = compileDollarDataMaximum(schemaObj, jsonSchema.maximum.$data);
497
- if (validator) validators.push(validator);
498
- }
499
-
500
- if (isDollarDataRef(jsonSchema.exclusiveMinimum)) {
501
- const validator = compileDollarDataExclusiveMinimum(schemaObj, jsonSchema.exclusiveMinimum.$data);
502
- if (validator) validators.push(validator);
503
- }
504
-
505
- if (isDollarDataRef(jsonSchema.exclusiveMaximum)) {
506
- const validator = compileDollarDataExclusiveMaximum(schemaObj, jsonSchema.exclusiveMaximum.$data);
507
- if (validator) validators.push(validator);
508
- }
509
-
510
- if (isDollarDataRef(jsonSchema.multipleOf)) {
511
- const validator = compileDollarDataMultipleOf(schemaObj, jsonSchema.multipleOf.$data);
512
- if (validator) validators.push(validator);
513
- }
514
-
515
- // String constraints
516
- if (isDollarDataRef(jsonSchema.minLength)) {
517
- const validator = compileDollarDataMinLength(schemaObj, jsonSchema.minLength.$data);
518
- if (validator) validators.push(validator);
519
- }
520
-
521
- if (isDollarDataRef(jsonSchema.maxLength)) {
522
- const validator = compileDollarDataMaxLength(schemaObj, jsonSchema.maxLength.$data);
523
- if (validator) validators.push(validator);
524
- }
525
-
526
- if (isDollarDataRef(jsonSchema.pattern)) {
527
- const validator = compileDollarDataPattern(schemaObj, jsonSchema.pattern.$data);
528
- if (validator) validators.push(validator);
529
- }
530
-
531
- if (isDollarDataRef(jsonSchema.format)) {
532
- const validator = compileDollarDataFormat(schemaObj, jsonSchema.format.$data);
533
- if (validator) validators.push(validator);
534
- }
535
-
536
- // Array constraints
537
- if (isDollarDataRef(jsonSchema.minItems)) {
538
- const validator = compileDollarDataMinItems(schemaObj, jsonSchema.minItems.$data);
539
- if (validator) validators.push(validator);
540
- }
541
-
542
- if (isDollarDataRef(jsonSchema.maxItems)) {
543
- const validator = compileDollarDataMaxItems(schemaObj, jsonSchema.maxItems.$data);
544
- if (validator) validators.push(validator);
545
- }
546
-
547
- if (isDollarDataRef(jsonSchema.uniqueItems)) {
548
- const validator = compileDollarDataUniqueItems(schemaObj, jsonSchema.uniqueItems.$data);
549
- if (validator) validators.push(validator);
550
- }
551
-
552
- // Object constraints
553
- if (isDollarDataRef(jsonSchema.minProperties)) {
554
- const validator = compileDollarDataMinProperties(schemaObj, jsonSchema.minProperties.$data);
555
- if (validator) validators.push(validator);
556
- }
557
-
558
- if (isDollarDataRef(jsonSchema.maxProperties)) {
559
- const validator = compileDollarDataMaxProperties(schemaObj, jsonSchema.maxProperties.$data);
560
- if (validator) validators.push(validator);
561
- }
182
+ for (const keyword of DOLLAR_KEYWORD_ORDER) {
183
+ const value = jsonSchema[keyword];
184
+ if (!isDollarDataRef(value)) continue;
562
185
 
563
- if (isDollarDataRef(jsonSchema.required)) {
564
- const validator = compileDollarDataRequired(schemaObj, jsonSchema.required.$data);
565
- if (validator) validators.push(validator);
566
- }
567
-
568
- // Enum/Const constraints
569
- if (isDollarDataRef(jsonSchema.enum)) {
570
- const validator = compileDollarDataEnum(schemaObj, jsonSchema.enum.$data);
571
- if (validator) validators.push(validator);
572
- }
573
-
574
- if (isDollarDataRef(jsonSchema.const)) {
575
- const validator = compileDollarDataConst(schemaObj, jsonSchema.const.$data);
186
+ const compileKeyword = KEYWORD_COMPILERS[keyword] ?? DOLLAR_ONLY_COMPILERS[keyword];
187
+ const validator = compileKeyword(schemaObj, value.$data);
576
188
  if (validator) validators.push(validator);
577
189
  }
578
190
 
@@ -594,35 +206,6 @@ export function compileDollarDataSchema(schemaObj, jsonSchema) {
594
206
  };
595
207
  }
596
208
 
597
- /**
598
- * Check if the schema has any $data references
599
- * This is used to determine if we should use the $data-aware compilation path
600
- * or the standard static compilation path.
601
- *
602
- * @param {object} jsonSchema - The JSON schema to check
603
- * @returns {boolean} True if the schema has any $data references
604
- */
605
- export function hasDollarDataReferences(jsonSchema) {
606
- if (!isObjectType(jsonSchema)) return false;
607
-
608
- // Keywords that can have $data values
609
- const keywords = [
610
- 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf',
611
- 'minLength', 'maxLength', 'pattern', 'format',
612
- 'minItems', 'maxItems', 'uniqueItems',
613
- 'minProperties', 'maxProperties', 'required',
614
- 'enum', 'const'
615
- ];
616
-
617
- for (const keyword of keywords) {
618
- if (isDollarDataRef(jsonSchema[keyword])) {
619
- return true;
620
- }
621
- }
622
-
623
- return false;
624
- }
625
-
626
209
  export function isDollarDataReference(jsonSchema) {
627
210
  return (jsonSchema !== null && (isObjectType(jsonSchema) && jsonSchema.$data));
628
211
  }
package/src/enum.js CHANGED
@@ -1,7 +1,6 @@
1
1
  //@ts-check
2
2
 
3
3
  import {
4
- isObjectType,
5
4
  isScalarType,
6
5
  } from '@jarenjs/core';
7
6
 
package/src/format.js CHANGED
@@ -40,6 +40,11 @@ export function registerFormatCompilers(registered, formatCompilers) {
40
40
  return registered;
41
41
  }
42
42
 
43
+ /** Named once: both wrong-shape paths end at the same mistake. */
44
+ const WRONG_SHAPE_HINT = 'Register the format COMPILERS (stringFormats, dateTimeFormats, '
45
+ + 'numberFormats, jsonFormats, geoFormats) rather than the raw testers (formatTesters) — '
46
+ + 'both are objects full of functions, and only the compilers take (schemaObj, jsonSchema).';
47
+
43
48
  export function getSchemaFormatCompiler(registered, name) {
44
49
  if (isStringType(name))
45
50
  return registered[name];
@@ -53,14 +58,51 @@ export function compileFormatBasic(schemaObj, jsonSchema) {
53
58
 
54
59
  // From draft 2020-12 on, format is annotation-only unless assertion is
55
60
  // enabled (formatAssertion option or format-assertion vocabulary).
61
+ // Nothing is lost by an unregistered name here — the keyword would not
62
+ // have asserted even with a compiler — so the unknownFormats check
63
+ // below deliberately does not run in this branch. Complaining here
64
+ // would be complaining about the spec.
56
65
  if (schemaObj.options.formatAssertion === false)
57
66
  return undefined;
58
67
  const compiler = getSchemaFormatCompiler(
59
68
  schemaObj.formats,
60
69
  jsonSchema.format);
61
70
 
62
- if (compiler)
63
- return compiler(schemaObj, jsonSchema);
64
- else
65
- return undefined;
71
+ // An unregistered name with assertion ON is the silent failure this
72
+ // check exists to end: the author asked for the value to be checked,
73
+ // the registry has nothing to check it with, and without this the
74
+ // schema compiles to a validator that accepts everything. Raising it
75
+ // at COMPILE time keeps instance validation spec-exact — no data is
76
+ // ever invalidated by an unknown format, the schema's AUTHOR is told.
77
+ if (compiler == null) {
78
+ if (schemaObj.options.unknownFormats === 'ignore')
79
+ return undefined;
80
+ throw new Error(`Unknown format '${jsonSchema.format}': no compiler is registered for it, `
81
+ + 'so this schema would accept every value for that keyword. Register one '
82
+ + '(addFormats(dateTimeFormats) etc. from @jarenjs/formats, or addFormat(name, compiler) '
83
+ + "for your own), or pass { unknownFormats: 'ignore' } to accept it as an annotation.");
84
+ }
85
+
86
+ // A format COMPILER is called once per schema location and returns the
87
+ // per-value validator. Handing it a bare tester — `formatTesters`
88
+ // instead of `dateTimeFormats`, an easy mistake since both are objects
89
+ // full of functions — either returns a boolean (which the caller
90
+ // silently drops, so the format checks nothing again) or throws deep
91
+ // inside the tester on an argument it never expected. Both become one
92
+ // legible complaint here.
93
+ let validator;
94
+ try {
95
+ validator = compiler(schemaObj, jsonSchema);
96
+ }
97
+ catch (err) {
98
+ throw new Error(`Format '${jsonSchema.format}' failed to compile: ${err?.message ?? err}. `
99
+ + `${WRONG_SHAPE_HINT}`, { cause: err });
100
+ }
101
+ // Nullish stays legal: a compiler may decline a schema it cannot serve.
102
+ if (validator != null && !isFn(validator) && !Array.isArray(validator)) {
103
+ throw new Error(`Format '${jsonSchema.format}' is registered with something that is not a `
104
+ + `format compiler: calling it returned ${typeof validator}, not a validator function. `
105
+ + `${WRONG_SHAPE_HINT}`);
106
+ }
107
+ return validator;
66
108
  }