schema-shield 0.0.6 → 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 +219 -65
  2. package/dist/formats.d.ts.map +1 -1
  3. package/dist/index.d.ts +25 -6
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1837 -484
  6. package/dist/index.min.js +1 -1
  7. package/dist/index.min.js.map +1 -1
  8. package/dist/index.mjs +1837 -484
  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} +7 -9
  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 +468 -155
  25. package/lib/index.ts +702 -107
  26. package/lib/keywords/array-keywords.ts +260 -52
  27. package/lib/keywords/number-keywords.ts +1 -1
  28. package/lib/keywords/object-keywords.ts +295 -88
  29. package/lib/keywords/other-keywords.ts +263 -70
  30. package/lib/keywords/string-keywords.ts +123 -7
  31. package/lib/types.ts +5 -18
  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.ts → utils/main-utils.ts} +63 -77
  36. package/lib/utils/pattern-matcher.ts +66 -0
  37. package/package.json +2 -2
  38. package/tsconfig.json +4 -4
  39. package/dist/utils.d.ts.map +0 -1
@@ -1,17 +1,108 @@
1
- import { 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
- // Object
7
98
  required(schema, data, defineError) {
8
- if (!isObject(data)) {
99
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
9
100
  return;
10
101
  }
11
102
 
12
103
  for (let i = 0; i < schema.required.length; i++) {
13
104
  const key = schema.required[i];
14
- if (!data.hasOwnProperty(key)) {
105
+ if (!Object.prototype.hasOwnProperty.call(data, key)) {
15
106
  return defineError("Required property is missing", {
16
107
  item: key,
17
108
  data: data[key]
@@ -23,21 +114,65 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
23
114
  },
24
115
 
25
116
  properties(schema, data, defineError) {
26
- if (!isObject(data)) {
117
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
27
118
  return;
28
119
  }
29
120
 
30
- for (const key of Object.keys(schema.properties)) {
31
- if (!data.hasOwnProperty(key)) {
32
- const schemaProp = schema.properties[key];
33
- if (isObject(schemaProp) && "default" in schemaProp) {
34
- data[key] = schemaProp.default;
121
+ let propKeys = (schema as any)._propKeys as string[] | undefined;
122
+ if (!propKeys) {
123
+ propKeys = Object.keys(schema.properties || {});
124
+ Object.defineProperty(schema, "_propKeys", {
125
+ value: propKeys,
126
+ enumerable: false,
127
+ configurable: false,
128
+ writable: false
129
+ });
130
+ }
131
+
132
+ let requiredSet = (schema as any)._requiredSet as
133
+ | Set<string>
134
+ | null
135
+ | undefined;
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,
142
+ enumerable: false,
143
+ configurable: false,
144
+ writable: false
145
+ });
146
+ }
147
+
148
+ for (let i = 0; i < propKeys.length; i++) {
149
+ const key = propKeys[i];
150
+ const schemaProp = schema.properties[key];
151
+
152
+ if (!Object.prototype.hasOwnProperty.call(data, key)) {
153
+ if (
154
+ requiredSet &&
155
+ requiredSet.has(key) &&
156
+ schemaProp &&
157
+ typeof schemaProp === "object" &&
158
+ !Array.isArray(schemaProp) &&
159
+ "default" in schemaProp
160
+ ) {
161
+ const error = (schemaProp as any).$validate(schemaProp.default);
162
+ if (error) {
163
+ return defineError("Default property is invalid", {
164
+ item: key,
165
+ cause: error,
166
+ data: schemaProp.default
167
+ });
168
+ }
169
+ data[key] = deepCloneUnfreeze(schemaProp.default);
35
170
  }
36
171
  continue;
37
172
  }
38
173
 
39
- if (typeof schema.properties[key] === "boolean") {
40
- if (schema.properties[key] === false) {
174
+ if (typeof schemaProp === "boolean") {
175
+ if (schemaProp === false) {
41
176
  return defineError("Property is not allowed", {
42
177
  item: key,
43
178
  data: data[key]
@@ -46,8 +181,8 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
46
181
  continue;
47
182
  }
48
183
 
49
- if ("$validate" in schema.properties[key]) {
50
- const error = schema.properties[key].$validate(data[key]);
184
+ if (schemaProp && "$validate" in schemaProp) {
185
+ const error = schemaProp.$validate(data[key]);
51
186
  if (error) {
52
187
  return defineError("Property is invalid", {
53
188
  item: key,
@@ -60,15 +195,23 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
60
195
 
61
196
  return;
62
197
  },
63
-
64
198
  values(schema, data, defineError) {
65
- if (!isObject(data) || !isCompiledSchema(schema.values)) {
199
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
200
+ return;
201
+ }
202
+
203
+ const valueSchema = schema.values;
204
+ const validate = valueSchema && valueSchema.$validate;
205
+ if (typeof validate !== "function") {
66
206
  return;
67
207
  }
68
208
 
69
- const keys = Object.keys(data);
70
- for (const key of keys) {
71
- const error = schema.values.$validate(data[key]);
209
+ for (const key in data) {
210
+ if (!Object.prototype.hasOwnProperty.call(data, key)) {
211
+ continue;
212
+ }
213
+
214
+ const error = validate(data[key]);
72
215
  if (error) {
73
216
  return defineError("Property is invalid", {
74
217
  item: key,
@@ -77,47 +220,83 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
77
220
  });
78
221
  }
79
222
  }
80
-
81
- return;
82
223
  },
83
224
 
84
225
  maxProperties(schema, data, defineError) {
85
- if (!isObject(data) || Object.keys(data).length <= schema.maxProperties) {
226
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
86
227
  return;
87
228
  }
88
229
 
89
- 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;
90
242
  },
91
243
 
92
244
  minProperties(schema, data, defineError) {
93
- if (!isObject(data) || Object.keys(data).length >= schema.minProperties) {
245
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
94
246
  return;
95
247
  }
96
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
+
97
260
  return defineError("Too few properties", { data });
98
261
  },
99
262
 
100
263
  additionalProperties(schema, data, defineError) {
101
- if (!isObject(data)) {
264
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
102
265
  return;
103
266
  }
104
267
 
105
- const keys = Object.keys(data);
106
- const isCompiled = isCompiledSchema(schema.additionalProperties);
107
- for (const key of keys) {
108
- if (schema.properties && schema.properties.hasOwnProperty(key)) {
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
281
+ });
282
+ }
283
+
284
+ const patternEntries = getPatternPropertyEntries(schema);
285
+
286
+ for (const key in data) {
287
+ if (!Object.prototype.hasOwnProperty.call(data, key)) {
109
288
  continue;
110
289
  }
111
290
 
112
- if (schema.patternProperties) {
113
- let match = false;
114
- for (const pattern in schema.patternProperties) {
115
- if (new RegExp(pattern, "u").test(key)) {
116
- match = true;
117
- break;
118
- }
119
- }
120
- if (match) {
291
+ if (
292
+ schema.properties &&
293
+ Object.prototype.hasOwnProperty.call(schema.properties, key)
294
+ ) {
295
+ continue;
296
+ }
297
+
298
+ if (patternEntries && patternEntries.length) {
299
+ if (getPatternKeyMatchIndexes(schema, key, patternEntries).length > 0) {
121
300
  continue;
122
301
  }
123
302
  }
@@ -129,8 +308,8 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
129
308
  });
130
309
  }
131
310
 
132
- if (isCompiled) {
133
- const error = schema.additionalProperties.$validate(data[key]);
311
+ if (apValidate) {
312
+ const error = apValidate(data[key]);
134
313
  if (error) {
135
314
  return defineError("Additional properties are invalid", {
136
315
  item: key,
@@ -143,43 +322,59 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
143
322
 
144
323
  return;
145
324
  },
146
-
147
325
  patternProperties(schema, data, defineError) {
148
- if (!isObject(data)) {
326
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
149
327
  return;
150
328
  }
151
329
 
152
- const patterns = Object.keys(schema.patternProperties);
153
- for (const pattern of patterns) {
154
- const regex = new RegExp(pattern, "u");
155
- if (typeof schema.patternProperties[pattern] === "boolean") {
156
- if (schema.patternProperties[pattern] === false) {
157
- for (const key in data) {
158
- if (regex.test(key)) {
159
- return defineError("Property is not allowed", {
160
- item: key,
161
- data: data[key]
162
- });
163
- }
164
- }
330
+ const patternEntries = getPatternPropertyEntries(schema);
331
+ if (!patternEntries || patternEntries.length === 0) {
332
+ return;
333
+ }
334
+
335
+ for (const key in data) {
336
+ if (!Object.prototype.hasOwnProperty.call(data, key)) {
337
+ continue;
338
+ }
339
+
340
+ const matchingIndexes = getPatternKeyMatchIndexes(schema, key, patternEntries);
341
+
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
+ });
165
351
  }
352
+
166
353
  continue;
167
354
  }
168
355
 
169
- const keys = Object.keys(data);
170
- for (const key of keys) {
171
- if (regex.test(key)) {
172
- if ("$validate" in schema.patternProperties[pattern]) {
173
- const error = schema.patternProperties[pattern].$validate(
174
- data[key]
175
- );
176
- if (error) {
177
- return defineError("Property is invalid", {
178
- item: key,
179
- cause: error,
180
- data: data[key]
181
- });
182
- }
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
+ });
183
378
  }
184
379
  }
185
380
  }
@@ -187,34 +382,47 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
187
382
 
188
383
  return;
189
384
  },
190
-
191
385
  propertyNames(schema, data, defineError) {
192
- if (!isObject(data)) {
386
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
193
387
  return;
194
388
  }
195
- if (typeof schema.propertyNames === "boolean") {
196
- if (schema.propertyNames === false && Object.keys(data).length > 0) {
197
- return defineError("Properties are not allowed", { data });
198
- }
199
- }
200
- if (isCompiledSchema(schema.propertyNames)) {
201
- for (let key in data) {
202
- const error = schema.propertyNames.$validate(key);
203
- if (error) {
204
- return defineError("Property name is invalid", {
205
- item: key,
206
- cause: error,
207
- data: data[key]
208
- });
389
+
390
+ const pn = schema.propertyNames;
391
+
392
+ if (typeof pn === "boolean") {
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
+ }
209
398
  }
210
399
  }
400
+
401
+ return;
211
402
  }
212
403
 
213
- return;
404
+ const validate = pn && pn.$validate;
405
+ if (typeof validate !== "function") {
406
+ return;
407
+ }
408
+
409
+ for (const key in data) {
410
+ if (!Object.prototype.hasOwnProperty.call(data, key)) {
411
+ continue;
412
+ }
413
+ const error = validate(key);
414
+ if (error) {
415
+ return defineError("Property name is invalid", {
416
+ item: key,
417
+ cause: error,
418
+ data: data[key]
419
+ });
420
+ }
421
+ }
214
422
  },
215
423
 
216
424
  dependencies(schema, data, defineError) {
217
- if (!isObject(data)) {
425
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
218
426
  return;
219
427
  }
220
428
 
@@ -266,7 +474,6 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
266
474
  default: false,
267
475
 
268
476
  // Not implemented yet
269
- $ref: false,
270
477
  definitions: false,
271
478
  $id: false,
272
479
  $schema: false,