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.
- package/README.md +219 -65
- package/dist/formats.d.ts.map +1 -1
- package/dist/index.d.ts +25 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1837 -484
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +1837 -484
- 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} +7 -9
- 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 +468 -155
- package/lib/index.ts +702 -107
- package/lib/keywords/array-keywords.ts +260 -52
- package/lib/keywords/number-keywords.ts +1 -1
- package/lib/keywords/object-keywords.ts +295 -88
- package/lib/keywords/other-keywords.ts +263 -70
- package/lib/keywords/string-keywords.ts +123 -7
- package/lib/types.ts +5 -18
- 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.ts → utils/main-utils.ts} +63 -77
- package/lib/utils/pattern-matcher.ts +66 -0
- package/package.json +2 -2
- package/tsconfig.json +4 -4
- package/dist/utils.d.ts.map +0 -1
|
@@ -1,17 +1,108 @@
|
|
|
1
|
-
import { isCompiledSchema
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
117
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
27
118
|
return;
|
|
28
119
|
}
|
|
29
120
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
40
|
-
if (
|
|
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
|
|
50
|
-
const error =
|
|
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 (!
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
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 (!
|
|
226
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
86
227
|
return;
|
|
87
228
|
}
|
|
88
229
|
|
|
89
|
-
|
|
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 (!
|
|
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 (!
|
|
264
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
102
265
|
return;
|
|
103
266
|
}
|
|
104
267
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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 (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (
|
|
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 (
|
|
133
|
-
const error =
|
|
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 (!
|
|
326
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
149
327
|
return;
|
|
150
328
|
}
|
|
151
329
|
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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 (!
|
|
386
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
193
387
|
return;
|
|
194
388
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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 (!
|
|
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,
|