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,16 +1,108 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isCompiledSchema } from "../utils/main-utils";
|
|
2
2
|
|
|
3
3
|
import { KeywordFunction } from "../index";
|
|
4
|
+
import { deepCloneUnfreeze } from "../utils/deep-freeze";
|
|
5
|
+
import { compilePatternMatcher } from "../utils/pattern-matcher";
|
|
6
|
+
|
|
7
|
+
const PATTERN_KEY_CACHE_LIMIT = 512;
|
|
8
|
+
|
|
9
|
+
type PatternPropertyEntry = {
|
|
10
|
+
schemaProp: any;
|
|
11
|
+
match: (key: string) => boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function getPatternPropertyEntries(schema: Record<string, any>) {
|
|
15
|
+
let entries = (schema as any)._patternPropertyEntries as
|
|
16
|
+
| PatternPropertyEntry[]
|
|
17
|
+
| undefined;
|
|
18
|
+
|
|
19
|
+
if (entries) {
|
|
20
|
+
return entries;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (
|
|
24
|
+
!schema.patternProperties ||
|
|
25
|
+
typeof schema.patternProperties !== "object" ||
|
|
26
|
+
Array.isArray(schema.patternProperties)
|
|
27
|
+
) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const patternKeys = Object.keys(schema.patternProperties);
|
|
32
|
+
entries = new Array(patternKeys.length);
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < patternKeys.length; i++) {
|
|
35
|
+
const key = patternKeys[i];
|
|
36
|
+
const compiledMatcher = compilePatternMatcher(key);
|
|
37
|
+
const match =
|
|
38
|
+
compiledMatcher instanceof RegExp
|
|
39
|
+
? (value: string) => compiledMatcher.test(value)
|
|
40
|
+
: compiledMatcher;
|
|
41
|
+
|
|
42
|
+
entries[i] = {
|
|
43
|
+
schemaProp: schema.patternProperties[key],
|
|
44
|
+
match
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Object.defineProperty(schema, "_patternPropertyEntries", {
|
|
49
|
+
value: entries,
|
|
50
|
+
enumerable: false,
|
|
51
|
+
configurable: false,
|
|
52
|
+
writable: false
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return entries;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getPatternKeyMatchIndexes(
|
|
59
|
+
schema: Record<string, any>,
|
|
60
|
+
key: string,
|
|
61
|
+
entries: PatternPropertyEntry[]
|
|
62
|
+
) {
|
|
63
|
+
let cache = (schema as any)._patternKeyMatchIndexCache as
|
|
64
|
+
| Map<string, number[]>
|
|
65
|
+
| undefined;
|
|
66
|
+
|
|
67
|
+
if (cache) {
|
|
68
|
+
const cached = cache.get(key);
|
|
69
|
+
if (cached) {
|
|
70
|
+
return cached;
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
cache = new Map<string, number[]>();
|
|
74
|
+
Object.defineProperty(schema, "_patternKeyMatchIndexCache", {
|
|
75
|
+
value: cache,
|
|
76
|
+
enumerable: false,
|
|
77
|
+
configurable: false,
|
|
78
|
+
writable: false
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const indexes: number[] = [];
|
|
83
|
+
|
|
84
|
+
for (let i = 0; i < entries.length; i++) {
|
|
85
|
+
if (entries[i].match(key)) {
|
|
86
|
+
indexes.push(i);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (cache.size < PATTERN_KEY_CACHE_LIMIT) {
|
|
91
|
+
cache.set(key, indexes);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return indexes;
|
|
95
|
+
}
|
|
4
96
|
|
|
5
97
|
export const ObjectKeywords: Record<string, KeywordFunction | false> = {
|
|
6
98
|
required(schema, data, defineError) {
|
|
7
|
-
if (!
|
|
99
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
8
100
|
return;
|
|
9
101
|
}
|
|
10
102
|
|
|
11
103
|
for (let i = 0; i < schema.required.length; i++) {
|
|
12
104
|
const key = schema.required[i];
|
|
13
|
-
if (!
|
|
105
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
14
106
|
return defineError("Required property is missing", {
|
|
15
107
|
item: key,
|
|
16
108
|
data: data[key]
|
|
@@ -22,7 +114,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
|
|
|
22
114
|
},
|
|
23
115
|
|
|
24
116
|
properties(schema, data, defineError) {
|
|
25
|
-
if (!
|
|
117
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
26
118
|
return;
|
|
27
119
|
}
|
|
28
120
|
|
|
@@ -37,34 +129,36 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
|
|
|
37
129
|
});
|
|
38
130
|
}
|
|
39
131
|
|
|
40
|
-
let
|
|
41
|
-
| string
|
|
132
|
+
let requiredSet = (schema as any)._requiredSet as
|
|
133
|
+
| Set<string>
|
|
42
134
|
| null
|
|
43
135
|
| undefined;
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
136
|
+
if (requiredSet === undefined) {
|
|
137
|
+
requiredSet = Array.isArray(schema.required)
|
|
138
|
+
? new Set<string>(schema.required)
|
|
139
|
+
: null;
|
|
140
|
+
Object.defineProperty(schema, "_requiredSet", {
|
|
141
|
+
value: requiredSet,
|
|
48
142
|
enumerable: false,
|
|
49
143
|
configurable: false,
|
|
50
144
|
writable: false
|
|
51
145
|
});
|
|
52
146
|
}
|
|
53
147
|
|
|
54
|
-
const required = requiredKeys || [];
|
|
55
|
-
|
|
56
148
|
for (let i = 0; i < propKeys.length; i++) {
|
|
57
149
|
const key = propKeys[i];
|
|
58
150
|
const schemaProp = schema.properties[key];
|
|
59
151
|
|
|
60
152
|
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
61
153
|
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
154
|
+
requiredSet &&
|
|
155
|
+
requiredSet.has(key) &&
|
|
156
|
+
schemaProp &&
|
|
157
|
+
typeof schemaProp === "object" &&
|
|
158
|
+
!Array.isArray(schemaProp) &&
|
|
65
159
|
"default" in schemaProp
|
|
66
160
|
) {
|
|
67
|
-
const error = schemaProp.$validate(schemaProp.default);
|
|
161
|
+
const error = (schemaProp as any).$validate(schemaProp.default);
|
|
68
162
|
if (error) {
|
|
69
163
|
return defineError("Default property is invalid", {
|
|
70
164
|
item: key,
|
|
@@ -72,7 +166,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
|
|
|
72
166
|
data: schemaProp.default
|
|
73
167
|
});
|
|
74
168
|
}
|
|
75
|
-
data[key] =
|
|
169
|
+
data[key] = deepCloneUnfreeze(schemaProp.default);
|
|
76
170
|
}
|
|
77
171
|
continue;
|
|
78
172
|
}
|
|
@@ -102,7 +196,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
|
|
|
102
196
|
return;
|
|
103
197
|
},
|
|
104
198
|
values(schema, data, defineError) {
|
|
105
|
-
if (!
|
|
199
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
106
200
|
return;
|
|
107
201
|
}
|
|
108
202
|
|
|
@@ -112,9 +206,11 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
|
|
|
112
206
|
return;
|
|
113
207
|
}
|
|
114
208
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
209
|
+
for (const key in data) {
|
|
210
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
118
214
|
const error = validate(data[key]);
|
|
119
215
|
if (error) {
|
|
120
216
|
return defineError("Property is invalid", {
|
|
@@ -127,71 +223,80 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
|
|
|
127
223
|
},
|
|
128
224
|
|
|
129
225
|
maxProperties(schema, data, defineError) {
|
|
130
|
-
if (!
|
|
226
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
131
227
|
return;
|
|
132
228
|
}
|
|
133
229
|
|
|
134
|
-
|
|
230
|
+
let count = 0;
|
|
231
|
+
for (const key in data) {
|
|
232
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
count++;
|
|
236
|
+
if (count > schema.maxProperties) {
|
|
237
|
+
return defineError("Too many properties", { data });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return;
|
|
135
242
|
},
|
|
136
243
|
|
|
137
244
|
minProperties(schema, data, defineError) {
|
|
138
|
-
if (!
|
|
245
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
139
246
|
return;
|
|
140
247
|
}
|
|
141
248
|
|
|
249
|
+
let count = 0;
|
|
250
|
+
for (const key in data) {
|
|
251
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
count++;
|
|
255
|
+
if (count >= schema.minProperties) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
142
260
|
return defineError("Too few properties", { data });
|
|
143
261
|
},
|
|
144
262
|
|
|
145
263
|
additionalProperties(schema, data, defineError) {
|
|
146
|
-
if (!
|
|
264
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
147
265
|
return;
|
|
148
266
|
}
|
|
149
267
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
268
|
+
let apValidate = (schema as any)._apValidate as
|
|
269
|
+
| ((data: any) => any)
|
|
270
|
+
| null
|
|
271
|
+
| undefined;
|
|
272
|
+
if (apValidate === undefined) {
|
|
273
|
+
apValidate = isCompiledSchema(schema.additionalProperties)
|
|
274
|
+
? schema.additionalProperties.$validate
|
|
275
|
+
: null;
|
|
276
|
+
Object.defineProperty(schema, "_apValidate", {
|
|
277
|
+
value: apValidate,
|
|
278
|
+
enumerable: false,
|
|
279
|
+
configurable: false,
|
|
280
|
+
writable: false
|
|
158
281
|
});
|
|
159
282
|
}
|
|
160
283
|
|
|
161
|
-
|
|
162
|
-
| { regex: RegExp; key: string }[]
|
|
163
|
-
| undefined;
|
|
284
|
+
const patternEntries = getPatternPropertyEntries(schema);
|
|
164
285
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
patternList.push({
|
|
169
|
-
regex: new RegExp(pattern, "u"),
|
|
170
|
-
key: pattern
|
|
171
|
-
});
|
|
286
|
+
for (const key in data) {
|
|
287
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
288
|
+
continue;
|
|
172
289
|
}
|
|
173
|
-
Object.defineProperty(schema, "_patternPropertiesList", {
|
|
174
|
-
value: patternList,
|
|
175
|
-
enumerable: false
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
290
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
291
|
+
if (
|
|
292
|
+
schema.properties &&
|
|
293
|
+
Object.prototype.hasOwnProperty.call(schema.properties, key)
|
|
294
|
+
) {
|
|
183
295
|
continue;
|
|
184
296
|
}
|
|
185
297
|
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
for (let j = 0; j < patternList.length; j++) {
|
|
189
|
-
if (patternList[j].regex.test(key)) {
|
|
190
|
-
match = true;
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (match) {
|
|
298
|
+
if (patternEntries && patternEntries.length) {
|
|
299
|
+
if (getPatternKeyMatchIndexes(schema, key, patternEntries).length > 0) {
|
|
195
300
|
continue;
|
|
196
301
|
}
|
|
197
302
|
}
|
|
@@ -203,8 +308,8 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
|
|
|
203
308
|
});
|
|
204
309
|
}
|
|
205
310
|
|
|
206
|
-
if (
|
|
207
|
-
const error =
|
|
311
|
+
if (apValidate) {
|
|
312
|
+
const error = apValidate(data[key]);
|
|
208
313
|
if (error) {
|
|
209
314
|
return defineError("Additional properties are invalid", {
|
|
210
315
|
item: key,
|
|
@@ -218,63 +323,58 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
|
|
|
218
323
|
return;
|
|
219
324
|
},
|
|
220
325
|
patternProperties(schema, data, defineError) {
|
|
221
|
-
if (!
|
|
326
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
222
327
|
return;
|
|
223
328
|
}
|
|
224
329
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (!patternList) {
|
|
230
|
-
patternList = [];
|
|
231
|
-
const patterns = Object.keys(schema.patternProperties || {});
|
|
232
|
-
for (let i = 0; i < patterns.length; i++) {
|
|
233
|
-
const pattern = patterns[i];
|
|
234
|
-
patternList.push({
|
|
235
|
-
regex: new RegExp(pattern, "u"),
|
|
236
|
-
key: pattern
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
Object.defineProperty(schema, "_patternPropertiesList", {
|
|
240
|
-
value: patternList,
|
|
241
|
-
enumerable: false
|
|
242
|
-
});
|
|
330
|
+
const patternEntries = getPatternPropertyEntries(schema);
|
|
331
|
+
if (!patternEntries || patternEntries.length === 0) {
|
|
332
|
+
return;
|
|
243
333
|
}
|
|
244
334
|
|
|
245
|
-
const
|
|
335
|
+
for (const key in data) {
|
|
336
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
246
339
|
|
|
247
|
-
|
|
248
|
-
const { regex, key: patternKey } = patternList[p];
|
|
249
|
-
const schemaProp = schema.patternProperties[patternKey];
|
|
340
|
+
const matchingIndexes = getPatternKeyMatchIndexes(schema, key, patternEntries);
|
|
250
341
|
|
|
251
|
-
if (
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
}
|
|
342
|
+
if (matchingIndexes.length === 0) {
|
|
343
|
+
if (
|
|
344
|
+
schema.additionalProperties === false &&
|
|
345
|
+
!(schema.properties && Object.prototype.hasOwnProperty.call(schema.properties, key))
|
|
346
|
+
) {
|
|
347
|
+
return defineError("Additional properties are not allowed", {
|
|
348
|
+
item: key,
|
|
349
|
+
data: data[key]
|
|
350
|
+
});
|
|
262
351
|
}
|
|
352
|
+
|
|
263
353
|
continue;
|
|
264
354
|
}
|
|
265
355
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
356
|
+
for (let j = 0; j < matchingIndexes.length; j++) {
|
|
357
|
+
const schemaProp = patternEntries[matchingIndexes[j]].schemaProp;
|
|
358
|
+
|
|
359
|
+
if (typeof schemaProp === "boolean") {
|
|
360
|
+
if (schemaProp === false) {
|
|
361
|
+
return defineError("Property is not allowed", {
|
|
362
|
+
item: key,
|
|
363
|
+
data: data[key]
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if ("$validate" in schemaProp) {
|
|
371
|
+
const error = schemaProp.$validate(data[key]);
|
|
372
|
+
if (error) {
|
|
373
|
+
return defineError("Property is invalid", {
|
|
374
|
+
item: key,
|
|
375
|
+
cause: error,
|
|
376
|
+
data: data[key]
|
|
377
|
+
});
|
|
278
378
|
}
|
|
279
379
|
}
|
|
280
380
|
}
|
|
@@ -283,16 +383,21 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
|
|
|
283
383
|
return;
|
|
284
384
|
},
|
|
285
385
|
propertyNames(schema, data, defineError) {
|
|
286
|
-
if (!
|
|
386
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
287
387
|
return;
|
|
288
388
|
}
|
|
289
389
|
|
|
290
390
|
const pn = schema.propertyNames;
|
|
291
391
|
|
|
292
392
|
if (typeof pn === "boolean") {
|
|
293
|
-
if (pn === false
|
|
294
|
-
|
|
393
|
+
if (pn === false) {
|
|
394
|
+
for (const key in data) {
|
|
395
|
+
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
396
|
+
return defineError("Properties are not allowed", { data });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
295
399
|
}
|
|
400
|
+
|
|
296
401
|
return;
|
|
297
402
|
}
|
|
298
403
|
|
|
@@ -317,7 +422,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
|
|
|
317
422
|
},
|
|
318
423
|
|
|
319
424
|
dependencies(schema, data, defineError) {
|
|
320
|
-
if (!
|
|
425
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
321
426
|
return;
|
|
322
427
|
}
|
|
323
428
|
|