schema-shield 1.0.0 → 1.0.1
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/README.md +38 -12
- package/dist/formats.d.ts.map +1 -1
- package/dist/index.d.ts +14 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1445 -447
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +1445 -447
- package/dist/keywords/array-keywords.d.ts.map +1 -1
- package/dist/keywords/object-keywords.d.ts.map +1 -1
- package/dist/keywords/other-keywords.d.ts.map +1 -1
- package/dist/keywords/string-keywords.d.ts.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/deep-freeze.d.ts +5 -0
- package/dist/utils/deep-freeze.d.ts.map +1 -0
- package/dist/utils/has-changed.d.ts +2 -0
- package/dist/utils/has-changed.d.ts.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/{utils.d.ts → utils/main-utils.d.ts} +3 -6
- package/dist/utils/main-utils.d.ts.map +1 -0
- package/dist/utils/pattern-matcher.d.ts +3 -0
- package/dist/utils/pattern-matcher.d.ts.map +1 -0
- package/lib/formats.ts +402 -84
- package/lib/index.ts +494 -46
- package/lib/keywords/array-keywords.ts +215 -21
- package/lib/keywords/number-keywords.ts +1 -1
- package/lib/keywords/object-keywords.ts +218 -113
- package/lib/keywords/other-keywords.ts +229 -76
- package/lib/keywords/string-keywords.ts +97 -7
- package/lib/types.ts +4 -5
- package/lib/utils/deep-freeze.ts +208 -0
- package/lib/utils/has-changed.ts +51 -0
- package/lib/utils/index.ts +4 -0
- package/lib/utils/main-utils.ts +190 -0
- package/lib/utils/pattern-matcher.ts +66 -0
- package/package.json +1 -1
- package/dist/utils.d.ts.map +0 -1
- package/lib/utils.ts +0 -362
|
@@ -1,6 +1,99 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isCompiledSchema } from "../utils/main-utils";
|
|
2
2
|
|
|
3
3
|
import { KeywordFunction } from "../index";
|
|
4
|
+
import { hasChanged } from "../utils/has-changed";
|
|
5
|
+
|
|
6
|
+
function isUniquePrimitive(value: any) {
|
|
7
|
+
return (
|
|
8
|
+
value === null ||
|
|
9
|
+
typeof value === "string" ||
|
|
10
|
+
typeof value === "number" ||
|
|
11
|
+
typeof value === "boolean"
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getArrayBucketKey(value: any[]): string {
|
|
16
|
+
const length = value.length;
|
|
17
|
+
if (length === 0) {
|
|
18
|
+
return "0";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const first = value[0];
|
|
22
|
+
const last = value[length - 1];
|
|
23
|
+
const firstType = first === null ? "null" : typeof first;
|
|
24
|
+
const lastType = last === null ? "null" : typeof last;
|
|
25
|
+
|
|
26
|
+
let firstArrayMarker = "";
|
|
27
|
+
if (Array.isArray(first)) {
|
|
28
|
+
const firstSignature = getPrimitiveArraySignature(first);
|
|
29
|
+
firstArrayMarker = firstSignature === null ? `a:${first.length}` : firstSignature;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let lastArrayMarker = "";
|
|
33
|
+
if (Array.isArray(last)) {
|
|
34
|
+
const lastSignature = getPrimitiveArraySignature(last);
|
|
35
|
+
lastArrayMarker = lastSignature === null ? `a:${last.length}` : lastSignature;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return `${length}:${firstType}:${firstArrayMarker}:${lastType}:${lastArrayMarker}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getObjectShapeKey(value: Record<string, any>): string {
|
|
42
|
+
const keys = Object.keys(value).sort();
|
|
43
|
+
return `${keys.length}:${keys.join("\u0001")}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getPrimitiveArraySignature(value: any[]): string | null {
|
|
47
|
+
const length = value.length;
|
|
48
|
+
|
|
49
|
+
if (length === 0) {
|
|
50
|
+
return "a:0";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!isUniquePrimitive(value[0]) || !isUniquePrimitive(value[length - 1])) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let signature = `a:${length}:`;
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < length; i++) {
|
|
60
|
+
const item = value[i];
|
|
61
|
+
|
|
62
|
+
if (item === null) {
|
|
63
|
+
signature += "l;";
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (typeof item === "string") {
|
|
68
|
+
signature += `s${item.length}:${item};`;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (typeof item === "number") {
|
|
73
|
+
if (Number.isNaN(item)) {
|
|
74
|
+
signature += "n:NaN;";
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (Object.is(item, -0)) {
|
|
79
|
+
signature += "n:-0;";
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
signature += `n:${item};`;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (typeof item === "boolean") {
|
|
88
|
+
signature += item ? "b:1;" : "b:0;";
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return signature;
|
|
96
|
+
}
|
|
4
97
|
|
|
5
98
|
export const ArrayKeywords: Record<string, KeywordFunction> = {
|
|
6
99
|
// lib/keywords/array-keywords.ts
|
|
@@ -110,20 +203,36 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
|
|
|
110
203
|
},
|
|
111
204
|
|
|
112
205
|
additionalItems(schema, data, defineError) {
|
|
113
|
-
if (!
|
|
206
|
+
if (!Array.isArray(data) || !Array.isArray(schema.items)) {
|
|
114
207
|
return;
|
|
115
208
|
}
|
|
116
209
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
210
|
+
let tupleLength = (schema as any)._tupleItemsLength as number | undefined;
|
|
211
|
+
if (tupleLength === undefined) {
|
|
212
|
+
tupleLength = schema.items.length;
|
|
213
|
+
Object.defineProperty(schema, "_tupleItemsLength", {
|
|
214
|
+
value: tupleLength,
|
|
215
|
+
enumerable: false,
|
|
216
|
+
configurable: false,
|
|
217
|
+
writable: false
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (data.length <= tupleLength) {
|
|
121
222
|
return;
|
|
122
223
|
}
|
|
123
224
|
|
|
124
|
-
if (
|
|
225
|
+
if (schema.additionalItems === false) {
|
|
226
|
+
return defineError("Array is too long", { data });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (
|
|
230
|
+
schema.additionalItems &&
|
|
231
|
+
typeof schema.additionalItems === "object" &&
|
|
232
|
+
!Array.isArray(schema.additionalItems)
|
|
233
|
+
) {
|
|
125
234
|
if (isCompiledSchema(schema.additionalItems)) {
|
|
126
|
-
for (let i =
|
|
235
|
+
for (let i = tupleLength; i < data.length; i++) {
|
|
127
236
|
const error = schema.additionalItems.$validate(data[i]);
|
|
128
237
|
if (error) {
|
|
129
238
|
return defineError("Array item is invalid", {
|
|
@@ -152,18 +261,50 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
|
|
|
152
261
|
return;
|
|
153
262
|
}
|
|
154
263
|
|
|
264
|
+
if (len <= 8) {
|
|
265
|
+
for (let i = 0; i < len; i++) {
|
|
266
|
+
const left = data[i];
|
|
267
|
+
|
|
268
|
+
for (let j = i + 1; j < len; j++) {
|
|
269
|
+
const right = data[j];
|
|
270
|
+
|
|
271
|
+
if (left === right) {
|
|
272
|
+
return defineError("Array items are not unique", { data: right });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (
|
|
276
|
+
typeof left === "number" &&
|
|
277
|
+
typeof right === "number" &&
|
|
278
|
+
Number.isNaN(left) &&
|
|
279
|
+
Number.isNaN(right)
|
|
280
|
+
) {
|
|
281
|
+
return defineError("Array items are not unique", { data: right });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (
|
|
285
|
+
left &&
|
|
286
|
+
right &&
|
|
287
|
+
typeof left === "object" &&
|
|
288
|
+
typeof right === "object" &&
|
|
289
|
+
!hasChanged(left, right)
|
|
290
|
+
) {
|
|
291
|
+
return defineError("Array items are not unique", { data: right });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
155
299
|
const primitiveSeen = new Set<any>();
|
|
300
|
+
let primitiveArraySignatures: Set<string> | undefined;
|
|
301
|
+
let arrayBuckets: Map<string, any[]> | undefined;
|
|
302
|
+
let objectBuckets: Map<string, any[]> | undefined;
|
|
156
303
|
|
|
157
304
|
for (let i = 0; i < len; i++) {
|
|
158
305
|
const item = data[i];
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
item === null ||
|
|
163
|
-
type === "string" ||
|
|
164
|
-
type === "number" ||
|
|
165
|
-
type === "boolean"
|
|
166
|
-
) {
|
|
306
|
+
|
|
307
|
+
if (isUniquePrimitive(item)) {
|
|
167
308
|
if (primitiveSeen.has(item)) {
|
|
168
309
|
return defineError("Array items are not unique", { data: item });
|
|
169
310
|
}
|
|
@@ -171,14 +312,66 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
|
|
|
171
312
|
continue;
|
|
172
313
|
}
|
|
173
314
|
|
|
174
|
-
if (item
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
315
|
+
if (!item || typeof item !== "object") {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (Array.isArray(item)) {
|
|
320
|
+
const signature = getPrimitiveArraySignature(item);
|
|
321
|
+
if (signature !== null) {
|
|
322
|
+
if (!primitiveArraySignatures) {
|
|
323
|
+
primitiveArraySignatures = new Set<string>();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (primitiveArraySignatures.has(signature)) {
|
|
327
|
+
return defineError("Array items are not unique", { data: item });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
primitiveArraySignatures.add(signature);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!arrayBuckets) {
|
|
335
|
+
arrayBuckets = new Map<string, any[]>();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const bucketKey = getArrayBucketKey(item);
|
|
339
|
+
let candidates = arrayBuckets.get(bucketKey);
|
|
340
|
+
|
|
341
|
+
if (!candidates) {
|
|
342
|
+
candidates = [];
|
|
343
|
+
arrayBuckets.set(bucketKey, candidates);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
for (let j = 0; j < candidates.length; j++) {
|
|
347
|
+
if (!hasChanged(candidates[j], item)) {
|
|
178
348
|
return defineError("Array items are not unique", { data: item });
|
|
179
349
|
}
|
|
180
350
|
}
|
|
351
|
+
|
|
352
|
+
candidates.push(item);
|
|
353
|
+
continue;
|
|
181
354
|
}
|
|
355
|
+
|
|
356
|
+
if (!objectBuckets) {
|
|
357
|
+
objectBuckets = new Map<string, any[]>();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const bucketKey = getObjectShapeKey(item);
|
|
361
|
+
let candidates = objectBuckets.get(bucketKey);
|
|
362
|
+
|
|
363
|
+
if (!candidates) {
|
|
364
|
+
candidates = [];
|
|
365
|
+
objectBuckets.set(bucketKey, candidates);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
for (let j = 0; j < candidates.length; j++) {
|
|
369
|
+
if (!hasChanged(candidates[j], item)) {
|
|
370
|
+
return defineError("Array items are not unique", { data: item });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
candidates.push(item);
|
|
182
375
|
}
|
|
183
376
|
},
|
|
184
377
|
|
|
@@ -197,8 +390,9 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
|
|
|
197
390
|
return defineError("Array must not contain any items", { data });
|
|
198
391
|
}
|
|
199
392
|
|
|
393
|
+
const containsValidate = schema.contains.$validate;
|
|
200
394
|
for (let i = 0; i < data.length; i++) {
|
|
201
|
-
const error =
|
|
395
|
+
const error = containsValidate(data[i]);
|
|
202
396
|
if (!error) {
|
|
203
397
|
return;
|
|
204
398
|
}
|