schema-shield 1.0.5 → 1.1.0

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.
@@ -1,4 +1,7 @@
1
- import { isCompiledSchema } from "../utils/main-utils";
1
+ import {
2
+ definePropertyOrThrow,
3
+ isCompiledSchema
4
+ } from "../utils/main-utils";
2
5
 
3
6
  import { KeywordFunction } from "../index";
4
7
  import { hasChanged } from "../utils/has-changed";
@@ -210,7 +213,7 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
210
213
  let tupleLength = (schema as any)._tupleItemsLength as number | undefined;
211
214
  if (tupleLength === undefined) {
212
215
  tupleLength = schema.items.length;
213
- Object.defineProperty(schema, "_tupleItemsLength", {
216
+ definePropertyOrThrow(schema, "_tupleItemsLength", {
214
217
  value: tupleLength,
215
218
  enumerable: false,
216
219
  configurable: false,
@@ -296,7 +299,9 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
296
299
  return;
297
300
  }
298
301
 
299
- const primitiveSeen = new Set<any>();
302
+ let hasFirstPrimitive = false;
303
+ let firstPrimitive: any;
304
+ let primitiveSeen: Set<any> | undefined;
300
305
  let primitiveArraySignatures: Set<string> | undefined;
301
306
  let arrayBuckets: Map<string, any[]> | undefined;
302
307
  let objectBuckets: Map<string, any[]> | undefined;
@@ -305,6 +310,16 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
305
310
  const item = data[i];
306
311
 
307
312
  if (isUniquePrimitive(item)) {
313
+ if (!hasFirstPrimitive) {
314
+ hasFirstPrimitive = true;
315
+ firstPrimitive = item;
316
+ continue;
317
+ }
318
+
319
+ if (!primitiveSeen) {
320
+ primitiveSeen = new Set<any>([firstPrimitive]);
321
+ }
322
+
308
323
  if (primitiveSeen.has(item)) {
309
324
  return defineError("Array items are not unique", { data: item });
310
325
  }
@@ -6,6 +6,9 @@ export const NumberKeywords: Record<string, KeywordFunction> = {
6
6
  if (typeof data !== "number") {
7
7
  return;
8
8
  }
9
+ if (!Number.isFinite(data)) {
10
+ return defineError("Value must be finite", { data });
11
+ }
9
12
 
10
13
  let min = schema.minimum;
11
14
  if (typeof schema.exclusiveMinimum === "number") {
@@ -25,6 +28,9 @@ export const NumberKeywords: Record<string, KeywordFunction> = {
25
28
  if (typeof data !== "number") {
26
29
  return;
27
30
  }
31
+ if (!Number.isFinite(data)) {
32
+ return defineError("Value must be finite", { data });
33
+ }
28
34
 
29
35
  let max = schema.maximum;
30
36
  if (typeof schema.exclusiveMaximum === "number") {
@@ -45,13 +51,21 @@ export const NumberKeywords: Record<string, KeywordFunction> = {
45
51
  return;
46
52
  }
47
53
 
48
- const quotient = data / schema.multipleOf;
49
-
50
- if (!isFinite(quotient)) {
51
- return;
54
+ if (
55
+ !Number.isFinite(data) ||
56
+ !Number.isFinite(schema.multipleOf) ||
57
+ schema.multipleOf <= 0
58
+ ) {
59
+ return defineError("Value must use a finite positive multipleOf", {
60
+ data
61
+ });
52
62
  }
53
63
 
54
- if (!areCloseEnough(quotient, Math.round(quotient))) {
64
+ const quotient = data / schema.multipleOf;
65
+ const valid = Number.isFinite(quotient)
66
+ ? areCloseEnough(quotient, Math.round(quotient))
67
+ : data % schema.multipleOf === 0;
68
+ if (!valid) {
55
69
  return defineError("Value is not a multiple of the multipleOf", { data });
56
70
  }
57
71
 
@@ -1,6 +1,10 @@
1
- import { isCompiledSchema } from "../utils/main-utils";
1
+ import {
2
+ definePropertyOrThrow,
3
+ hasOwn,
4
+ isCompiledSchema
5
+ } from "../utils/main-utils";
2
6
 
3
- import { KeywordFunction } from "../index";
7
+ import type { KeywordFunction, SchemaShield } from "../index";
4
8
  import { deepCloneUnfreeze } from "../utils/deep-freeze";
5
9
  import { compilePatternMatcher } from "../utils/pattern-matcher";
6
10
 
@@ -11,6 +15,43 @@ type PatternPropertyEntry = {
11
15
  match: (key: string) => boolean;
12
16
  };
13
17
 
18
+ interface ApplyPropertyDefaultsFunction {
19
+ (schema: Record<string, any>, data: any, instance: SchemaShield): void;
20
+ }
21
+
22
+ function createApplyPropertyDefaults(
23
+ replaceEmpty: boolean
24
+ ): ApplyPropertyDefaultsFunction {
25
+ return function applyPropertyDefaults(schema, data, instance) {
26
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
27
+ return;
28
+ }
29
+
30
+ const defaultKeys = (schema as any)._defaultKeys as string[];
31
+ for (let i = 0; i < defaultKeys.length; i++) {
32
+ const key = defaultKeys[i];
33
+ const hasOwnValue = hasOwn(data, key);
34
+ const value = hasOwnValue ? data[key] : undefined;
35
+ if (
36
+ hasOwnValue &&
37
+ value !== undefined &&
38
+ (!replaceEmpty || (value !== null && value !== ""))
39
+ ) {
40
+ continue;
41
+ }
42
+
43
+ instance.setDefault(
44
+ data,
45
+ key,
46
+ deepCloneUnfreeze(schema.properties[key].default)
47
+ );
48
+ }
49
+ };
50
+ }
51
+
52
+ export const applyPropertyDefaults = createApplyPropertyDefaults(false);
53
+ export const applyEmptyPropertyDefaults = createApplyPropertyDefaults(true);
54
+
14
55
  function getPatternPropertyEntries(schema: Record<string, any>) {
15
56
  let entries = (schema as any)._patternPropertyEntries as
16
57
  | PatternPropertyEntry[]
@@ -45,7 +86,7 @@ function getPatternPropertyEntries(schema: Record<string, any>) {
45
86
  };
46
87
  }
47
88
 
48
- Object.defineProperty(schema, "_patternPropertyEntries", {
89
+ definePropertyOrThrow(schema, "_patternPropertyEntries", {
49
90
  value: entries,
50
91
  enumerable: false,
51
92
  configurable: false,
@@ -71,7 +112,7 @@ function getPatternKeyMatchIndexes(
71
112
  }
72
113
  } else {
73
114
  cache = new Map<string, number[]>();
74
- Object.defineProperty(schema, "_patternKeyMatchIndexCache", {
115
+ definePropertyOrThrow(schema, "_patternKeyMatchIndexCache", {
75
116
  value: cache,
76
117
  enumerable: false,
77
118
  configurable: false,
@@ -102,7 +143,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
102
143
 
103
144
  for (let i = 0; i < schema.required.length; i++) {
104
145
  const key = schema.required[i];
105
- if (!Object.prototype.hasOwnProperty.call(data, key)) {
146
+ if (!hasOwn(data, key)) {
106
147
  return defineError("Required property is missing", {
107
148
  item: key,
108
149
  data: data[key]
@@ -113,61 +154,18 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
113
154
  return;
114
155
  },
115
156
 
116
- properties(schema, data, defineError) {
157
+ properties(schema, data, defineError, instance) {
117
158
  if (!data || typeof data !== "object" || Array.isArray(data)) {
118
159
  return;
119
160
  }
120
161
 
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
- }
162
+ const propKeys = (schema as any)._propKeys as string[];
147
163
 
148
164
  for (let i = 0; i < propKeys.length; i++) {
149
165
  const key = propKeys[i];
150
166
  const schemaProp = schema.properties[key];
151
167
 
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);
170
- }
168
+ if (!hasOwn(data, key)) {
171
169
  continue;
172
170
  }
173
171
 
@@ -207,7 +205,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
207
205
  }
208
206
 
209
207
  for (const key in data) {
210
- if (!Object.prototype.hasOwnProperty.call(data, key)) {
208
+ if (!hasOwn(data, key)) {
211
209
  continue;
212
210
  }
213
211
 
@@ -229,7 +227,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
229
227
 
230
228
  let count = 0;
231
229
  for (const key in data) {
232
- if (!Object.prototype.hasOwnProperty.call(data, key)) {
230
+ if (!hasOwn(data, key)) {
233
231
  continue;
234
232
  }
235
233
  count++;
@@ -248,7 +246,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
248
246
 
249
247
  let count = 0;
250
248
  for (const key in data) {
251
- if (!Object.prototype.hasOwnProperty.call(data, key)) {
249
+ if (!hasOwn(data, key)) {
252
250
  continue;
253
251
  }
254
252
  count++;
@@ -273,7 +271,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
273
271
  apValidate = isCompiledSchema(schema.additionalProperties)
274
272
  ? schema.additionalProperties.$validate
275
273
  : null;
276
- Object.defineProperty(schema, "_apValidate", {
274
+ definePropertyOrThrow(schema, "_apValidate", {
277
275
  value: apValidate,
278
276
  enumerable: false,
279
277
  configurable: false,
@@ -284,13 +282,13 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
284
282
  const patternEntries = getPatternPropertyEntries(schema);
285
283
 
286
284
  for (const key in data) {
287
- if (!Object.prototype.hasOwnProperty.call(data, key)) {
285
+ if (!hasOwn(data, key)) {
288
286
  continue;
289
287
  }
290
288
 
291
289
  if (
292
290
  schema.properties &&
293
- Object.prototype.hasOwnProperty.call(schema.properties, key)
291
+ hasOwn(schema.properties, key)
294
292
  ) {
295
293
  continue;
296
294
  }
@@ -333,7 +331,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
333
331
  }
334
332
 
335
333
  for (const key in data) {
336
- if (!Object.prototype.hasOwnProperty.call(data, key)) {
334
+ if (!hasOwn(data, key)) {
337
335
  continue;
338
336
  }
339
337
 
@@ -342,7 +340,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
342
340
  if (matchingIndexes.length === 0) {
343
341
  if (
344
342
  schema.additionalProperties === false &&
345
- !(schema.properties && Object.prototype.hasOwnProperty.call(schema.properties, key))
343
+ !(schema.properties && hasOwn(schema.properties, key))
346
344
  ) {
347
345
  return defineError("Additional properties are not allowed", {
348
346
  item: key,
@@ -392,7 +390,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
392
390
  if (typeof pn === "boolean") {
393
391
  if (pn === false) {
394
392
  for (const key in data) {
395
- if (Object.prototype.hasOwnProperty.call(data, key)) {
393
+ if (hasOwn(data, key)) {
396
394
  return defineError("Properties are not allowed", { data });
397
395
  }
398
396
  }
@@ -407,7 +405,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
407
405
  }
408
406
 
409
407
  for (const key in data) {
410
- if (!Object.prototype.hasOwnProperty.call(data, key)) {
408
+ if (!hasOwn(data, key)) {
411
409
  continue;
412
410
  }
413
411
  const error = validate(key);