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.
@@ -0,0 +1,79 @@
1
+ import { ValidationError } from "../utils";
2
+ import { ValidatorFunction } from "../index";
3
+
4
+ export const NumberKeywords: Record<string, ValidatorFunction> = {
5
+ minimum(schema, data, pointer) {
6
+ if (typeof data !== "number") {
7
+ return { valid: true, errors: [], data };
8
+ }
9
+
10
+ const min = schema.exclusiveMinimum
11
+ ? schema.minimum + 1e-15
12
+ : schema.minimum;
13
+
14
+ const valid = data >= min;
15
+
16
+ return {
17
+ valid,
18
+ errors: valid
19
+ ? []
20
+ : [
21
+ new ValidationError("Number is too small", {
22
+ pointer,
23
+ value: data,
24
+ code: "NUMBER_TOO_SMALL"
25
+ })
26
+ ],
27
+ data
28
+ };
29
+ },
30
+
31
+ maximum(schema, data, pointer) {
32
+ if (typeof data !== "number") {
33
+ return { valid: true, errors: [], data };
34
+ }
35
+
36
+ const max = schema.exclusiveMaximum
37
+ ? schema.maximum - 1e-15
38
+ : schema.maximum;
39
+
40
+ const valid = data <= max;
41
+
42
+ return {
43
+ valid,
44
+ errors: valid
45
+ ? []
46
+ : [
47
+ new ValidationError("Number is too large", {
48
+ pointer,
49
+ value: data,
50
+ code: "NUMBER_TOO_LARGE"
51
+ })
52
+ ],
53
+ data
54
+ };
55
+ },
56
+
57
+ multipleOf(schema, data, pointer) {
58
+ if (typeof data !== "number") {
59
+ return { valid: true, errors: [], data };
60
+ }
61
+
62
+ const quotient = data / schema.multipleOf;
63
+ const areMultiples = Math.abs(quotient - Math.round(quotient)) < 1e-15;
64
+
65
+ return {
66
+ valid: areMultiples,
67
+ errors: areMultiples
68
+ ? []
69
+ : [
70
+ new ValidationError("Number is not a multiple of", {
71
+ pointer,
72
+ value: data,
73
+ code: "NUMBER_NOT_MULTIPLE_OF"
74
+ })
75
+ ],
76
+ data
77
+ };
78
+ }
79
+ };
@@ -0,0 +1,230 @@
1
+ import { CompiledSchema, ValidatorFunction } from "../index";
2
+ import { ValidationError, isObject } from "../utils";
3
+
4
+ export const ObjectKeywords: Record<string, ValidatorFunction> = {
5
+ // Object
6
+ required(schema, data, pointer) {
7
+ if (!isObject(data)) {
8
+ return {
9
+ valid: true,
10
+ errors: [],
11
+ data
12
+ };
13
+ }
14
+
15
+ const errors = [];
16
+ for (let i = 0; i < schema.required.length; i++) {
17
+ const key = schema.required[i];
18
+ if (!data.hasOwnProperty(key)) {
19
+ errors.push(
20
+ new ValidationError("Missing required property", {
21
+ pointer: `${pointer}/${key}`,
22
+ value: data,
23
+ code: "MISSING_REQUIRED_PROPERTY"
24
+ })
25
+ );
26
+ }
27
+ }
28
+
29
+ return { valid: errors.length === 0, errors, data };
30
+ },
31
+
32
+ properties(schema, data, pointer, schemaShieldInstance) {
33
+ if (!isObject(data)) {
34
+ return { valid: true, errors: [], data };
35
+ }
36
+
37
+ const errors = [];
38
+ let finalData = { ...data };
39
+ for (let key in schema.properties) {
40
+ if (!data.hasOwnProperty(key) || typeof data[key] === "undefined") {
41
+ if (
42
+ isObject(schema.properties[key]) &&
43
+ "default" in schema.properties[key]
44
+ ) {
45
+ finalData[key] = schema.properties[key].default;
46
+ }
47
+
48
+ continue;
49
+ }
50
+
51
+ if (typeof schema.properties[key] === "boolean") {
52
+ if (schema.properties[key] === false) {
53
+ errors.push(
54
+ new ValidationError("Property is not allowed", {
55
+ pointer: `${pointer}/${key}`,
56
+ value: data[key],
57
+ code: "PROPERTY_NOT_ALLOWED"
58
+ })
59
+ );
60
+ }
61
+ continue;
62
+ }
63
+
64
+ const { validator } = schema.properties[key] as CompiledSchema;
65
+ if (!validator) {
66
+ continue;
67
+ }
68
+
69
+ const validatorResult = validator(
70
+ schema.properties[key],
71
+ finalData[key],
72
+ `${pointer}/${key}`,
73
+ schemaShieldInstance
74
+ );
75
+
76
+ finalData[key] = validatorResult.data;
77
+
78
+ if (!validatorResult.valid) {
79
+ errors.push(...validatorResult.errors);
80
+ }
81
+ }
82
+
83
+ return { valid: errors.length === 0, errors, data: finalData };
84
+ },
85
+
86
+ maxProperties(schema, data, pointer) {
87
+ if (!isObject(data) || Object.keys(data).length <= schema.maxProperties) {
88
+ return { valid: true, errors: [], data };
89
+ }
90
+
91
+ return {
92
+ valid: false,
93
+ errors: [
94
+ new ValidationError("Object has too many properties", {
95
+ pointer,
96
+ value: data,
97
+ code: "OBJECT_TOO_MANY_PROPERTIES"
98
+ })
99
+ ],
100
+ data
101
+ };
102
+ },
103
+
104
+ minProperties(schema, data, pointer) {
105
+ if (!isObject(data) || Object.keys(data).length >= schema.minProperties) {
106
+ return { valid: true, errors: [], data };
107
+ }
108
+
109
+ return {
110
+ valid: false,
111
+ errors: [
112
+ new ValidationError("Object has too few properties", {
113
+ pointer,
114
+ value: data,
115
+ code: "OBJECT_TOO_FEW_PROPERTIES"
116
+ })
117
+ ],
118
+ data
119
+ };
120
+ },
121
+
122
+ additionalProperties(schema, data, pointer, schemaShieldInstance) {
123
+ if (!isObject(data)) {
124
+ return { valid: true, errors: [], data };
125
+ }
126
+
127
+ const errors = [];
128
+ let finalData = { ...data };
129
+ for (let key in data) {
130
+ if (schema.properties && schema.properties.hasOwnProperty(key)) {
131
+ continue;
132
+ }
133
+
134
+ if (schema.patternProperties) {
135
+ let match = false;
136
+ for (let pattern in schema.patternProperties) {
137
+ if (new RegExp(pattern).test(key)) {
138
+ match = true;
139
+ break;
140
+ }
141
+ }
142
+ if (match) {
143
+ continue;
144
+ }
145
+ }
146
+
147
+ if (schema.additionalProperties === false) {
148
+ errors.push(
149
+ new ValidationError("Additional property not allowed", {
150
+ pointer: `${pointer}/${key}`,
151
+ value: data,
152
+ code: "ADDITIONAL_PROPERTY_NOT_ALLOWED"
153
+ })
154
+ );
155
+ continue;
156
+ }
157
+
158
+ const { validator } = schema.additionalProperties as CompiledSchema;
159
+ if (!validator) {
160
+ continue;
161
+ }
162
+
163
+ const validatorResult = validator(
164
+ schema.additionalProperties,
165
+ finalData[key],
166
+ `${pointer}/${key}`,
167
+ schemaShieldInstance
168
+ );
169
+
170
+ finalData[key] = validatorResult.data;
171
+
172
+ if (!validatorResult.valid) {
173
+ errors.push(...validatorResult.errors);
174
+ }
175
+ }
176
+
177
+ return { valid: errors.length === 0, errors, data: finalData };
178
+ },
179
+
180
+ patternProperties(schema, data, pointer, schemaShieldInstance) {
181
+ if (!isObject(data)) {
182
+ return { valid: true, errors: [], data };
183
+ }
184
+
185
+ const errors = [];
186
+ let finalData = { ...data };
187
+ for (let pattern in schema.patternProperties) {
188
+ if (typeof schema.patternProperties[pattern] === "boolean") {
189
+ if (schema.patternProperties[pattern] === false) {
190
+ for (let key in finalData) {
191
+ if (new RegExp(pattern).test(key)) {
192
+ errors.push(
193
+ new ValidationError("Property is not allowed", {
194
+ pointer: `${pointer}/${key}`,
195
+ value: data[key],
196
+ code: "PROPERTY_NOT_ALLOWED"
197
+ })
198
+ );
199
+ }
200
+ }
201
+ }
202
+ continue;
203
+ }
204
+
205
+ const { validator } = schema.patternProperties[pattern] as CompiledSchema;
206
+ if (!validator) {
207
+ continue;
208
+ }
209
+
210
+ for (let key in finalData) {
211
+ if (new RegExp(pattern).test(key)) {
212
+ const validatorResult = validator(
213
+ schema.patternProperties[pattern],
214
+ finalData[key],
215
+ `${pointer}/${key}`,
216
+ schemaShieldInstance
217
+ );
218
+
219
+ finalData[key] = validatorResult.data;
220
+
221
+ if (!validatorResult.valid) {
222
+ errors.push(...validatorResult.errors);
223
+ }
224
+ }
225
+ }
226
+ }
227
+
228
+ return { valid: errors.length === 0, errors, data: finalData };
229
+ }
230
+ };
@@ -0,0 +1,248 @@
1
+ import { CompiledSchema, ValidatorFunction } from "../index";
2
+ import { ValidationError, isObject } from "../utils";
3
+
4
+ export const OtherKeywords: Record<string, ValidatorFunction> = {
5
+ nullable(schema, data, pointer) {
6
+ if (schema.nullable && data !== null) {
7
+ return {
8
+ valid: false,
9
+ errors: [
10
+ new ValidationError("Value must be null to be empty", {
11
+ pointer,
12
+ value: data,
13
+ code: "VALUE_NOT_NULL"
14
+ })
15
+ ],
16
+ data
17
+ };
18
+ }
19
+
20
+ return { valid: true, errors: [], data };
21
+ },
22
+
23
+ oneOf(schema, data, pointer, schemaShieldInstance) {
24
+ const errors = [];
25
+ let validCount = 0;
26
+ let finalData = data;
27
+ for (let i = 0; i < schema.oneOf.length; i++) {
28
+ if (isObject(schema.oneOf[i])) {
29
+ const { validator } = schema.oneOf[i] as CompiledSchema;
30
+ if (!validator) {
31
+ validCount++;
32
+ continue;
33
+ }
34
+ const validationResult = validator(
35
+ schema.oneOf[i],
36
+ finalData,
37
+ pointer,
38
+ schemaShieldInstance
39
+ );
40
+ if (validationResult.valid) {
41
+ validCount++;
42
+ } else {
43
+ errors.push(...validationResult.errors);
44
+ }
45
+ finalData = validationResult.data;
46
+ } else {
47
+ if (typeof schema.oneOf[i] === "boolean") {
48
+ if (Boolean(data) === schema.oneOf[i]) {
49
+ validCount++;
50
+ }
51
+ continue;
52
+ }
53
+
54
+ if (data === schema.oneOf[i]) {
55
+ validCount++;
56
+ }
57
+ }
58
+ }
59
+
60
+ if (validCount === 1) {
61
+ return { valid: true, errors: [], data: finalData };
62
+ }
63
+
64
+ return {
65
+ valid: false,
66
+ errors: [
67
+ new ValidationError(`Value must match exactly one schema in oneOf`, {
68
+ pointer,
69
+ value: data,
70
+ code: "VALUE_DOES_NOT_MATCH_ONE_OF"
71
+ })
72
+ ],
73
+ data: finalData
74
+ };
75
+ },
76
+
77
+ allOf(schema, data, pointer, schemaShieldInstance) {
78
+ const errors = [];
79
+ let finalData = data;
80
+ for (let i = 0; i < schema.allOf.length; i++) {
81
+ if (isObject(schema.allOf[i])) {
82
+ const { validator } = schema.allOf[i] as CompiledSchema;
83
+ if (!validator) {
84
+ continue;
85
+ }
86
+
87
+ const validatorResult = validator(
88
+ schema.allOf[i],
89
+ finalData,
90
+ pointer,
91
+ schemaShieldInstance
92
+ );
93
+
94
+ if (!validatorResult.valid) {
95
+ errors.push(...validatorResult.errors);
96
+ }
97
+
98
+ finalData = validatorResult.data;
99
+ } else {
100
+ if (typeof schema.allOf[i] === "boolean") {
101
+ if (Boolean(data) !== schema.allOf[i]) {
102
+ errors.push(
103
+ new ValidationError(`Value must match all schemas in allOf`, {
104
+ pointer,
105
+ value: data,
106
+ code: "VALUE_DOES_NOT_MATCH_ALL_OF"
107
+ })
108
+ );
109
+ }
110
+ continue;
111
+ }
112
+
113
+ if (data !== schema.allOf[i]) {
114
+ errors.push(
115
+ new ValidationError(`Value must match all schemas in allOf`, {
116
+ pointer,
117
+ value: data,
118
+ code: "VALUE_DOES_NOT_MATCH_ALL_OF"
119
+ })
120
+ );
121
+ }
122
+ }
123
+ }
124
+
125
+ return { valid: errors.length === 0, errors, data: finalData };
126
+ },
127
+
128
+ anyOf(schema, data, pointer, schemaShieldInstance) {
129
+ let finalData = data;
130
+
131
+ for (let i = 0; i < schema.anyOf.length; i++) {
132
+ if (isObject(schema.anyOf[i])) {
133
+ const { validator } = schema.anyOf[i] as CompiledSchema;
134
+ if (!validator) {
135
+ return { valid: true, errors: [], data };
136
+ }
137
+ const validationResult = validator(
138
+ schema.anyOf[i],
139
+ finalData,
140
+ pointer,
141
+ schemaShieldInstance
142
+ );
143
+ finalData = validationResult.data;
144
+ if (validationResult.valid) {
145
+ return { valid: true, errors: [], data: finalData };
146
+ }
147
+ } else {
148
+ if (typeof schema.anyOf[i] === "boolean") {
149
+ if (Boolean(data) === schema.anyOf[i]) {
150
+ return { valid: true, errors: [], data: finalData };
151
+ }
152
+ }
153
+
154
+ if (data === schema.anyOf[i]) {
155
+ return { valid: true, errors: [], data: finalData };
156
+ }
157
+ }
158
+ }
159
+
160
+ return {
161
+ valid: false,
162
+ errors: [
163
+ new ValidationError(`Value must match at least one schema in anyOf`, {
164
+ pointer,
165
+ value: data,
166
+ code: "VALUE_DOES_NOT_MATCH_ANY_OF"
167
+ })
168
+ ],
169
+ data
170
+ };
171
+ },
172
+
173
+ dependencies(schema, data, pointer, schemaShieldInstance) {
174
+ if (!isObject(data)) {
175
+ return { valid: true, errors: [], data };
176
+ }
177
+
178
+ const errors = [];
179
+ let finalData = data;
180
+ for (const key in schema.dependencies) {
181
+ if (key in data === false) {
182
+ continue;
183
+ }
184
+
185
+ const dependency = schema.dependencies[key];
186
+ if (Array.isArray(dependency)) {
187
+ for (let i = 0; i < dependency.length; i++) {
188
+ if (!(dependency[i] in data)) {
189
+ errors.push(
190
+ new ValidationError(`Dependency ${dependency[i]} is missing`, {
191
+ pointer,
192
+ value: data,
193
+ code: "DEPENDENCY_MISSING"
194
+ })
195
+ );
196
+ }
197
+ }
198
+ continue;
199
+ }
200
+
201
+ if (typeof dependency === "boolean") {
202
+ if (dependency) {
203
+ continue;
204
+ }
205
+ errors.push(
206
+ new ValidationError(`Dependency ${key} is missing`, {
207
+ pointer,
208
+ value: data,
209
+ code: "DEPENDENCY_MISSING"
210
+ })
211
+ );
212
+ continue;
213
+ }
214
+
215
+ if (typeof dependency === "string") {
216
+ if (dependency in data) {
217
+ continue;
218
+ }
219
+ errors.push(
220
+ new ValidationError(`Dependency ${dependency} is missing`, {
221
+ pointer,
222
+ value: data,
223
+ code: "DEPENDENCY_MISSING"
224
+ })
225
+ );
226
+ continue;
227
+ }
228
+
229
+ const { validator } = dependency as CompiledSchema;
230
+ if (!validator) {
231
+ continue;
232
+ }
233
+
234
+ const validatorResult = validator(
235
+ dependency,
236
+ finalData,
237
+ pointer,
238
+ schemaShieldInstance
239
+ );
240
+ if (!validatorResult.valid) {
241
+ errors.push(...validatorResult.errors);
242
+ }
243
+ finalData = validatorResult.data;
244
+ }
245
+
246
+ return { valid: errors.length === 0, errors, data: finalData };
247
+ }
248
+ };
@@ -0,0 +1,165 @@
1
+ import { ValidationError, deepEqual } from "../utils";
2
+
3
+ import { ValidatorFunction } from "../index";
4
+
5
+ export const StringKeywords: Record<string, ValidatorFunction> = {
6
+ minLength(schema, data, pointer) {
7
+ if (typeof data !== "string" || data.length >= schema.minLength) {
8
+ return { valid: true, errors: [], data };
9
+ }
10
+
11
+ return {
12
+ valid: false,
13
+ errors: [
14
+ new ValidationError("String is too short", {
15
+ pointer,
16
+ value: data,
17
+ code: "STRING_TOO_SHORT"
18
+ })
19
+ ],
20
+ data
21
+ };
22
+ },
23
+
24
+ maxLength(schema, data, pointer) {
25
+ if (typeof data !== "string" || data.length <= schema.maxLength) {
26
+ return { valid: true, errors: [], data };
27
+ }
28
+
29
+ return {
30
+ valid: false,
31
+ errors: [
32
+ new ValidationError("String is too long", {
33
+ pointer,
34
+ value: data,
35
+ code: "STRING_TOO_LONG"
36
+ })
37
+ ],
38
+ data
39
+ };
40
+ },
41
+
42
+ pattern(schema, data, pointer) {
43
+ if (typeof data !== "string") {
44
+ return { valid: true, errors: [], data };
45
+ }
46
+
47
+ const patternRegexp =
48
+ typeof schema.pattern === "string"
49
+ ? new RegExp(schema.pattern)
50
+ : schema.pattern;
51
+
52
+ if (patternRegexp instanceof RegExp === false) {
53
+ return {
54
+ valid: false,
55
+ errors: [
56
+ new ValidationError("Pattern is not a valid regular expression", {
57
+ pointer,
58
+ value: data,
59
+ code: "PATTERN_IS_NOT_REGEXP"
60
+ })
61
+ ],
62
+ data
63
+ };
64
+ }
65
+
66
+ const valid = patternRegexp.test(data);
67
+
68
+ return {
69
+ valid,
70
+ errors: valid
71
+ ? []
72
+ : [
73
+ new ValidationError("String does not match pattern", {
74
+ pointer,
75
+ value: data,
76
+ code: "STRING_DOES_NOT_MATCH_PATTERN"
77
+ })
78
+ ],
79
+ data
80
+ };
81
+ },
82
+
83
+ format(schema, data, pointer, formatInstance) {
84
+ if (typeof data !== "string") {
85
+ return { valid: true, errors: [], data };
86
+ }
87
+
88
+ const formatValidate = formatInstance.formats.get(schema.format);
89
+ if (!formatValidate) {
90
+ return {
91
+ valid: false,
92
+ errors: [
93
+ new ValidationError(`Unknown format ${schema.format}`, {
94
+ pointer,
95
+ value: data,
96
+ code: "UNKNOWN_FORMAT"
97
+ })
98
+ ],
99
+ data
100
+ };
101
+ }
102
+
103
+ const valid = formatValidate(data);
104
+
105
+ return {
106
+ valid,
107
+ errors: valid
108
+ ? []
109
+ : [
110
+ new ValidationError(
111
+ `String does not match format ${schema.format}`,
112
+ {
113
+ pointer,
114
+ value: data,
115
+ code: "STRING_DOES_NOT_MATCH_FORMAT"
116
+ }
117
+ )
118
+ ],
119
+ data
120
+ };
121
+ },
122
+
123
+ enum(schema, data, pointer) {
124
+ // Simple equality check
125
+ for (let i = 0; i < schema.enum.length; i++) {
126
+ if (schema.enum[i] === data) {
127
+ return { valid: true, errors: [], data };
128
+ }
129
+ }
130
+
131
+ // If is an array check for a deep equality
132
+ if (Array.isArray(data)) {
133
+ for (let i = 0; i < schema.enum.length; i++) {
134
+ if (Array.isArray(schema.enum[i])) {
135
+ if (deepEqual(schema.enum[i], data)) {
136
+ return { valid: true, errors: [], data };
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ // If is an object check for a deep equality
143
+ if (typeof data === "object" && data !== null) {
144
+ for (let i = 0; i < schema.enum.length; i++) {
145
+ if (typeof schema.enum[i] === "object" && schema.enum[i] !== null) {
146
+ if (deepEqual(schema.enum[i], data)) {
147
+ return { valid: true, errors: [], data };
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ return {
154
+ valid: false,
155
+ errors: [
156
+ new ValidationError(`Value must be one of ${schema.enum.join(", ")}`, {
157
+ pointer,
158
+ value: data,
159
+ code: "VALUE_NOT_IN_ENUM"
160
+ })
161
+ ],
162
+ data
163
+ };
164
+ }
165
+ };