schema-shield 0.0.6 → 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 +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +702 -347
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +702 -347
- 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 +244 -97
- 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,10 +1,12 @@
|
|
|
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";
|
|
@@ -12,10 +14,9 @@ import { Types } from "./types";
|
|
|
12
14
|
import { keywords } from "./keywords";
|
|
13
15
|
|
|
14
16
|
export { ValidationError } from "./utils";
|
|
15
|
-
|
|
16
17
|
export { deepClone } from "./utils";
|
|
17
18
|
|
|
18
|
-
export type Result = void | ValidationError;
|
|
19
|
+
export type Result = void | ValidationError | true;
|
|
19
20
|
|
|
20
21
|
export interface KeywordFunction {
|
|
21
22
|
(
|
|
@@ -44,22 +45,37 @@ export interface CompiledSchema {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export interface Validator {
|
|
47
|
-
(data: any): {
|
|
48
|
+
(data: any): {
|
|
49
|
+
data: any;
|
|
50
|
+
error: ValidationError | null | true;
|
|
51
|
+
valid: boolean;
|
|
52
|
+
};
|
|
48
53
|
compiledSchema: CompiledSchema;
|
|
49
54
|
}
|
|
50
55
|
|
|
56
|
+
interface ValidatorItem {
|
|
57
|
+
fn: KeywordFunction;
|
|
58
|
+
defineError: DefineErrorFunction;
|
|
59
|
+
}
|
|
60
|
+
|
|
51
61
|
export class SchemaShield {
|
|
52
62
|
private types: Record<string, TypeFunction | false> = {};
|
|
53
63
|
private formats: Record<string, FormatFunction | false> = {};
|
|
54
64
|
private keywords: Record<string, KeywordFunction | false> = {};
|
|
55
65
|
private immutable = false;
|
|
66
|
+
private rootSchema: CompiledSchema | null = null;
|
|
67
|
+
private idRegistry: Map<string, CompiledSchema> = new Map();
|
|
68
|
+
private failFast: boolean = true;
|
|
56
69
|
|
|
57
70
|
constructor({
|
|
58
|
-
immutable = false
|
|
71
|
+
immutable = false,
|
|
72
|
+
failFast = true
|
|
59
73
|
}: {
|
|
60
74
|
immutable?: boolean;
|
|
75
|
+
failFast?: boolean;
|
|
61
76
|
} = {}) {
|
|
62
77
|
this.immutable = immutable;
|
|
78
|
+
this.failFast = failFast;
|
|
63
79
|
|
|
64
80
|
for (const [type, validator] of Object.entries(Types)) {
|
|
65
81
|
if (validator) {
|
|
@@ -111,146 +127,189 @@ export class SchemaShield {
|
|
|
111
127
|
return this.keywords[keyword];
|
|
112
128
|
}
|
|
113
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
|
+
|
|
114
141
|
compile(schema: any): Validator {
|
|
142
|
+
this.idRegistry.clear();
|
|
115
143
|
const compiledSchema = this.compileSchema(schema);
|
|
144
|
+
this.rootSchema = compiledSchema;
|
|
145
|
+
this.linkReferences(compiledSchema);
|
|
146
|
+
|
|
116
147
|
if (!compiledSchema.$validate) {
|
|
117
148
|
if (this.isSchemaLike(schema) === false) {
|
|
118
149
|
throw new ValidationError("Invalid schema");
|
|
119
150
|
}
|
|
120
151
|
|
|
121
152
|
compiledSchema.$validate = getNamedFunction<ValidateFunction>(
|
|
122
|
-
"
|
|
153
|
+
"Validate_Any",
|
|
123
154
|
() => {}
|
|
124
155
|
);
|
|
125
156
|
}
|
|
126
157
|
|
|
127
158
|
const validate: Validator = (data: any) => {
|
|
159
|
+
this.rootSchema = compiledSchema;
|
|
160
|
+
|
|
128
161
|
const clonedData = this.immutable ? deepClone(data) : data;
|
|
129
|
-
const
|
|
162
|
+
const res = compiledSchema.$validate!(clonedData);
|
|
130
163
|
|
|
131
|
-
|
|
132
|
-
data: clonedData,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
};
|
|
164
|
+
if (res) {
|
|
165
|
+
return { data: clonedData, error: res, valid: false };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { data: clonedData, error: null, valid: true };
|
|
136
169
|
};
|
|
137
170
|
|
|
138
171
|
validate.compiledSchema = compiledSchema;
|
|
139
|
-
|
|
140
172
|
return validate;
|
|
141
173
|
}
|
|
142
174
|
|
|
143
175
|
private compileSchema(schema: Partial<CompiledSchema> | any): CompiledSchema {
|
|
144
176
|
if (!isObject(schema)) {
|
|
145
177
|
if (schema === true) {
|
|
146
|
-
schema = {
|
|
147
|
-
anyOf: [{}]
|
|
148
|
-
};
|
|
178
|
+
schema = { anyOf: [{}] }; // Always valid
|
|
149
179
|
} else if (schema === false) {
|
|
150
|
-
schema = {
|
|
151
|
-
oneOf: []
|
|
152
|
-
};
|
|
180
|
+
schema = { oneOf: [] }; // Always invalid
|
|
153
181
|
} else {
|
|
154
|
-
schema = {
|
|
155
|
-
oneOf: [schema]
|
|
156
|
-
};
|
|
182
|
+
schema = { oneOf: [schema] };
|
|
157
183
|
}
|
|
158
184
|
}
|
|
159
185
|
|
|
160
186
|
const compiledSchema: CompiledSchema = deepClone(schema) as CompiledSchema;
|
|
161
|
-
const defineTypeError = getDefinedErrorFunctionForKey("type", schema);
|
|
162
|
-
const typeValidations: TypeFunction[] = [];
|
|
163
187
|
|
|
164
|
-
|
|
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[] = [];
|
|
165
217
|
|
|
166
218
|
if ("type" in schema) {
|
|
219
|
+
const defineTypeError = getDefinedErrorFunctionForKey(
|
|
220
|
+
"type",
|
|
221
|
+
schema,
|
|
222
|
+
this.failFast
|
|
223
|
+
);
|
|
167
224
|
const types = Array.isArray(schema.type)
|
|
168
225
|
? schema.type
|
|
169
|
-
: 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[] = [];
|
|
170
230
|
|
|
171
231
|
for (const type of types) {
|
|
172
232
|
const validator = this.getType(type);
|
|
173
233
|
if (validator) {
|
|
174
|
-
|
|
175
|
-
|
|
234
|
+
typeFunctions.push(validator);
|
|
235
|
+
typeNames.push(validator.name);
|
|
176
236
|
}
|
|
177
237
|
}
|
|
178
238
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
239
|
+
if (typeFunctions.length === 0) {
|
|
240
|
+
throw getDefinedErrorFunctionForKey(
|
|
241
|
+
"type",
|
|
242
|
+
schema,
|
|
243
|
+
this.failFast
|
|
244
|
+
)("Invalid type for schema", { data: schema.type });
|
|
183
245
|
}
|
|
184
246
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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 });
|
|
193
256
|
}
|
|
194
|
-
|
|
195
|
-
} else
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
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;
|
|
203
264
|
}
|
|
204
|
-
return defineTypeError("Invalid type", { data });
|
|
205
265
|
}
|
|
206
|
-
|
|
266
|
+
return defineTypeError("Invalid type", { data });
|
|
267
|
+
};
|
|
207
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);
|
|
208
278
|
}
|
|
209
279
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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;
|
|
215
297
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
compiledSchema.$validate = getNamedFunction<ValidateFunction>(
|
|
223
|
-
methodName,
|
|
224
|
-
(data) => {
|
|
225
|
-
const error = prevValidator(data);
|
|
226
|
-
if (error) {
|
|
227
|
-
return error;
|
|
228
|
-
}
|
|
229
|
-
return (keywordValidator as KeywordFunction)(
|
|
230
|
-
compiledSchema,
|
|
231
|
-
data,
|
|
232
|
-
defineError,
|
|
233
|
-
this
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
);
|
|
237
|
-
} else {
|
|
238
|
-
methodName = keywordValidator.name;
|
|
239
|
-
compiledSchema.$validate = getNamedFunction<ValidateFunction>(
|
|
240
|
-
methodName,
|
|
241
|
-
(data) =>
|
|
242
|
-
(keywordValidator as KeywordFunction)(
|
|
243
|
-
compiledSchema,
|
|
244
|
-
data,
|
|
245
|
-
defineError,
|
|
246
|
-
this
|
|
247
|
-
)
|
|
248
|
-
);
|
|
249
|
-
}
|
|
298
|
+
validators.push({
|
|
299
|
+
fn: keywordFn as KeywordFunction,
|
|
300
|
+
defineError
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
activeNames.push(fnName);
|
|
250
304
|
}
|
|
305
|
+
}
|
|
251
306
|
|
|
307
|
+
const literalKeywords = ["enum", "const", "default", "examples"];
|
|
308
|
+
for (const key of keyOrder) {
|
|
309
|
+
if (literalKeywords.includes(key)) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
252
312
|
if (isObject(schema[key])) {
|
|
253
|
-
// If the key is properties go through each property and try to compile it as a schema
|
|
254
313
|
if (key === "properties") {
|
|
255
314
|
for (const subKey of Object.keys(schema[key])) {
|
|
256
315
|
compiledSchema[key][subKey] = this.compileSchema(
|
|
@@ -264,15 +323,42 @@ export class SchemaShield {
|
|
|
264
323
|
}
|
|
265
324
|
|
|
266
325
|
if (Array.isArray(schema[key])) {
|
|
267
|
-
|
|
268
|
-
this.isSchemaLike(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
+
}
|
|
272
331
|
continue;
|
|
273
332
|
}
|
|
333
|
+
}
|
|
274
334
|
|
|
275
|
-
|
|
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
|
+
);
|
|
276
362
|
}
|
|
277
363
|
|
|
278
364
|
return compiledSchema as CompiledSchema;
|
|
@@ -292,4 +378,65 @@ export class SchemaShield {
|
|
|
292
378
|
}
|
|
293
379
|
return false;
|
|
294
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
|
+
}
|
|
295
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) {
|