@jarenjs/validate 0.8.4 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +1067 -0
- package/LICENSE +21 -0
- package/README.md +339 -2
- package/dist/types/array.d.ts +2 -0
- package/dist/types/bigint.d.ts +1 -0
- package/dist/types/combine.d.ts +1 -0
- package/dist/types/condition.d.ts +1 -0
- package/dist/types/content.d.ts +3 -0
- package/dist/types/data.d.ts +7 -0
- package/dist/types/dollar-data.d.ts +20 -0
- package/dist/types/dynamic-ref.d.ts +44 -0
- package/dist/types/enum.d.ts +1 -0
- package/dist/types/format.d.ts +21 -0
- package/dist/types/index.d.ts +874 -0
- package/dist/types/number.d.ts +1 -0
- package/dist/types/object.d.ts +3 -0
- package/dist/types/query-keyword.d.ts +19 -0
- package/dist/types/query.d.ts +29 -0
- package/dist/types/schema.d.ts +1 -0
- package/dist/types/string.d.ts +1 -0
- package/dist/types/tools.d.ts +51 -0
- package/dist/types/traverse.d.ts +32 -0
- package/dist/types/unevaluated.d.ts +12 -0
- package/package.json +32 -7
- package/src/array.js +565 -0
- package/src/bigint.js +97 -0
- package/src/combine.js +226 -0
- package/src/condition.js +109 -0
- package/src/content.js +83 -0
- package/src/data.js +477 -0
- package/src/dollar-data.js +629 -0
- package/src/dynamic-ref.js +121 -0
- package/src/enum.js +148 -0
- package/src/format.js +66 -0
- package/src/index.js +1854 -0
- package/src/number.js +159 -0
- package/src/object.js +755 -0
- package/src/query-keyword.js +99 -0
- package/src/query.js +59 -0
- package/src/schema.js +645 -0
- package/src/string.js +152 -0
- package/src/tools.js +205 -0
- package/src/traverse.js +433 -0
- package/src/unevaluated.js +151 -0
- package/dist/index.js +0 -1998
- package/dist/index.js.map +0 -7
- package/dist/index.min.js +0 -2
- package/dist/index.min.js.map +0 -7
package/src/array.js
ADDED
|
@@ -0,0 +1,565 @@
|
|
|
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
|
+
// Fast path: type-only schema (most common case) - check property directly first
|
|
236
|
+
if (itemSchema.type !== undefined && Object.keys(itemSchema).length === 1) {
|
|
237
|
+
return compileTypeOnlyValidator(itemSchema.type);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Fast path: required-only schema - check property directly first
|
|
241
|
+
if (itemSchema.required !== undefined && Object.keys(itemSchema).length === 1) {
|
|
242
|
+
const required = itemSchema.required;
|
|
243
|
+
return function validateRequiredOnly(data, dataPath, dataRoot) {
|
|
244
|
+
// Required properties only apply to objects, not arrays or other types
|
|
245
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data)) return true;
|
|
246
|
+
for (let i = 0; i < required.length; i++) {
|
|
247
|
+
if (!(required[i] in data)) return false;
|
|
248
|
+
}
|
|
249
|
+
return true;
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Fall back to full schema compilation for complex cases
|
|
255
|
+
return schemaObj.createValidator(itemSchema, key, index);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Compile a validator for simple type-only schemas
|
|
260
|
+
* @param {string} type - The type to validate
|
|
261
|
+
* @returns {Function} Type validator function
|
|
262
|
+
*/
|
|
263
|
+
function compileTypeOnlyValidator(type) {
|
|
264
|
+
switch (type) {
|
|
265
|
+
case 'string':
|
|
266
|
+
return function validateString(data) {
|
|
267
|
+
return typeof data === 'string';
|
|
268
|
+
};
|
|
269
|
+
case 'number':
|
|
270
|
+
return function validateNumber(data) {
|
|
271
|
+
return typeof data === 'number' && !isNaN(data);
|
|
272
|
+
};
|
|
273
|
+
case 'integer':
|
|
274
|
+
return function validateInteger(data) {
|
|
275
|
+
return typeof data === 'number' && Number.isInteger(data);
|
|
276
|
+
};
|
|
277
|
+
case 'boolean':
|
|
278
|
+
return function validateBoolean(data) {
|
|
279
|
+
return typeof data === 'boolean';
|
|
280
|
+
};
|
|
281
|
+
case 'array':
|
|
282
|
+
return function validateArray(data) {
|
|
283
|
+
return Array.isArray(data);
|
|
284
|
+
};
|
|
285
|
+
case 'object':
|
|
286
|
+
return function validateObject(data) {
|
|
287
|
+
return typeof data === 'object' && data !== null && !Array.isArray(data);
|
|
288
|
+
};
|
|
289
|
+
case 'null':
|
|
290
|
+
return function validateNull(data) {
|
|
291
|
+
return data === null;
|
|
292
|
+
};
|
|
293
|
+
default:
|
|
294
|
+
return trueThat;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
//#endregion
|
|
298
|
+
|
|
299
|
+
//#region Main
|
|
300
|
+
export function compileArrayPrimitives(schemaObj, jsonSchema) {
|
|
301
|
+
// minItems/maxItems/uniqueItems belong to the validation vocabulary;
|
|
302
|
+
// assert nothing when the metaschema disables it.
|
|
303
|
+
if (schemaObj.options.vocabValidation === false)
|
|
304
|
+
return undefined;
|
|
305
|
+
|
|
306
|
+
const minItems = compileMinItems(schemaObj, jsonSchema);
|
|
307
|
+
const maxItems = compileMaxItems(schemaObj, jsonSchema);
|
|
308
|
+
const uniqueItems = compileUniqueItems(schemaObj, jsonSchema);
|
|
309
|
+
|
|
310
|
+
if ((minItems
|
|
311
|
+
|| maxItems
|
|
312
|
+
|| uniqueItems) == null)
|
|
313
|
+
return undefined;
|
|
314
|
+
|
|
315
|
+
// Single-constraint schemas are the common case; skip the trueThat chain.
|
|
316
|
+
if (minItems == null && maxItems == null)
|
|
317
|
+
return uniqueItems;
|
|
318
|
+
|
|
319
|
+
if (uniqueItems == null) {
|
|
320
|
+
if (maxItems == null) {
|
|
321
|
+
return function validateArrayMinItems(data, dataPath) {
|
|
322
|
+
return minItems(data.length, dataPath);
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (minItems == null) {
|
|
326
|
+
return function validateArrayMaxItems(data, dataPath) {
|
|
327
|
+
return maxItems(data.length, dataPath);
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return function validateArrayMinMaxItems(data, dataPath) {
|
|
331
|
+
const len = data.length;
|
|
332
|
+
return minItems(len, dataPath)
|
|
333
|
+
&& maxItems(len, dataPath);
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const isMinItems = minItems || trueThat;
|
|
338
|
+
const isMaxItems = maxItems || trueThat;
|
|
339
|
+
|
|
340
|
+
return function validateArrayPrimitives(data, dataPath) {
|
|
341
|
+
const len = data.length;
|
|
342
|
+
return isMinItems(len, dataPath)
|
|
343
|
+
&& isMaxItems(len, dataPath)
|
|
344
|
+
&& uniqueItems(data, dataPath);
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function compileArrayChildren(schemaObj, jsonSchema) {
|
|
349
|
+
// Check for prefixItems (draft 2020-12+) first, then items.
|
|
350
|
+
// A document that declares an older draft via $schema treats
|
|
351
|
+
// prefixItems as an unknown keyword.
|
|
352
|
+
const prefixItems = (schemaObj.declaredDraft != null && schemaObj.declaredDraft < 2020)
|
|
353
|
+
? undefined
|
|
354
|
+
: getArrayClassMinItems(jsonSchema.prefixItems, 1);
|
|
355
|
+
const items = jsonSchema.items;
|
|
356
|
+
const isTuple = getArrayClassMinItems(items, 1) != null;
|
|
357
|
+
|
|
358
|
+
const root = schemaObj.root;
|
|
359
|
+
const track = root.usesUnevaluated;
|
|
360
|
+
// In draft 2020-12 contains produces item annotations; in 2019-09 it doesn't.
|
|
361
|
+
const trackContains = track && (schemaObj.options.draftVersion || 7) >= 2020;
|
|
362
|
+
// Item paths are consumed by error reporting and $data resolution only.
|
|
363
|
+
const extendPaths = !schemaObj.options.skipErrors || root.usesDollarData;
|
|
364
|
+
|
|
365
|
+
// Indexes below this limit count as evaluated (for unevaluatedItems) when
|
|
366
|
+
// their item validation succeeds. Extra tuple items beyond the tuple length
|
|
367
|
+
// pass validation when additionalItems/items is ABSENT, but are then not
|
|
368
|
+
// evaluated and must remain visible to unevaluatedItems.
|
|
369
|
+
let evalLimit = Infinity;
|
|
370
|
+
if (track) {
|
|
371
|
+
if (prefixItems != null) {
|
|
372
|
+
if (items === undefined) evalLimit = prefixItems.length;
|
|
373
|
+
} else if (isTuple) {
|
|
374
|
+
if (jsonSchema.additionalItems === undefined) evalLimit = items.length;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let validateItem;
|
|
379
|
+
|
|
380
|
+
if (prefixItems != null) {
|
|
381
|
+
// Draft 2020-12+ style prefixItems
|
|
382
|
+
validateItem = compilePrefixItems(schemaObj, jsonSchema);
|
|
383
|
+
} else if (isTuple) {
|
|
384
|
+
// Draft 7 style tuple items
|
|
385
|
+
validateItem = compileTupleItems(schemaObj, jsonSchema);
|
|
386
|
+
} else if (items !== undefined) {
|
|
387
|
+
// Single schema for all items
|
|
388
|
+
const itemsSchema = getObjectType(items);
|
|
389
|
+
if (itemsSchema != null) {
|
|
390
|
+
// Use direct validator compilation for items
|
|
391
|
+
validateItem = compileItemValidator(schemaObj, itemsSchema, 'items', undefined);
|
|
392
|
+
|
|
393
|
+
// Wrap single-item validator with index loop
|
|
394
|
+
const itemValidator = validateItem;
|
|
395
|
+
validateItem = function validateSingleItemSchema(data, dataPath, dataRoot, i) {
|
|
396
|
+
return itemValidator(data, dataPath, dataRoot);
|
|
397
|
+
};
|
|
398
|
+
} else if (items === true) {
|
|
399
|
+
validateItem = trueThat;
|
|
400
|
+
}
|
|
401
|
+
// items === false is fully handled by compileArrayItemsBoolean: only an
|
|
402
|
+
// empty array can pass, so a per-item validator would never be invoked.
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const validateContains = compileArrayContains(schemaObj, jsonSchema);
|
|
406
|
+
if ((validateItem || validateContains) == null)
|
|
407
|
+
return undefined;
|
|
408
|
+
|
|
409
|
+
const validateMinMax = compileContainsMinMax(schemaObj, jsonSchema) || trueThat;
|
|
410
|
+
|
|
411
|
+
const maxItems = getIntishType(jsonSchema.maxItems) || 0;
|
|
412
|
+
|
|
413
|
+
const resolveLength = len => (maxItems > 0
|
|
414
|
+
? Math.min(maxItems, len)
|
|
415
|
+
: len);
|
|
416
|
+
|
|
417
|
+
// Fast path: items only, no contains
|
|
418
|
+
if (validateContains == null && validateItem != null) {
|
|
419
|
+
// if validateItem is trueThat, just check length
|
|
420
|
+
if (validateItem === trueThat) {
|
|
421
|
+
// items: true evaluates every item, which matters when annotations
|
|
422
|
+
// are tracked for unevaluatedItems.
|
|
423
|
+
if (track) {
|
|
424
|
+
return function validateArrayItemsTrue(data, dataPath, dataRoot) {
|
|
425
|
+
root.evalLog.add(data, -1);
|
|
426
|
+
return true;
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return undefined; // No actual validation needed
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const addError = schemaObj.createErrorHandler(0, 'items');
|
|
433
|
+
const validator = validateItem;
|
|
434
|
+
|
|
435
|
+
return function validateArrayItemsOnly(data, dataPath, dataRoot) {
|
|
436
|
+
const len = resolveLength(data.length);
|
|
437
|
+
const arr = data;
|
|
438
|
+
|
|
439
|
+
let invalid = 0;
|
|
440
|
+
for (let i = 0; i < len; ++i) {
|
|
441
|
+
const itemPath = extendPaths ? dataPath + '/' + i : dataPath;
|
|
442
|
+
// Direct validator call, no intermediate wrappers
|
|
443
|
+
if (validator(arr[i], itemPath, dataRoot, i) !== true) {
|
|
444
|
+
invalid++;
|
|
445
|
+
}
|
|
446
|
+
else if (track && i < evalLimit) {
|
|
447
|
+
root.evalLog.add(data, i);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return invalid === 0
|
|
451
|
+
|| addError(invalid, dataPath);
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Fast path: contains only, no items
|
|
456
|
+
if (validateItem == null && validateContains != null) {
|
|
457
|
+
const validator = validateContains;
|
|
458
|
+
|
|
459
|
+
return function validateArrayContainsOnly(data, dataPath) {
|
|
460
|
+
const len = resolveLength(data.length);
|
|
461
|
+
const arr = data;
|
|
462
|
+
|
|
463
|
+
let contains = 0;
|
|
464
|
+
for (let i = 0; i < len; ++i) {
|
|
465
|
+
if (validator(arr[i], dataPath) === true) {
|
|
466
|
+
contains++;
|
|
467
|
+
if (trackContains) root.evalLog.add(data, i);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return validateMinMax(contains, dataPath);
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Combined: both items and contains
|
|
475
|
+
const itemValidator = validateItem;
|
|
476
|
+
const containsValidator = validateContains;
|
|
477
|
+
|
|
478
|
+
return function validateArrayChildren(data, dataPath, dataRoot) {
|
|
479
|
+
const len = resolveLength(data.length);
|
|
480
|
+
const arr = data;
|
|
481
|
+
|
|
482
|
+
let invalid = 0;
|
|
483
|
+
let contains = 0;
|
|
484
|
+
for (let i = 0; i < len; ++i) {
|
|
485
|
+
const obj = arr[i];
|
|
486
|
+
const itemPath = extendPaths ? dataPath + '/' + i : dataPath;
|
|
487
|
+
// Direct validator calls without intermediate wrappers
|
|
488
|
+
if (itemValidator(obj, itemPath, dataRoot, i) !== true) {
|
|
489
|
+
invalid++;
|
|
490
|
+
}
|
|
491
|
+
else if (track && i < evalLimit) {
|
|
492
|
+
root.evalLog.add(data, i);
|
|
493
|
+
}
|
|
494
|
+
if (containsValidator(obj, dataPath, dataRoot) === true) {
|
|
495
|
+
contains++;
|
|
496
|
+
if (trackContains) root.evalLog.add(data, i);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return invalid === 0
|
|
500
|
+
&& validateMinMax(contains, dataPath);
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export function compileArraySchema(schemaObj, jsonSchema) {
|
|
505
|
+
if (isOfSchemaType(jsonSchema, 'set'))
|
|
506
|
+
return undefined;
|
|
507
|
+
|
|
508
|
+
const compiledPrimitives = compileArrayPrimitives(schemaObj, jsonSchema);
|
|
509
|
+
const compiledItemsBoolean = compileArrayItemsBoolean(schemaObj, jsonSchema);
|
|
510
|
+
const compiledContainsBoolean = compileArrayContainsBoolean(schemaObj, jsonSchema);
|
|
511
|
+
const compiledArrayChildren = compileArrayChildren(schemaObj, jsonSchema);
|
|
512
|
+
|
|
513
|
+
if ((compiledPrimitives
|
|
514
|
+
|| compiledItemsBoolean
|
|
515
|
+
|| compiledContainsBoolean
|
|
516
|
+
|| compiledArrayChildren) === undefined)
|
|
517
|
+
return undefined;
|
|
518
|
+
|
|
519
|
+
// Single-validator schemas are the common case; skip the trueThat chain.
|
|
520
|
+
const parts = [];
|
|
521
|
+
if (compiledPrimitives) parts.push(compiledPrimitives);
|
|
522
|
+
if (compiledItemsBoolean && compiledItemsBoolean !== trueThat) parts.push(compiledItemsBoolean);
|
|
523
|
+
if (compiledContainsBoolean) parts.push(compiledContainsBoolean);
|
|
524
|
+
if (compiledArrayChildren) parts.push(compiledArrayChildren);
|
|
525
|
+
|
|
526
|
+
if (parts.length === 0)
|
|
527
|
+
return undefined;
|
|
528
|
+
|
|
529
|
+
if (parts.length === 1) {
|
|
530
|
+
const single = parts[0];
|
|
531
|
+
return function validateArraySchemaSingle(data, dataPath, dataRoot) {
|
|
532
|
+
return isArrayClass(data)
|
|
533
|
+
? single(data, dataPath, dataRoot)
|
|
534
|
+
: true;
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (parts.length === 2) {
|
|
539
|
+
const first = parts[0];
|
|
540
|
+
const second = parts[1];
|
|
541
|
+
return function validateArraySchemaDouble(data, dataPath, dataRoot) {
|
|
542
|
+
if (isArrayClass(data)) {
|
|
543
|
+
return first(data, dataPath, dataRoot)
|
|
544
|
+
&& second(data, dataPath, dataRoot);
|
|
545
|
+
}
|
|
546
|
+
return true;
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const validatePrimitives = compiledPrimitives || trueThat;
|
|
551
|
+
const hasBooleanItems = compiledItemsBoolean || trueThat;
|
|
552
|
+
const hasBooleanContains = compiledContainsBoolean || trueThat;
|
|
553
|
+
const validateItems = compiledArrayChildren || trueThat;
|
|
554
|
+
|
|
555
|
+
return function validateArraySchema(data, dataPath, dataRoot) {
|
|
556
|
+
if (isArrayClass(data)) {
|
|
557
|
+
return validatePrimitives(data, dataPath)
|
|
558
|
+
&& hasBooleanItems(data, dataPath, dataRoot)
|
|
559
|
+
&& hasBooleanContains(data, dataPath, dataRoot)
|
|
560
|
+
&& validateItems(data, dataPath, dataRoot);
|
|
561
|
+
}
|
|
562
|
+
return true;
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
//#endregion
|
package/src/bigint.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isBigIntType,
|
|
5
|
+
getBigIntType,
|
|
6
|
+
getInclusiveExclusiveBounds,
|
|
7
|
+
} from '@jarenjs/core';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
trueThat,
|
|
11
|
+
} from '@jarenjs/core/function';
|
|
12
|
+
|
|
13
|
+
function compileBigIntMaximum(schemaObj, jsonSchema) {
|
|
14
|
+
const [max, emax] = getInclusiveExclusiveBounds(
|
|
15
|
+
getBigIntType,
|
|
16
|
+
jsonSchema.maximum,
|
|
17
|
+
jsonSchema.exclusiveMaximum,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
if (emax != null) {
|
|
21
|
+
const addError = schemaObj.createErrorHandler(emax, 'exclusiveMaximum');
|
|
22
|
+
|
|
23
|
+
return function validateExclusiveMaximumBigInt(data, dataPath) {
|
|
24
|
+
return data < emax
|
|
25
|
+
|| addError(data, dataPath);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
else if (max != null) {
|
|
29
|
+
const addError = schemaObj.createErrorHandler(max, 'maximum');
|
|
30
|
+
|
|
31
|
+
return function validateMaximumBigInt(data, dataPath) {
|
|
32
|
+
return data <= max
|
|
33
|
+
|| addError(data, dataPath);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function compileBigIntMinimum(schemaObj, jsonSchema) {
|
|
41
|
+
const [min, emin] = getInclusiveExclusiveBounds(
|
|
42
|
+
getBigIntType,
|
|
43
|
+
jsonSchema.minimum,
|
|
44
|
+
jsonSchema.exclusiveMinimum,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (emin != null) {
|
|
48
|
+
const addError = schemaObj.createErrorHandler(emin, 'exclusiveMinimum');
|
|
49
|
+
|
|
50
|
+
return function validateExclusiveMinimumBigInt(data, dataPath) {
|
|
51
|
+
return data > emin
|
|
52
|
+
|| addError(data, dataPath);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
else if (min != null) {
|
|
56
|
+
const addError = schemaObj.createErrorHandler(min, 'minimum');
|
|
57
|
+
|
|
58
|
+
return function validateMinimumBigInt(data, dataPath) {
|
|
59
|
+
return data >= min
|
|
60
|
+
|| addError(data, dataPath);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function compileBigIntMultipleOf(schemaObj, jsonSchema) {
|
|
68
|
+
const mulOf = getBigIntType(jsonSchema.multipleOf);
|
|
69
|
+
if (mulOf == null) return undefined;
|
|
70
|
+
|
|
71
|
+
const addError = schemaObj.createErrorHandler(mulOf, 'multipleOf');
|
|
72
|
+
|
|
73
|
+
return function validateMultipleOfBigInt(data, dataPath) {
|
|
74
|
+
return data % mulOf === BigInt(0)
|
|
75
|
+
|| addError(data, dataPath);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function compileBigIntBasic(schemaObj, jsonSchema) {
|
|
80
|
+
const maximum = compileBigIntMaximum(schemaObj, jsonSchema);
|
|
81
|
+
const minimum = compileBigIntMinimum(schemaObj, jsonSchema);
|
|
82
|
+
const multipleOf = compileBigIntMultipleOf(schemaObj, jsonSchema);
|
|
83
|
+
if (maximum == null && minimum == null && multipleOf == null) return undefined;
|
|
84
|
+
|
|
85
|
+
const isMax = maximum || trueThat;
|
|
86
|
+
const isMin = minimum || trueThat;
|
|
87
|
+
const isMul = multipleOf || trueThat;
|
|
88
|
+
|
|
89
|
+
return function validateBigIntSchema(data, dataPath) {
|
|
90
|
+
if (isBigIntType(data)) {
|
|
91
|
+
return isMax(data, dataPath)
|
|
92
|
+
&& isMin(data, dataPath)
|
|
93
|
+
&& isMul(data, dataPath);
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
};
|
|
97
|
+
}
|