schema-shield 0.0.2
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 +11 -0
- package/dist/formats.d.ts +3 -0
- package/dist/formats.d.ts.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1289 -0
- package/dist/index.min.js +1 -0
- package/dist/index.min.js.map +1 -0
- package/dist/index.mjs +1267 -0
- package/dist/keywords/array-keywords.d.ts +3 -0
- package/dist/keywords/array-keywords.d.ts.map +1 -0
- package/dist/keywords/number-keywords.d.ts +3 -0
- package/dist/keywords/number-keywords.d.ts.map +1 -0
- package/dist/keywords/object-keywords.d.ts +3 -0
- package/dist/keywords/object-keywords.d.ts.map +1 -0
- package/dist/keywords/other-keywords.d.ts +3 -0
- package/dist/keywords/other-keywords.d.ts.map +1 -0
- package/dist/keywords/string-keywords.d.ts +3 -0
- package/dist/keywords/string-keywords.d.ts.map +1 -0
- package/dist/keywords.d.ts +3 -0
- package/dist/keywords.d.ts.map +1 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.d.ts.map +1 -0
- package/lib/formats.ts +56 -0
- package/lib/index.ts +308 -0
- package/lib/keywords/array-keywords.ts +204 -0
- package/lib/keywords/number-keywords.ts +79 -0
- package/lib/keywords/object-keywords.ts +230 -0
- package/lib/keywords/other-keywords.ts +248 -0
- package/lib/keywords/string-keywords.ts +165 -0
- package/lib/keywords.ts +14 -0
- package/lib/types.ts +176 -0
- package/lib/utils.ts +79 -0
- package/package.json +189 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"array-keywords.d.ts","sourceRoot":"","sources":["../../lib/keywords/array-keywords.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7D,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAwM3D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-keywords.d.ts","sourceRoot":"","sources":["../../lib/keywords/number-keywords.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CA2E5D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-keywords.d.ts","sourceRoot":"","sources":["../../lib/keywords/object-keywords.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7D,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAkO5D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"other-keywords.d.ts","sourceRoot":"","sources":["../../lib/keywords/other-keywords.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7D,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAoP3D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string-keywords.d.ts","sourceRoot":"","sources":["../../lib/keywords/string-keywords.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAgK5D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keywords.d.ts","sourceRoot":"","sources":["../lib/keywords.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAMtD,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../lib/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CA4KnD,CAAC"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ValidationErrorProps } from "./index";
|
|
2
|
+
export declare class ValidationError extends Error {
|
|
3
|
+
name: string;
|
|
4
|
+
pointer: string;
|
|
5
|
+
message: string;
|
|
6
|
+
value: any;
|
|
7
|
+
code: string;
|
|
8
|
+
constructor(message: string, options?: ValidationErrorProps);
|
|
9
|
+
}
|
|
10
|
+
export declare const defaultValidator: (schema: any, data: any, pointer: any) => ValidationError[];
|
|
11
|
+
export declare function deepEqual(obj: Array<any> | Record<string, any>, other: Array<any> | Record<string, any>): boolean;
|
|
12
|
+
export declare function isObject(data: any): boolean;
|
|
13
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE7D,qBAAa,eAAgB,SAAQ,KAAK;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,GAAG,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;gBAGX,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAIR;CASJ;AAED,eAAO,MAAM,gBAAgB,6DAQ5B,CAAC;AAEF,wBAAgB,SAAS,CACvB,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACrC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,WAoCxC;AAED,wBAAgB,QAAQ,CAAC,IAAI,KAAA,WAE5B"}
|
package/lib/formats.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { FormatFunction, ValidationError, ValidatorFunction } from "./utils";
|
|
2
|
+
|
|
3
|
+
const RegExps = {
|
|
4
|
+
"date-time":
|
|
5
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/,
|
|
6
|
+
uri: /^[a-zA-Z][a-zA-Z0-9+\-.]*:[^\s]*$/,
|
|
7
|
+
email:
|
|
8
|
+
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
|
|
9
|
+
ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
|
10
|
+
ipv6: /^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?=(?:[0-9a-fA-F]{0,4}:){0,7}[0-9a-fA-F]{0,4}(?![:.\w]))(([0-9a-fA-F]{1,4}:){1,7}|:)((:[0-9a-fA-F]{1,4}){1,7}|:))$/,
|
|
11
|
+
hostname: /^[a-zA-Z0-9][a-zA-Z0-9-]{0,62}(\.[a-zA-Z0-9][a-zA-Z0-9-]{0,62})*$/
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function notImplementedFormat(data: any) {
|
|
15
|
+
throw new ValidationError(
|
|
16
|
+
`Format "${data}" is not implemented yet. Please open an issue on GitHub.`
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const Formats: Record<string, FormatFunction> = {
|
|
23
|
+
["date-time"](data) {
|
|
24
|
+
return RegExps["date-time"].test(data);
|
|
25
|
+
},
|
|
26
|
+
uri(data) {
|
|
27
|
+
return RegExps.uri.test(data);
|
|
28
|
+
},
|
|
29
|
+
email(data) {
|
|
30
|
+
return RegExps.email.test(data);
|
|
31
|
+
},
|
|
32
|
+
ipv4(data) {
|
|
33
|
+
return RegExps.ipv4.test(data);
|
|
34
|
+
},
|
|
35
|
+
ipv6(data) {
|
|
36
|
+
return RegExps.ipv6.test(data);
|
|
37
|
+
},
|
|
38
|
+
hostname(data) {
|
|
39
|
+
return RegExps.hostname.test(data);
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// Not supported yet
|
|
43
|
+
time: notImplementedFormat,
|
|
44
|
+
date: notImplementedFormat,
|
|
45
|
+
duration: notImplementedFormat,
|
|
46
|
+
"idn-email": notImplementedFormat,
|
|
47
|
+
"idn-hostname": notImplementedFormat,
|
|
48
|
+
uuid: notImplementedFormat,
|
|
49
|
+
"uri-reference": notImplementedFormat,
|
|
50
|
+
iri: notImplementedFormat,
|
|
51
|
+
"iri-reference": notImplementedFormat,
|
|
52
|
+
"uri-template": notImplementedFormat,
|
|
53
|
+
"json-pointer": notImplementedFormat,
|
|
54
|
+
"relative-json-pointer": notImplementedFormat,
|
|
55
|
+
regex: notImplementedFormat
|
|
56
|
+
};
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { ValidationError, isObject } from "./utils";
|
|
2
|
+
|
|
3
|
+
import { Formats } from "./formats";
|
|
4
|
+
import { Types } from "./types";
|
|
5
|
+
import { keywords } from "./keywords";
|
|
6
|
+
|
|
7
|
+
export interface ValidationErrorProps {
|
|
8
|
+
pointer: string;
|
|
9
|
+
value: any;
|
|
10
|
+
code: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Result {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
errors: ValidationError[];
|
|
16
|
+
data: any;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ValidatorFunction {
|
|
20
|
+
(
|
|
21
|
+
schema: CompiledSchema,
|
|
22
|
+
data: any,
|
|
23
|
+
pointer: string,
|
|
24
|
+
schemaShieldInstance: SchemaShield
|
|
25
|
+
): Result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FormatFunction {
|
|
29
|
+
(data: any): boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CompiledSchema {
|
|
33
|
+
pointer: string;
|
|
34
|
+
validator?: ValidatorFunction;
|
|
35
|
+
type?: string;
|
|
36
|
+
validators?: ValidatorFunction[];
|
|
37
|
+
keywords?: Record<string, ValidatorFunction>;
|
|
38
|
+
[key: string]: any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Validator {
|
|
42
|
+
(data: any): Result;
|
|
43
|
+
compiledSchema: CompiledSchema;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class SchemaShield {
|
|
47
|
+
types = new Map<string, ValidatorFunction>();
|
|
48
|
+
formats = new Map<string, FormatFunction>();
|
|
49
|
+
keywords = new Map<string, ValidatorFunction>();
|
|
50
|
+
|
|
51
|
+
constructor() {
|
|
52
|
+
for (const type in Types) {
|
|
53
|
+
this.addType(type, Types[type]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const keyword in keywords) {
|
|
57
|
+
this.addKeyword(keyword, keywords[keyword]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const format in Formats) {
|
|
61
|
+
this.addFormat(format, Formats[format]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
addType(name: string, validator: ValidatorFunction) {
|
|
66
|
+
this.types.set(name, validator);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
addFormat(name: string, validator: FormatFunction) {
|
|
70
|
+
this.formats.set(name, validator);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
addKeyword(name: string, validator: ValidatorFunction) {
|
|
74
|
+
this.keywords.set(name, validator);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
compile(schema: any): Validator {
|
|
78
|
+
const compiledSchema = this.compileSchema(schema, "#");
|
|
79
|
+
const schemaShield = this;
|
|
80
|
+
|
|
81
|
+
function validate(data: any) {
|
|
82
|
+
return compiledSchema.validator(compiledSchema, data, "#", schemaShield);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
validate.compiledSchema = compiledSchema;
|
|
86
|
+
|
|
87
|
+
return validate;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private compileSchema(
|
|
91
|
+
schema: Partial<CompiledSchema>,
|
|
92
|
+
pointer
|
|
93
|
+
): CompiledSchema {
|
|
94
|
+
if (typeof schema !== "object" || schema === null) {
|
|
95
|
+
throw new ValidationError("Schema is not an object", {
|
|
96
|
+
pointer,
|
|
97
|
+
value: schema,
|
|
98
|
+
code: "SCHEMA_NOT_OBJECT"
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const compiledSchema = {
|
|
103
|
+
...schema,
|
|
104
|
+
pointer
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if ("type" in compiledSchema) {
|
|
108
|
+
const types = Array.isArray(compiledSchema.type)
|
|
109
|
+
? compiledSchema.type
|
|
110
|
+
: compiledSchema.type.split(",").map((t) => t.trim());
|
|
111
|
+
|
|
112
|
+
compiledSchema.validators = types
|
|
113
|
+
.filter((type) => this.types.has(type))
|
|
114
|
+
.map((type) => this.types.get(type));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Compile schema type
|
|
118
|
+
const validator: ValidatorFunction = (
|
|
119
|
+
schema: any,
|
|
120
|
+
data: any,
|
|
121
|
+
pointer: string
|
|
122
|
+
) => {
|
|
123
|
+
if (typeof data === "undefined") {
|
|
124
|
+
if (pointer === "#") {
|
|
125
|
+
return {
|
|
126
|
+
valid: false,
|
|
127
|
+
errors: [
|
|
128
|
+
new ValidationError("Data is undefined", {
|
|
129
|
+
pointer,
|
|
130
|
+
value: data,
|
|
131
|
+
code: "DATA_UNDEFINED"
|
|
132
|
+
})
|
|
133
|
+
],
|
|
134
|
+
data
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let finalData = data;
|
|
140
|
+
const typeErrorsResult = this.validateTypes(schema, finalData, pointer);
|
|
141
|
+
if (typeErrorsResult.valid === false) {
|
|
142
|
+
return typeErrorsResult;
|
|
143
|
+
}
|
|
144
|
+
finalData = typeErrorsResult.data;
|
|
145
|
+
|
|
146
|
+
return this.validateKeywords(schema, finalData, pointer);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
compiledSchema.validator = validator;
|
|
150
|
+
|
|
151
|
+
// Recursively compile sub schemas
|
|
152
|
+
for (let key in schema) {
|
|
153
|
+
// Skip type as it is already compiled
|
|
154
|
+
if (key === "type") {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (this.keywords.has(key)) {
|
|
159
|
+
const validator = this.keywords.get(key);
|
|
160
|
+
compiledSchema.keywords = compiledSchema.keywords || {};
|
|
161
|
+
compiledSchema.keywords[key] = validator;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (Array.isArray(schema[key])) {
|
|
165
|
+
this.handleArraySchema(key, schema, pointer, compiledSchema);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (isObject(schema[key])) {
|
|
170
|
+
this.handleObjectSchema(key, schema, pointer, compiledSchema);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return compiledSchema;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private handleArraySchema(
|
|
179
|
+
key: string,
|
|
180
|
+
schema: any,
|
|
181
|
+
pointer: string,
|
|
182
|
+
compiledSchema: any
|
|
183
|
+
) {
|
|
184
|
+
compiledSchema[key] = schema[key].map((subSchema, index) => {
|
|
185
|
+
if (typeof subSchema === "object" && subSchema !== null) {
|
|
186
|
+
if ("type" in subSchema) {
|
|
187
|
+
return this.compileSchema(subSchema, `${pointer}/${key}/${index}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (let subKey in subSchema) {
|
|
191
|
+
if (this.keywords.has(subKey)) {
|
|
192
|
+
return this.compileSchema(subSchema, `${pointer}/${key}/${index}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return subSchema;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private handleObjectSchema(
|
|
201
|
+
key: string,
|
|
202
|
+
schema: any,
|
|
203
|
+
pointer: string,
|
|
204
|
+
compiledSchema: any
|
|
205
|
+
) {
|
|
206
|
+
if ("type" in schema[key]) {
|
|
207
|
+
compiledSchema[key] = this.compileSchema(
|
|
208
|
+
schema[key],
|
|
209
|
+
`${pointer}/${key}`
|
|
210
|
+
);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (let subKey in schema[key]) {
|
|
215
|
+
compiledSchema[key] = compiledSchema[key] || {};
|
|
216
|
+
|
|
217
|
+
if (this.keywords.has(subKey)) {
|
|
218
|
+
compiledSchema[key][subKey] = this.compileSchema(
|
|
219
|
+
schema[key][subKey],
|
|
220
|
+
`${pointer}/${subKey}`
|
|
221
|
+
);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (typeof schema[key][subKey] === "object") {
|
|
226
|
+
if ("type" in schema[key][subKey]) {
|
|
227
|
+
compiledSchema[key][subKey] = this.compileSchema(
|
|
228
|
+
schema[key][subKey],
|
|
229
|
+
`${pointer}/${key}/${subKey}`
|
|
230
|
+
);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (let subSubKey in schema[key][subKey]) {
|
|
235
|
+
if (this.keywords.has(subSubKey)) {
|
|
236
|
+
compiledSchema[key][subKey] = this.compileSchema(
|
|
237
|
+
schema[key][subKey],
|
|
238
|
+
`${pointer}/${key}/${subKey}`
|
|
239
|
+
);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private validateTypes(schema: CompiledSchema, data, pointer): Result {
|
|
248
|
+
if (
|
|
249
|
+
typeof data === "undefined" ||
|
|
250
|
+
!Array.isArray(schema.validators) ||
|
|
251
|
+
schema.validators.length === 0
|
|
252
|
+
) {
|
|
253
|
+
return {
|
|
254
|
+
valid: true,
|
|
255
|
+
errors: [],
|
|
256
|
+
data
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let errors = [];
|
|
261
|
+
let finalData = data;
|
|
262
|
+
|
|
263
|
+
for (let schemaValidator of schema.validators) {
|
|
264
|
+
const schemaResult = schemaValidator(schema, data, pointer, this);
|
|
265
|
+
|
|
266
|
+
finalData = schemaResult.data;
|
|
267
|
+
|
|
268
|
+
if (schemaResult.valid) {
|
|
269
|
+
return schemaResult;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
errors = schemaResult.errors;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
valid: errors.length === 0,
|
|
277
|
+
errors,
|
|
278
|
+
data: finalData
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private validateKeywords(schema: CompiledSchema, data, pointer): Result {
|
|
283
|
+
const errors = [];
|
|
284
|
+
let finalData = data;
|
|
285
|
+
|
|
286
|
+
if ("keywords" in schema) {
|
|
287
|
+
for (let keyword in schema.keywords) {
|
|
288
|
+
const keywordValidator: ValidatorFunction = schema.keywords[keyword];
|
|
289
|
+
const keywordResult = keywordValidator(
|
|
290
|
+
schema,
|
|
291
|
+
finalData,
|
|
292
|
+
pointer,
|
|
293
|
+
this
|
|
294
|
+
);
|
|
295
|
+
finalData = keywordResult.data;
|
|
296
|
+
if (!keywordResult.valid) {
|
|
297
|
+
errors.push(...keywordResult.errors);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
valid: errors.length === 0,
|
|
304
|
+
errors,
|
|
305
|
+
data: finalData
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { CompiledSchema, ValidatorFunction } from "../index";
|
|
2
|
+
import { ValidationError, isObject } from "../utils";
|
|
3
|
+
|
|
4
|
+
export const ArrayKeywords: Record<string, ValidatorFunction> = {
|
|
5
|
+
items(schema, data, pointer, schemaShieldInstance) {
|
|
6
|
+
if (!Array.isArray(data)) {
|
|
7
|
+
return { valid: true, errors: [], data };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const errors = [];
|
|
11
|
+
let finalData = [...data];
|
|
12
|
+
if (Array.isArray(schema.items)) {
|
|
13
|
+
for (let i = 0; i < schema.items.length; i++) {
|
|
14
|
+
if (typeof schema.items[i] === "boolean") {
|
|
15
|
+
if (schema.items[i] === false && typeof data[i] !== "undefined") {
|
|
16
|
+
errors.push(
|
|
17
|
+
new ValidationError("Array item is not allowed", {
|
|
18
|
+
pointer: `${pointer}/${i}`,
|
|
19
|
+
value: data[i],
|
|
20
|
+
code: "ARRAY_ITEM_NOT_ALLOWED"
|
|
21
|
+
})
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { validator } = schema.items[i] as CompiledSchema;
|
|
28
|
+
if (!validator) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const validatorResult = validator(
|
|
32
|
+
schema.items[i],
|
|
33
|
+
finalData[i],
|
|
34
|
+
`${pointer}/${i}`,
|
|
35
|
+
schemaShieldInstance
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
finalData[i] = validatorResult.data;
|
|
39
|
+
|
|
40
|
+
if (!validatorResult.valid) {
|
|
41
|
+
errors.push(...validatorResult.errors);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} else if (typeof schema.items === "boolean") {
|
|
45
|
+
if (schema.items === false && data.length > 0) {
|
|
46
|
+
errors.push(
|
|
47
|
+
new ValidationError("Array is not allowed", {
|
|
48
|
+
pointer,
|
|
49
|
+
value: data,
|
|
50
|
+
code: "ARRAY_NOT_ALLOWED"
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
const { validator } = schema.items as CompiledSchema;
|
|
56
|
+
if (!validator) {
|
|
57
|
+
return { valid: true, errors: [], data };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < finalData.length; i++) {
|
|
61
|
+
const validatorErrors = validator(
|
|
62
|
+
schema.items,
|
|
63
|
+
finalData[i],
|
|
64
|
+
`${pointer}/${i}`,
|
|
65
|
+
schemaShieldInstance
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
finalData[i] = validatorErrors.data;
|
|
69
|
+
|
|
70
|
+
if (!validatorErrors.valid) {
|
|
71
|
+
errors.push(...validatorErrors.errors);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { valid: errors.length === 0, errors, data: finalData };
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
minItems(schema, data, pointer) {
|
|
80
|
+
if (!Array.isArray(data) || data.length >= schema.minItems) {
|
|
81
|
+
return { valid: true, errors: [], data };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
valid: false,
|
|
86
|
+
errors: [
|
|
87
|
+
new ValidationError("Array is too short", {
|
|
88
|
+
pointer,
|
|
89
|
+
value: data,
|
|
90
|
+
code: "ARRAY_TOO_SHORT"
|
|
91
|
+
})
|
|
92
|
+
],
|
|
93
|
+
data
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
maxItems(schema, data, pointer) {
|
|
98
|
+
if (!Array.isArray(data) || data.length <= schema.maxItems) {
|
|
99
|
+
return { valid: true, errors: [], data };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
valid: false,
|
|
104
|
+
errors: [
|
|
105
|
+
new ValidationError("Array is too long", {
|
|
106
|
+
pointer,
|
|
107
|
+
value: data,
|
|
108
|
+
code: "ARRAY_TOO_LONG"
|
|
109
|
+
})
|
|
110
|
+
],
|
|
111
|
+
data
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
additionalItems(schema, data, pointer, schemaShieldInstance) {
|
|
116
|
+
if (!Array.isArray(data) || !schema.items || !Array.isArray(schema.items)) {
|
|
117
|
+
return { valid: true, errors: [], data };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (schema.additionalItems === false) {
|
|
121
|
+
if (data.length > schema.items.length) {
|
|
122
|
+
return {
|
|
123
|
+
valid: false,
|
|
124
|
+
errors: [
|
|
125
|
+
new ValidationError("Array has too many items", {
|
|
126
|
+
pointer,
|
|
127
|
+
value: data,
|
|
128
|
+
code: "ARRAY_TOO_MANY_ITEMS"
|
|
129
|
+
})
|
|
130
|
+
],
|
|
131
|
+
data
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { valid: true, errors: [], data };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const errors = [];
|
|
139
|
+
let finalData = [...data];
|
|
140
|
+
if (typeof schema.additionalItems === "object") {
|
|
141
|
+
for (let i = schema.items.length; i < finalData.length; i++) {
|
|
142
|
+
const { validator } = schema.additionalItems as CompiledSchema;
|
|
143
|
+
const validatorResult = validator(
|
|
144
|
+
schema.additionalItems,
|
|
145
|
+
finalData[i],
|
|
146
|
+
`${pointer}/${i}`,
|
|
147
|
+
schemaShieldInstance
|
|
148
|
+
);
|
|
149
|
+
if (!validatorResult.valid) {
|
|
150
|
+
errors.push(...validatorResult.errors);
|
|
151
|
+
}
|
|
152
|
+
finalData[i] = validatorResult.data;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { valid: errors.length === 0, errors, data: finalData };
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
uniqueItems(schema, data, pointer) {
|
|
160
|
+
if (!Array.isArray(data) || !schema.uniqueItems) {
|
|
161
|
+
return { valid: true, errors: [], data };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const unique = new Set();
|
|
165
|
+
|
|
166
|
+
for (const item of data) {
|
|
167
|
+
let itemStr = item;
|
|
168
|
+
|
|
169
|
+
// Change string to "string" to avoid false positives
|
|
170
|
+
if (typeof item === "string") {
|
|
171
|
+
itemStr = `"${item}"`;
|
|
172
|
+
|
|
173
|
+
// Sort object keys to avoid false positives
|
|
174
|
+
} else if (isObject(item)) {
|
|
175
|
+
const keys = Object.keys(item).sort();
|
|
176
|
+
const sorted = {};
|
|
177
|
+
for (let i = 0; i < keys.length; i++) {
|
|
178
|
+
sorted[keys[i]] = item[keys[i]];
|
|
179
|
+
}
|
|
180
|
+
itemStr = JSON.stringify(sorted);
|
|
181
|
+
} else if (Array.isArray(item)) {
|
|
182
|
+
itemStr = JSON.stringify(item);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (unique.has(itemStr)) {
|
|
186
|
+
return {
|
|
187
|
+
valid: false,
|
|
188
|
+
errors: [
|
|
189
|
+
new ValidationError("Array items are not unique", {
|
|
190
|
+
pointer,
|
|
191
|
+
value: data,
|
|
192
|
+
code: "ARRAY_ITEMS_NOT_UNIQUE"
|
|
193
|
+
})
|
|
194
|
+
],
|
|
195
|
+
data
|
|
196
|
+
};
|
|
197
|
+
} else {
|
|
198
|
+
unique.add(itemStr);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { valid: true, errors: [], data };
|
|
203
|
+
}
|
|
204
|
+
};
|