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/index.ts
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
+
/****************** Path: lib/index.ts ******************/
|
|
1
2
|
import {
|
|
2
3
|
DefineErrorFunction,
|
|
3
4
|
ValidationError,
|
|
4
5
|
deepClone,
|
|
5
6
|
getDefinedErrorFunctionForKey,
|
|
6
7
|
getNamedFunction,
|
|
7
|
-
isObject
|
|
8
|
+
isObject,
|
|
9
|
+
resolvePath
|
|
8
10
|
} from "./utils";
|
|
9
11
|
|
|
10
12
|
import { Formats } from "./formats";
|
|
11
13
|
import { Types } from "./types";
|
|
12
14
|
import { keywords } from "./keywords";
|
|
13
15
|
|
|
14
|
-
export
|
|
16
|
+
export { ValidationError } from "./utils";
|
|
17
|
+
export { deepClone } from "./utils";
|
|
18
|
+
|
|
19
|
+
export type Result = void | ValidationError | true;
|
|
15
20
|
|
|
16
21
|
export interface KeywordFunction {
|
|
17
22
|
(
|
|
@@ -40,22 +45,37 @@ export interface CompiledSchema {
|
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
export interface Validator {
|
|
43
|
-
(data: any): {
|
|
48
|
+
(data: any): {
|
|
49
|
+
data: any;
|
|
50
|
+
error: ValidationError | null | true;
|
|
51
|
+
valid: boolean;
|
|
52
|
+
};
|
|
44
53
|
compiledSchema: CompiledSchema;
|
|
45
54
|
}
|
|
46
55
|
|
|
56
|
+
interface ValidatorItem {
|
|
57
|
+
fn: KeywordFunction;
|
|
58
|
+
defineError: DefineErrorFunction;
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
export class SchemaShield {
|
|
48
62
|
private types: Record<string, TypeFunction | false> = {};
|
|
49
63
|
private formats: Record<string, FormatFunction | false> = {};
|
|
50
64
|
private keywords: Record<string, KeywordFunction | false> = {};
|
|
51
65
|
private immutable = false;
|
|
66
|
+
private rootSchema: CompiledSchema | null = null;
|
|
67
|
+
private idRegistry: Map<string, CompiledSchema> = new Map();
|
|
68
|
+
private failFast: boolean = true;
|
|
52
69
|
|
|
53
70
|
constructor({
|
|
54
|
-
immutable = false
|
|
71
|
+
immutable = false,
|
|
72
|
+
failFast = true
|
|
55
73
|
}: {
|
|
56
74
|
immutable?: boolean;
|
|
75
|
+
failFast?: boolean;
|
|
57
76
|
} = {}) {
|
|
58
77
|
this.immutable = immutable;
|
|
78
|
+
this.failFast = failFast;
|
|
59
79
|
|
|
60
80
|
for (const [type, validator] of Object.entries(Types)) {
|
|
61
81
|
if (validator) {
|
|
@@ -107,146 +127,189 @@ export class SchemaShield {
|
|
|
107
127
|
return this.keywords[keyword];
|
|
108
128
|
}
|
|
109
129
|
|
|
130
|
+
getSchemaRef(path: string): CompiledSchema | undefined {
|
|
131
|
+
if (!this.rootSchema) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
return resolvePath(this.rootSchema, path);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
getSchemaById(id: string): CompiledSchema | undefined {
|
|
138
|
+
return this.idRegistry.get(id);
|
|
139
|
+
}
|
|
140
|
+
|
|
110
141
|
compile(schema: any): Validator {
|
|
142
|
+
this.idRegistry.clear();
|
|
111
143
|
const compiledSchema = this.compileSchema(schema);
|
|
144
|
+
this.rootSchema = compiledSchema;
|
|
145
|
+
this.linkReferences(compiledSchema);
|
|
146
|
+
|
|
112
147
|
if (!compiledSchema.$validate) {
|
|
113
148
|
if (this.isSchemaLike(schema) === false) {
|
|
114
149
|
throw new ValidationError("Invalid schema");
|
|
115
150
|
}
|
|
116
151
|
|
|
117
152
|
compiledSchema.$validate = getNamedFunction<ValidateFunction>(
|
|
118
|
-
"
|
|
153
|
+
"Validate_Any",
|
|
119
154
|
() => {}
|
|
120
155
|
);
|
|
121
156
|
}
|
|
122
157
|
|
|
123
158
|
const validate: Validator = (data: any) => {
|
|
159
|
+
this.rootSchema = compiledSchema;
|
|
160
|
+
|
|
124
161
|
const clonedData = this.immutable ? deepClone(data) : data;
|
|
125
|
-
const
|
|
162
|
+
const res = compiledSchema.$validate!(clonedData);
|
|
126
163
|
|
|
127
|
-
|
|
128
|
-
data: clonedData,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
};
|
|
164
|
+
if (res) {
|
|
165
|
+
return { data: clonedData, error: res, valid: false };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { data: clonedData, error: null, valid: true };
|
|
132
169
|
};
|
|
133
170
|
|
|
134
171
|
validate.compiledSchema = compiledSchema;
|
|
135
|
-
|
|
136
172
|
return validate;
|
|
137
173
|
}
|
|
138
174
|
|
|
139
175
|
private compileSchema(schema: Partial<CompiledSchema> | any): CompiledSchema {
|
|
140
176
|
if (!isObject(schema)) {
|
|
141
177
|
if (schema === true) {
|
|
142
|
-
schema = {
|
|
143
|
-
anyOf: [{}]
|
|
144
|
-
};
|
|
178
|
+
schema = { anyOf: [{}] }; // Always valid
|
|
145
179
|
} else if (schema === false) {
|
|
146
|
-
schema = {
|
|
147
|
-
oneOf: []
|
|
148
|
-
};
|
|
180
|
+
schema = { oneOf: [] }; // Always invalid
|
|
149
181
|
} else {
|
|
150
|
-
schema = {
|
|
151
|
-
oneOf: [schema]
|
|
152
|
-
};
|
|
182
|
+
schema = { oneOf: [schema] };
|
|
153
183
|
}
|
|
154
184
|
}
|
|
155
185
|
|
|
156
186
|
const compiledSchema: CompiledSchema = deepClone(schema) as CompiledSchema;
|
|
157
|
-
const defineTypeError = getDefinedErrorFunctionForKey("type", schema);
|
|
158
|
-
const typeValidations: TypeFunction[] = [];
|
|
159
187
|
|
|
160
|
-
|
|
188
|
+
if (typeof schema.$id === "string") {
|
|
189
|
+
this.idRegistry.set(schema.$id, compiledSchema);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if ("$ref" in schema) {
|
|
193
|
+
const refValidator = this.getKeyword("$ref");
|
|
194
|
+
if (refValidator) {
|
|
195
|
+
const defineError = getDefinedErrorFunctionForKey(
|
|
196
|
+
"$ref",
|
|
197
|
+
schema["$ref"],
|
|
198
|
+
this.failFast
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
compiledSchema.$validate = getNamedFunction<ValidateFunction>(
|
|
202
|
+
"Validate_Reference",
|
|
203
|
+
(data) =>
|
|
204
|
+
(refValidator as KeywordFunction)(
|
|
205
|
+
compiledSchema,
|
|
206
|
+
data,
|
|
207
|
+
defineError,
|
|
208
|
+
this
|
|
209
|
+
)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return compiledSchema;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const validators: ValidatorItem[] = [];
|
|
216
|
+
const activeNames: string[] = [];
|
|
161
217
|
|
|
162
218
|
if ("type" in schema) {
|
|
219
|
+
const defineTypeError = getDefinedErrorFunctionForKey(
|
|
220
|
+
"type",
|
|
221
|
+
schema,
|
|
222
|
+
this.failFast
|
|
223
|
+
);
|
|
163
224
|
const types = Array.isArray(schema.type)
|
|
164
225
|
? schema.type
|
|
165
|
-
: schema.type.split(",").map((t) => t.trim());
|
|
226
|
+
: schema.type.split(",").map((t: string) => t.trim());
|
|
227
|
+
|
|
228
|
+
const typeFunctions: TypeFunction[] = [];
|
|
229
|
+
const typeNames: string[] = [];
|
|
166
230
|
|
|
167
231
|
for (const type of types) {
|
|
168
232
|
const validator = this.getType(type);
|
|
169
233
|
if (validator) {
|
|
170
|
-
|
|
171
|
-
|
|
234
|
+
typeFunctions.push(validator);
|
|
235
|
+
typeNames.push(validator.name);
|
|
172
236
|
}
|
|
173
237
|
}
|
|
174
238
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
239
|
+
if (typeFunctions.length === 0) {
|
|
240
|
+
throw getDefinedErrorFunctionForKey(
|
|
241
|
+
"type",
|
|
242
|
+
schema,
|
|
243
|
+
this.failFast
|
|
244
|
+
)("Invalid type for schema", { data: schema.type });
|
|
179
245
|
}
|
|
180
246
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
247
|
+
let combinedTypeValidator: ValidateFunction;
|
|
248
|
+
let typeMethodName = "";
|
|
249
|
+
|
|
250
|
+
if (typeFunctions.length === 1) {
|
|
251
|
+
typeMethodName = typeNames[0];
|
|
252
|
+
const singleTypeFn = typeFunctions[0];
|
|
253
|
+
combinedTypeValidator = (data) => {
|
|
254
|
+
if (!singleTypeFn(data)) {
|
|
255
|
+
return defineTypeError("Invalid type", { data });
|
|
189
256
|
}
|
|
190
|
-
|
|
191
|
-
} else
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
257
|
+
};
|
|
258
|
+
} else {
|
|
259
|
+
typeMethodName = typeNames.join("_OR_");
|
|
260
|
+
combinedTypeValidator = (data) => {
|
|
261
|
+
for (let i = 0; i < typeFunctions.length; i++) {
|
|
262
|
+
if (typeFunctions[i](data)) {
|
|
263
|
+
return;
|
|
199
264
|
}
|
|
200
|
-
return defineTypeError("Invalid type", { data });
|
|
201
265
|
}
|
|
202
|
-
|
|
266
|
+
return defineTypeError("Invalid type", { data });
|
|
267
|
+
};
|
|
203
268
|
}
|
|
269
|
+
|
|
270
|
+
const typeAdapter: KeywordFunction = (_s, data) =>
|
|
271
|
+
combinedTypeValidator(data);
|
|
272
|
+
|
|
273
|
+
validators.push({
|
|
274
|
+
fn: getNamedFunction(typeMethodName, typeAdapter),
|
|
275
|
+
defineError: defineTypeError
|
|
276
|
+
});
|
|
277
|
+
activeNames.push(typeMethodName);
|
|
204
278
|
}
|
|
205
279
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
280
|
+
const { type, $id, $ref, $validate, required, ...otherKeys } = schema; // Exclude handled keys
|
|
281
|
+
|
|
282
|
+
// In here we create an array of keys putting the require keyword last
|
|
283
|
+
// This is to ensure required properties are checked after defaults are applied
|
|
284
|
+
const keyOrder = required
|
|
285
|
+
? [...Object.keys(otherKeys), "required"]
|
|
286
|
+
: Object.keys(otherKeys);
|
|
287
|
+
for (const key of keyOrder) {
|
|
288
|
+
const keywordFn = this.getKeyword(key);
|
|
289
|
+
|
|
290
|
+
if (keywordFn) {
|
|
291
|
+
const defineError = getDefinedErrorFunctionForKey(
|
|
292
|
+
key,
|
|
293
|
+
schema[key],
|
|
294
|
+
this.failFast
|
|
295
|
+
);
|
|
296
|
+
const fnName = keywordFn.name || key;
|
|
211
297
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
compiledSchema.$validate = getNamedFunction<ValidateFunction>(
|
|
219
|
-
methodName,
|
|
220
|
-
(data) => {
|
|
221
|
-
const error = prevValidator(data);
|
|
222
|
-
if (error) {
|
|
223
|
-
return error;
|
|
224
|
-
}
|
|
225
|
-
return (keywordValidator as KeywordFunction)(
|
|
226
|
-
compiledSchema,
|
|
227
|
-
data,
|
|
228
|
-
defineError,
|
|
229
|
-
this
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
);
|
|
233
|
-
} else {
|
|
234
|
-
methodName = keywordValidator.name;
|
|
235
|
-
compiledSchema.$validate = getNamedFunction<ValidateFunction>(
|
|
236
|
-
methodName,
|
|
237
|
-
(data) =>
|
|
238
|
-
(keywordValidator as KeywordFunction)(
|
|
239
|
-
compiledSchema,
|
|
240
|
-
data,
|
|
241
|
-
defineError,
|
|
242
|
-
this
|
|
243
|
-
)
|
|
244
|
-
);
|
|
245
|
-
}
|
|
298
|
+
validators.push({
|
|
299
|
+
fn: keywordFn as KeywordFunction,
|
|
300
|
+
defineError
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
activeNames.push(fnName);
|
|
246
304
|
}
|
|
305
|
+
}
|
|
247
306
|
|
|
307
|
+
const literalKeywords = ["enum", "const", "default", "examples"];
|
|
308
|
+
for (const key of keyOrder) {
|
|
309
|
+
if (literalKeywords.includes(key)) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
248
312
|
if (isObject(schema[key])) {
|
|
249
|
-
// If the key is properties go through each property and try to compile it as a schema
|
|
250
313
|
if (key === "properties") {
|
|
251
314
|
for (const subKey of Object.keys(schema[key])) {
|
|
252
315
|
compiledSchema[key][subKey] = this.compileSchema(
|
|
@@ -260,15 +323,42 @@ export class SchemaShield {
|
|
|
260
323
|
}
|
|
261
324
|
|
|
262
325
|
if (Array.isArray(schema[key])) {
|
|
263
|
-
|
|
264
|
-
this.isSchemaLike(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
326
|
+
for (let i = 0; i < schema[key].length; i++) {
|
|
327
|
+
if (this.isSchemaLike(schema[key][i])) {
|
|
328
|
+
compiledSchema[key][i] = this.compileSchema(schema[key][i]);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
268
331
|
continue;
|
|
269
332
|
}
|
|
333
|
+
}
|
|
270
334
|
|
|
271
|
-
|
|
335
|
+
if (validators.length === 0) {
|
|
336
|
+
return compiledSchema;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (validators.length === 1) {
|
|
340
|
+
const v = validators[0];
|
|
341
|
+
compiledSchema.$validate = getNamedFunction(activeNames[0], (data) =>
|
|
342
|
+
v.fn(compiledSchema, data, v.defineError, this)
|
|
343
|
+
);
|
|
344
|
+
} else {
|
|
345
|
+
const compositeName = "Validate_" + activeNames.join("_AND_");
|
|
346
|
+
|
|
347
|
+
const masterValidator: ValidateFunction = (data) => {
|
|
348
|
+
for (let i = 0; i < validators.length; i++) {
|
|
349
|
+
const v = validators[i];
|
|
350
|
+
const error = v.fn(compiledSchema, data, v.defineError, this);
|
|
351
|
+
if (error) {
|
|
352
|
+
return error;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
compiledSchema.$validate = getNamedFunction(
|
|
359
|
+
compositeName,
|
|
360
|
+
masterValidator
|
|
361
|
+
);
|
|
272
362
|
}
|
|
273
363
|
|
|
274
364
|
return compiledSchema as CompiledSchema;
|
|
@@ -288,4 +378,65 @@ export class SchemaShield {
|
|
|
288
378
|
}
|
|
289
379
|
return false;
|
|
290
380
|
}
|
|
381
|
+
|
|
382
|
+
private linkReferences(root: CompiledSchema) {
|
|
383
|
+
const stack: any[] = [root];
|
|
384
|
+
|
|
385
|
+
while (stack.length > 0) {
|
|
386
|
+
const node = stack.pop();
|
|
387
|
+
|
|
388
|
+
if (!node || typeof node !== "object") continue;
|
|
389
|
+
|
|
390
|
+
if (
|
|
391
|
+
typeof node.$ref === "string" &&
|
|
392
|
+
typeof node.$validate === "function" &&
|
|
393
|
+
node.$validate.name === "Validate_Reference"
|
|
394
|
+
) {
|
|
395
|
+
const refPath = node.$ref as string;
|
|
396
|
+
|
|
397
|
+
let target: any = this.getSchemaRef(refPath);
|
|
398
|
+
if (typeof target === "undefined") {
|
|
399
|
+
target = this.getSchemaById(refPath);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (typeof target === "boolean") {
|
|
403
|
+
if (target === true) {
|
|
404
|
+
node.$validate = getNamedFunction("Validate_Ref_True", () => {});
|
|
405
|
+
} else {
|
|
406
|
+
const defineError = getDefinedErrorFunctionForKey(
|
|
407
|
+
"$ref",
|
|
408
|
+
node as any,
|
|
409
|
+
this.failFast
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
node.$validate = getNamedFunction(
|
|
413
|
+
"Validate_Ref_False",
|
|
414
|
+
(_data: any) => defineError("Value is not valid")
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (target && typeof target.$validate === "function") {
|
|
421
|
+
node.$validate = target.$validate;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
for (const key in node) {
|
|
426
|
+
const value = node[key];
|
|
427
|
+
if (!value) continue;
|
|
428
|
+
|
|
429
|
+
if (Array.isArray(value)) {
|
|
430
|
+
for (let i = 0; i < value.length; i++) {
|
|
431
|
+
const v = value[i];
|
|
432
|
+
if (v && typeof v === "object") {
|
|
433
|
+
stack.push(v);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
} else if (typeof value === "object") {
|
|
437
|
+
stack.push(value);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
291
442
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { isCompiledSchema, isObject } from "../utils";
|
|
1
|
+
import { hasChanged, isCompiledSchema, isObject } from "../utils";
|
|
2
2
|
|
|
3
3
|
import { KeywordFunction } from "../index";
|
|
4
4
|
|
|
5
5
|
export const ArrayKeywords: Record<string, KeywordFunction> = {
|
|
6
|
+
// lib/keywords/array-keywords.ts
|
|
6
7
|
items(schema, data, defineError) {
|
|
7
8
|
if (!Array.isArray(data)) {
|
|
8
9
|
return;
|
|
@@ -15,17 +16,19 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
|
|
|
15
16
|
if (schemaItems === false && dataLength > 0) {
|
|
16
17
|
return defineError("Array items are not allowed", { data });
|
|
17
18
|
}
|
|
18
|
-
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
if (Array.isArray(schemaItems)) {
|
|
23
23
|
const schemaItemsLength = schemaItems.length;
|
|
24
|
-
const itemsLength =
|
|
24
|
+
const itemsLength =
|
|
25
|
+
schemaItemsLength < dataLength ? schemaItemsLength : dataLength;
|
|
26
|
+
|
|
25
27
|
for (let i = 0; i < itemsLength; i++) {
|
|
26
28
|
const schemaItem = schemaItems[i];
|
|
29
|
+
|
|
27
30
|
if (typeof schemaItem === "boolean") {
|
|
28
|
-
if (schemaItem === false &&
|
|
31
|
+
if (schemaItem === false && data[i] !== undefined) {
|
|
29
32
|
return defineError("Array item is not allowed", {
|
|
30
33
|
item: i,
|
|
31
34
|
data: data[i]
|
|
@@ -34,8 +37,9 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
|
|
|
34
37
|
continue;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
const validate = schemaItem && schemaItem.$validate;
|
|
41
|
+
if (typeof validate === "function") {
|
|
42
|
+
const error = validate(data[i]);
|
|
39
43
|
if (error) {
|
|
40
44
|
return defineError("Array item is invalid", {
|
|
41
45
|
item: i,
|
|
@@ -49,29 +53,36 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
|
|
|
49
53
|
return;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (error) {
|
|
56
|
-
return defineError("Array item is invalid", {
|
|
57
|
-
item: i,
|
|
58
|
-
cause: error,
|
|
59
|
-
data: data[i]
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
56
|
+
const validate = schemaItems && schemaItems.$validate;
|
|
57
|
+
if (typeof validate !== "function") {
|
|
58
|
+
return;
|
|
63
59
|
}
|
|
64
60
|
|
|
65
|
-
|
|
61
|
+
for (let i = 0; i < dataLength; i++) {
|
|
62
|
+
const error = validate(data[i]);
|
|
63
|
+
if (error) {
|
|
64
|
+
return defineError("Array item is invalid", {
|
|
65
|
+
item: i,
|
|
66
|
+
cause: error,
|
|
67
|
+
data: data[i]
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
66
71
|
},
|
|
67
72
|
|
|
68
73
|
elements(schema, data, defineError) {
|
|
69
|
-
if (!Array.isArray(data)
|
|
74
|
+
if (!Array.isArray(data)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const elementsSchema = schema.elements;
|
|
79
|
+
const validate = elementsSchema && elementsSchema.$validate;
|
|
80
|
+
if (typeof validate !== "function") {
|
|
70
81
|
return;
|
|
71
82
|
}
|
|
72
83
|
|
|
73
84
|
for (let i = 0; i < data.length; i++) {
|
|
74
|
-
const error =
|
|
85
|
+
const error = validate(data[i]);
|
|
75
86
|
if (error) {
|
|
76
87
|
return defineError("Array item is invalid", {
|
|
77
88
|
item: i,
|
|
@@ -80,8 +91,6 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
|
|
|
80
91
|
});
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
|
-
|
|
84
|
-
return;
|
|
85
94
|
},
|
|
86
95
|
|
|
87
96
|
minItems(schema, data, defineError) {
|
|
@@ -138,34 +147,39 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
|
|
|
138
147
|
return;
|
|
139
148
|
}
|
|
140
149
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
150
|
+
const len = data.length;
|
|
151
|
+
if (len <= 1) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const primitiveSeen = new Set<any>();
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < len; i++) {
|
|
158
|
+
const item = data[i];
|
|
159
|
+
const type = typeof item;
|
|
160
|
+
|
|
161
|
+
if (
|
|
162
|
+
item === null ||
|
|
163
|
+
type === "string" ||
|
|
164
|
+
type === "number" ||
|
|
165
|
+
type === "boolean"
|
|
166
|
+
) {
|
|
167
|
+
if (primitiveSeen.has(item)) {
|
|
168
|
+
return defineError("Array items are not unique", { data: item });
|
|
169
|
+
}
|
|
170
|
+
primitiveSeen.add(item);
|
|
171
|
+
continue;
|
|
160
172
|
}
|
|
161
173
|
|
|
162
|
-
if (
|
|
163
|
-
|
|
174
|
+
if (item && typeof item === "object") {
|
|
175
|
+
for (let j = 0; j < i; j++) {
|
|
176
|
+
const prev = data[j];
|
|
177
|
+
if (prev && typeof prev === "object" && !hasChanged(prev, item)) {
|
|
178
|
+
return defineError("Array items are not unique", { data: item });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
164
181
|
}
|
|
165
|
-
unique.add(itemStr);
|
|
166
182
|
}
|
|
167
|
-
|
|
168
|
-
return;
|
|
169
183
|
},
|
|
170
184
|
|
|
171
185
|
contains(schema, data, defineError) {
|