schema-shield 0.0.5 → 1.0.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.
- package/README.md +194 -66
- package/dist/formats.d.ts.map +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +705 -348
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +705 -348
- 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.d.ts +6 -5
- package/dist/utils.d.ts.map +1 -1
- package/lib/formats.ts +125 -130
- package/lib/index.ts +247 -96
- package/lib/keywords/array-keywords.ts +60 -46
- package/lib/keywords/object-keywords.ts +155 -53
- package/lib/keywords/other-keywords.ts +68 -28
- package/lib/keywords/string-keywords.ts +32 -6
- package/lib/types.ts +1 -13
- package/lib/utils.ts +202 -44
- package/package.json +2 -2
- package/tsconfig.json +4 -4
package/lib/utils.ts
CHANGED
|
@@ -78,18 +78,27 @@ export class ValidationError extends Error {
|
|
|
78
78
|
|
|
79
79
|
export interface DefineErrorOptions {
|
|
80
80
|
item?: any; // Final item in the schemaPath
|
|
81
|
-
cause?: ValidationError; // Cause of the error
|
|
81
|
+
cause?: ValidationError | true; // Cause of the error
|
|
82
82
|
data?: any; // Data that caused the error
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export interface DefineErrorFunction {
|
|
86
|
-
(message: string, options?: DefineErrorOptions):
|
|
86
|
+
(message: string, options?: DefineErrorOptions):
|
|
87
|
+
| ValidationError
|
|
88
|
+
| void
|
|
89
|
+
| true;
|
|
87
90
|
}
|
|
91
|
+
const FAIL_FAST_DEFINE_ERROR: DefineErrorFunction = () => true;
|
|
88
92
|
|
|
89
93
|
export function getDefinedErrorFunctionForKey(
|
|
90
94
|
key: string,
|
|
91
|
-
schema: CompiledSchema
|
|
95
|
+
schema: CompiledSchema,
|
|
96
|
+
failFast: boolean
|
|
92
97
|
) {
|
|
98
|
+
if (failFast) {
|
|
99
|
+
return FAIL_FAST_DEFINE_ERROR;
|
|
100
|
+
}
|
|
101
|
+
|
|
93
102
|
const KeywordError = new ValidationError(`Invalid ${key}`);
|
|
94
103
|
KeywordError.keyword = key;
|
|
95
104
|
KeywordError.schema = schema;
|
|
@@ -97,7 +106,8 @@ export function getDefinedErrorFunctionForKey(
|
|
|
97
106
|
const defineError: DefineErrorFunction = (message, options = {}) => {
|
|
98
107
|
KeywordError.message = message;
|
|
99
108
|
KeywordError.item = options.item;
|
|
100
|
-
KeywordError.cause =
|
|
109
|
+
KeywordError.cause =
|
|
110
|
+
options.cause && options.cause !== true ? options.cause : undefined;
|
|
101
111
|
KeywordError.data = options.data;
|
|
102
112
|
return KeywordError;
|
|
103
113
|
};
|
|
@@ -108,44 +118,46 @@ export function getDefinedErrorFunctionForKey(
|
|
|
108
118
|
);
|
|
109
119
|
}
|
|
110
120
|
|
|
111
|
-
export function
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (Array.isArray(obj) && Array.isArray(other)) {
|
|
116
|
-
if (obj.length !== other.length) {
|
|
117
|
-
return false;
|
|
121
|
+
export function hasChanged(prev: any, current: any) {
|
|
122
|
+
if (Array.isArray(prev)) {
|
|
123
|
+
if (Array.isArray(current) === false) {
|
|
124
|
+
return true;
|
|
118
125
|
}
|
|
119
126
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
127
|
+
if (prev.length !== current.length) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < current.length; i++) {
|
|
132
|
+
if (hasChanged(prev[i], current[i])) {
|
|
133
|
+
return true;
|
|
123
134
|
}
|
|
124
135
|
}
|
|
125
136
|
|
|
126
|
-
return
|
|
137
|
+
return false;
|
|
127
138
|
}
|
|
128
139
|
|
|
129
|
-
if (typeof
|
|
130
|
-
if (
|
|
131
|
-
return
|
|
140
|
+
if (typeof prev === "object" && prev !== null) {
|
|
141
|
+
if (typeof current !== "object" || current === null) {
|
|
142
|
+
return true;
|
|
132
143
|
}
|
|
133
144
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
145
|
+
for (const key in current) {
|
|
146
|
+
if (hasChanged(prev[key], current[key])) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
137
149
|
}
|
|
138
150
|
|
|
139
|
-
for (const key
|
|
140
|
-
if (
|
|
141
|
-
return
|
|
151
|
+
for (const key in prev) {
|
|
152
|
+
if (hasChanged(prev[key], current[key])) {
|
|
153
|
+
return true;
|
|
142
154
|
}
|
|
143
155
|
}
|
|
144
156
|
|
|
145
|
-
return
|
|
157
|
+
return false;
|
|
146
158
|
}
|
|
147
159
|
|
|
148
|
-
return
|
|
160
|
+
return Object.is(prev, current) === false;
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
export function isObject(data) {
|
|
@@ -168,31 +180,135 @@ export function getUTF16Length(str) {
|
|
|
168
180
|
return length;
|
|
169
181
|
}
|
|
170
182
|
|
|
171
|
-
export function deepClone(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return
|
|
183
|
+
export function deepClone<T>(
|
|
184
|
+
obj: T,
|
|
185
|
+
cloneClassInstances = false,
|
|
186
|
+
seen = new WeakMap()
|
|
187
|
+
): T {
|
|
188
|
+
if (typeof obj === "undefined" || obj === null || typeof obj !== "object") {
|
|
189
|
+
return obj;
|
|
178
190
|
}
|
|
179
191
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return obj;
|
|
192
|
+
if (seen.has(obj)) {
|
|
193
|
+
return seen.get(obj);
|
|
183
194
|
}
|
|
184
195
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
196
|
+
let clone: any;
|
|
197
|
+
|
|
198
|
+
if (typeof structuredClone === "function") {
|
|
199
|
+
clone = structuredClone(obj);
|
|
200
|
+
seen.set(obj, clone);
|
|
201
|
+
return clone;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
switch (true) {
|
|
205
|
+
case Array.isArray(obj): {
|
|
206
|
+
clone = [];
|
|
207
|
+
seen.set(obj, clone);
|
|
208
|
+
for (let i = 0, l = obj.length; i < l; i++) {
|
|
209
|
+
clone[i] = deepClone(obj[i], cloneClassInstances, seen);
|
|
210
|
+
}
|
|
211
|
+
return clone;
|
|
212
|
+
}
|
|
213
|
+
case obj instanceof Date: {
|
|
214
|
+
clone = new Date(obj.getTime());
|
|
215
|
+
seen.set(obj, clone);
|
|
216
|
+
return clone;
|
|
217
|
+
}
|
|
218
|
+
case obj instanceof RegExp: {
|
|
219
|
+
clone = new RegExp(obj.source, obj.flags);
|
|
220
|
+
seen.set(obj, clone);
|
|
221
|
+
return clone;
|
|
222
|
+
}
|
|
223
|
+
case obj instanceof Map: {
|
|
224
|
+
clone = new Map();
|
|
225
|
+
seen.set(obj, clone);
|
|
226
|
+
for (const [key, value] of obj.entries()) {
|
|
227
|
+
clone.set(
|
|
228
|
+
deepClone(key, cloneClassInstances, seen),
|
|
229
|
+
deepClone(value, cloneClassInstances, seen)
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
return clone;
|
|
233
|
+
}
|
|
234
|
+
case obj instanceof Set: {
|
|
235
|
+
clone = new Set();
|
|
236
|
+
seen.set(obj, clone);
|
|
237
|
+
for (const value of obj.values()) {
|
|
238
|
+
clone.add(deepClone(value, cloneClassInstances, seen));
|
|
239
|
+
}
|
|
240
|
+
return clone;
|
|
241
|
+
}
|
|
242
|
+
case obj instanceof ArrayBuffer: {
|
|
243
|
+
clone = obj.slice(0);
|
|
244
|
+
seen.set(obj, clone);
|
|
245
|
+
return clone;
|
|
246
|
+
}
|
|
247
|
+
// TypedArrays and DataView
|
|
248
|
+
case ArrayBuffer.isView(obj): {
|
|
249
|
+
clone = new (obj as any).constructor(obj.buffer.slice(0));
|
|
250
|
+
seen.set(obj, clone);
|
|
251
|
+
return clone;
|
|
252
|
+
}
|
|
253
|
+
// Node.js Buffer
|
|
254
|
+
case typeof Buffer !== "undefined" && obj instanceof Buffer: {
|
|
255
|
+
clone = Buffer.from(obj as any);
|
|
256
|
+
seen.set(obj, clone);
|
|
257
|
+
return clone;
|
|
258
|
+
}
|
|
259
|
+
case obj instanceof Error: {
|
|
260
|
+
clone = new (obj as any).constructor(obj.message);
|
|
261
|
+
seen.set(obj, clone);
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
// Non clonable objects
|
|
265
|
+
case obj instanceof Promise ||
|
|
266
|
+
obj instanceof WeakMap ||
|
|
267
|
+
obj instanceof WeakSet: {
|
|
268
|
+
clone = obj;
|
|
269
|
+
seen.set(obj, clone);
|
|
270
|
+
return clone;
|
|
271
|
+
}
|
|
272
|
+
// Instance of a class
|
|
273
|
+
case obj.constructor && obj.constructor !== Object: {
|
|
274
|
+
if (!cloneClassInstances) {
|
|
275
|
+
clone = obj;
|
|
276
|
+
seen.set(obj, clone);
|
|
277
|
+
return clone;
|
|
278
|
+
}
|
|
279
|
+
clone = Object.create(Object.getPrototypeOf(obj));
|
|
280
|
+
seen.set(obj, clone);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Plain objects
|
|
285
|
+
default: {
|
|
286
|
+
clone = {};
|
|
287
|
+
seen.set(obj, clone);
|
|
288
|
+
|
|
289
|
+
const keys = Reflect.ownKeys(obj);
|
|
290
|
+
for (let i = 0, l = keys.length; i < l; i++) {
|
|
291
|
+
const key = keys[i];
|
|
292
|
+
clone[key as string] = deepClone(
|
|
293
|
+
(obj as any)[key as string],
|
|
294
|
+
cloneClassInstances,
|
|
295
|
+
seen
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return clone;
|
|
191
299
|
}
|
|
192
|
-
return result;
|
|
193
300
|
}
|
|
194
301
|
|
|
195
|
-
|
|
302
|
+
const descriptors = Object.getOwnPropertyDescriptors(obj);
|
|
303
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
304
|
+
const descriptor = descriptors[key as string];
|
|
305
|
+
if ("value" in descriptor) {
|
|
306
|
+
descriptor.value = deepClone(descriptor.value, cloneClassInstances, seen);
|
|
307
|
+
}
|
|
308
|
+
Object.defineProperty(clone, key, descriptor);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return clone;
|
|
196
312
|
}
|
|
197
313
|
|
|
198
314
|
export function isCompiledSchema(subSchema: any): subSchema is CompiledSchema {
|
|
@@ -202,3 +318,45 @@ export function isCompiledSchema(subSchema: any): subSchema is CompiledSchema {
|
|
|
202
318
|
export function getNamedFunction<T>(name: string, fn: T): T {
|
|
203
319
|
return Object.defineProperty(fn, "name", { value: name });
|
|
204
320
|
}
|
|
321
|
+
|
|
322
|
+
export function resolvePath(root: any, path: string): any {
|
|
323
|
+
if (!path || path === "#") {
|
|
324
|
+
return root;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// JSON Pointer
|
|
328
|
+
if (path.startsWith("#/")) {
|
|
329
|
+
const parts = path.split("/").slice(1);
|
|
330
|
+
let current = root;
|
|
331
|
+
|
|
332
|
+
for (const part of parts) {
|
|
333
|
+
const decodedUriPart = decodeURIComponent(part);
|
|
334
|
+
const key = decodedUriPart.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
335
|
+
|
|
336
|
+
if (current && typeof current === "object" && key in current) {
|
|
337
|
+
current = current[key];
|
|
338
|
+
} else {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return current;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Simple lookup by definition name (non-standard, but useful)
|
|
346
|
+
if (!path.includes("#")) {
|
|
347
|
+
if (root.definitions && root.definitions[path]) {
|
|
348
|
+
return root.definitions[path];
|
|
349
|
+
}
|
|
350
|
+
if (root.defs && root.defs[path]) {
|
|
351
|
+
return root.defs[path];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (root.$id && typeof root.$id === "string") {
|
|
355
|
+
if (root.$id === path || root.$id.endsWith("/" + path)) {
|
|
356
|
+
return root;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return;
|
|
362
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "schema-shield",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "A fast library that protects your JSON schema from invalid data.",
|
|
5
5
|
"repository": "git@github.com:Masquerade-Circus/schema-shield.git",
|
|
6
6
|
"author": "Masquerade <christian@masquerade-circus.net>",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"dev:source": "NODE_ENV=development nodemon --enable-source-maps -e tsx,ts,json,css -w ./lib -w ./www source.js",
|
|
75
75
|
"build": "node source.js",
|
|
76
76
|
"coverage": "nyc report --reporter=lcov",
|
|
77
|
-
"commit": "git add . && git-cz",
|
|
77
|
+
"commit": "git add . && git-cz && git push",
|
|
78
78
|
"release": "release-it --verbose",
|
|
79
79
|
"release-test": "release-it --dry-run --verbose"
|
|
80
80
|
},
|
package/tsconfig.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"jsx": "preserve",
|
|
4
|
-
"target": "
|
|
5
|
-
"moduleResolution": "nodenext",
|
|
6
|
-
"resolveJsonModule": true,
|
|
4
|
+
"target": "esnext",
|
|
7
5
|
"allowSyntheticDefaultImports": true,
|
|
8
6
|
"allowJs": true,
|
|
9
|
-
"esModuleInterop": true
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"noEmit": false,
|
|
9
|
+
"emitDeclarationOnly": false
|
|
10
10
|
},
|
|
11
11
|
"include": ["lib/**/*"],
|
|
12
12
|
"exclude": ["**/*.spec.ts", "node_modules/**/*", "docs/**/*"],
|