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.
Files changed (39) hide show
  1. package/README.md +38 -12
  2. package/dist/formats.d.ts.map +1 -1
  3. package/dist/index.d.ts +14 -3
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1445 -447
  6. package/dist/index.min.js +1 -1
  7. package/dist/index.min.js.map +1 -1
  8. package/dist/index.mjs +1445 -447
  9. package/dist/keywords/array-keywords.d.ts.map +1 -1
  10. package/dist/keywords/object-keywords.d.ts.map +1 -1
  11. package/dist/keywords/other-keywords.d.ts.map +1 -1
  12. package/dist/keywords/string-keywords.d.ts.map +1 -1
  13. package/dist/types.d.ts.map +1 -1
  14. package/dist/utils/deep-freeze.d.ts +5 -0
  15. package/dist/utils/deep-freeze.d.ts.map +1 -0
  16. package/dist/utils/has-changed.d.ts +2 -0
  17. package/dist/utils/has-changed.d.ts.map +1 -0
  18. package/dist/utils/index.d.ts +5 -0
  19. package/dist/utils/index.d.ts.map +1 -0
  20. package/dist/{utils.d.ts → utils/main-utils.d.ts} +3 -6
  21. package/dist/utils/main-utils.d.ts.map +1 -0
  22. package/dist/utils/pattern-matcher.d.ts +3 -0
  23. package/dist/utils/pattern-matcher.d.ts.map +1 -0
  24. package/lib/formats.ts +402 -84
  25. package/lib/index.ts +494 -46
  26. package/lib/keywords/array-keywords.ts +215 -21
  27. package/lib/keywords/number-keywords.ts +1 -1
  28. package/lib/keywords/object-keywords.ts +218 -113
  29. package/lib/keywords/other-keywords.ts +229 -76
  30. package/lib/keywords/string-keywords.ts +97 -7
  31. package/lib/types.ts +4 -5
  32. package/lib/utils/deep-freeze.ts +208 -0
  33. package/lib/utils/has-changed.ts +51 -0
  34. package/lib/utils/index.ts +4 -0
  35. package/lib/utils/main-utils.ts +190 -0
  36. package/lib/utils/pattern-matcher.ts +66 -0
  37. package/package.json +1 -1
  38. package/dist/utils.d.ts.map +0 -1
  39. package/lib/utils.ts +0 -362
@@ -1,16 +1,108 @@
1
- import { deepClone, isCompiledSchema, isObject } from "../utils";
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 (!isObject(data)) {
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 (!data.hasOwnProperty(key)) {
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 (!isObject(data)) {
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 requiredKeys = (schema as any)._requiredKeys as
41
- | string[]
132
+ let requiredSet = (schema as any)._requiredSet as
133
+ | Set<string>
42
134
  | null
43
135
  | undefined;
44
- if (requiredKeys === undefined) {
45
- requiredKeys = Array.isArray(schema.required) ? schema.required : null;
46
- Object.defineProperty(schema, "_requiredKeys", {
47
- value: requiredKeys,
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
- required.length &&
63
- required.indexOf(key) !== -1 &&
64
- isObject(schemaProp) &&
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] = deepClone(schemaProp.default);
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 (!isObject(data)) {
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 keys = Object.keys(data);
116
- for (let i = 0; i < keys.length; i++) {
117
- const key = keys[i];
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 (!isObject(data) || Object.keys(data).length <= schema.maxProperties) {
226
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
131
227
  return;
132
228
  }
133
229
 
134
- return defineError("Too many properties", { data });
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 (!isObject(data) || Object.keys(data).length >= schema.minProperties) {
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 (!isObject(data)) {
264
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
147
265
  return;
148
266
  }
149
267
 
150
- const keys = Object.keys(data);
151
-
152
- let apIsCompiled = (schema as any)._apIsCompiled as boolean | undefined;
153
- if (apIsCompiled === undefined) {
154
- apIsCompiled = isCompiledSchema(schema.additionalProperties);
155
- Object.defineProperty(schema, "_apIsCompiled", {
156
- value: apIsCompiled,
157
- enumerable: false
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
- let patternList = (schema as any)._patternPropertiesList as
162
- | { regex: RegExp; key: string }[]
163
- | undefined;
284
+ const patternEntries = getPatternPropertyEntries(schema);
164
285
 
165
- if (schema.patternProperties && !patternList) {
166
- patternList = [];
167
- for (const pattern in schema.patternProperties) {
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
- for (let i = 0; i < keys.length; i++) {
180
- const key = keys[i];
181
-
182
- if (schema.properties && schema.properties.hasOwnProperty(key)) {
291
+ if (
292
+ schema.properties &&
293
+ Object.prototype.hasOwnProperty.call(schema.properties, key)
294
+ ) {
183
295
  continue;
184
296
  }
185
297
 
186
- if (patternList && patternList.length) {
187
- let match = false;
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 (apIsCompiled && isCompiledSchema(schema.additionalProperties)) {
207
- const error = schema.additionalProperties.$validate(data[key]);
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 (!isObject(data)) {
326
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
222
327
  return;
223
328
  }
224
329
 
225
- let patternList = (schema as any)._patternPropertiesList as
226
- | { regex: RegExp; key: string }[]
227
- | undefined;
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 dataKeys = Object.keys(data);
335
+ for (const key in data) {
336
+ if (!Object.prototype.hasOwnProperty.call(data, key)) {
337
+ continue;
338
+ }
246
339
 
247
- for (let p = 0; p < patternList.length; p++) {
248
- const { regex, key: patternKey } = patternList[p];
249
- const schemaProp = schema.patternProperties[patternKey];
340
+ const matchingIndexes = getPatternKeyMatchIndexes(schema, key, patternEntries);
250
341
 
251
- if (typeof schemaProp === "boolean") {
252
- if (schemaProp === false) {
253
- for (let i = 0; i < dataKeys.length; i++) {
254
- const key = dataKeys[i];
255
- if (regex.test(key)) {
256
- return defineError("Property is not allowed", {
257
- item: key,
258
- data: data[key]
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
- if ("$validate" in schemaProp) {
267
- for (let i = 0; i < dataKeys.length; i++) {
268
- const key = dataKeys[i];
269
- if (regex.test(key)) {
270
- const error = schemaProp.$validate(data[key]);
271
- if (error) {
272
- return defineError("Property is invalid", {
273
- item: key,
274
- cause: error,
275
- data: data[key]
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 (!isObject(data)) {
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 && Object.keys(data).length > 0) {
294
- return defineError("Properties are not allowed", { data });
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 (!isObject(data)) {
425
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
321
426
  return;
322
427
  }
323
428