arstotzka 0.13.1 → 0.13.3
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/index.js +37 -29
- package/package.json +2 -2
- package/tests.js +115 -0
- package/usage.js +53 -83
package/index.js
CHANGED
|
@@ -8,7 +8,8 @@ export const ERRORS = {
|
|
|
8
8
|
targetIsNull: "Passed object or array item is null",
|
|
9
9
|
functionExpected: "Expected function as dynamic constraint",
|
|
10
10
|
objectExpected: "Expected object",
|
|
11
|
-
anyFailed: "None of ANY_OF constraints are met"
|
|
11
|
+
anyFailed: "None of ANY_OF constraints are met",
|
|
12
|
+
parsingError: "Schema parsing error"
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
export const OPTIONAL = Symbol();
|
|
@@ -75,34 +76,38 @@ function constraint(f, failMessageId, expected){
|
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
function parseSchema(schemaProperty){
|
|
79
|
+
function parseSchema(schemaProperty, errors){
|
|
79
80
|
function unify(raw){
|
|
80
|
-
if (raw
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
81
|
+
if (raw !== null && raw !== undefined){
|
|
82
|
+
if (raw.type == CONSTRAINT) return raw;
|
|
83
|
+
|
|
84
|
+
if (Array.isArray(raw)){
|
|
85
|
+
return raw.map(c => unify(c)).filter(raw => raw !== null);
|
|
86
|
+
} else {
|
|
87
|
+
switch (typeof raw){
|
|
88
|
+
case ("string"): {
|
|
89
|
+
if (raw == "array")
|
|
90
|
+
return constraint(IS_ARRAY, "typeMismatch", raw);
|
|
91
|
+
else
|
|
92
|
+
return constraint(TYPE(raw), "typeMismatch", raw);
|
|
93
|
+
}
|
|
94
|
+
case ("function"): {
|
|
95
|
+
return constraint(raw, "customFail", undefined);
|
|
96
|
+
}
|
|
97
|
+
case ("object"): {
|
|
98
|
+
return constraint(FC_NESTED, null, raw);
|
|
99
|
+
}
|
|
100
|
+
case ("symbol"): {
|
|
101
|
+
return raw;
|
|
102
|
+
}
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
}
|
|
106
|
+
errors.push(error("<schema>", "parsingError", undefined, raw));
|
|
107
|
+
return null;
|
|
103
108
|
}
|
|
104
109
|
|
|
105
|
-
let intermediate = unify(schemaProperty);
|
|
110
|
+
let intermediate = unify(schemaProperty) || [];
|
|
106
111
|
if (!Array.isArray(intermediate))
|
|
107
112
|
intermediate = [intermediate];
|
|
108
113
|
return [
|
|
@@ -135,7 +140,7 @@ function checkValue(propertyName, value, constraints, options){
|
|
|
135
140
|
const schemaKeys = Object.keys(constraint.expected);
|
|
136
141
|
|
|
137
142
|
for (let key of schemaKeys){
|
|
138
|
-
const [subConstraints, flags] = parseSchema(constraint.expected[key]);
|
|
143
|
+
const [subConstraints, flags] = parseSchema(constraint.expected[key], errors);
|
|
139
144
|
|
|
140
145
|
if (!targetKeys.includes(key)){
|
|
141
146
|
if (!flags.includes(OPTIONAL)){
|
|
@@ -163,7 +168,7 @@ function checkValue(propertyName, value, constraints, options){
|
|
|
163
168
|
break;
|
|
164
169
|
}
|
|
165
170
|
|
|
166
|
-
const [subConstraints, flags] = parseSchema(constraint.expected);
|
|
171
|
+
const [subConstraints, flags] = parseSchema(constraint.expected, errors);
|
|
167
172
|
|
|
168
173
|
let indexCounter = 0;
|
|
169
174
|
for (let item of value){
|
|
@@ -182,8 +187,10 @@ function checkValue(propertyName, value, constraints, options){
|
|
|
182
187
|
let counter = 0;
|
|
183
188
|
|
|
184
189
|
for (let subSchema of subSchemas){
|
|
185
|
-
const
|
|
190
|
+
const subParseErrors = [];
|
|
191
|
+
const [subConstraints, flags] = parseSchema(subSchema, subParseErrors);
|
|
186
192
|
const caseErrors = checkValue(`${propertyName}.<any#${counter}>`, value, subConstraints, options);
|
|
193
|
+
caseErrors.push(...subParseErrors);
|
|
187
194
|
++counter;
|
|
188
195
|
subErrors.push(caseErrors);
|
|
189
196
|
if (caseErrors.length == 0) {
|
|
@@ -204,7 +211,7 @@ function checkValue(propertyName, value, constraints, options){
|
|
|
204
211
|
break;
|
|
205
212
|
}
|
|
206
213
|
|
|
207
|
-
const [subConstraints, flags] = parseSchema(constraintCallback(value))
|
|
214
|
+
const [subConstraints, flags] = parseSchema(constraintCallback(value), errors)
|
|
208
215
|
const subErrors = checkValue(propertyName, value, subConstraints, options);
|
|
209
216
|
errors.push(...subErrors);
|
|
210
217
|
|
|
@@ -227,14 +234,15 @@ function checkValue(propertyName, value, constraints, options){
|
|
|
227
234
|
export function validate(target, schema = {}, options = {}){
|
|
228
235
|
options = Object.assign(VALIDATION_DEFAULTS, options)
|
|
229
236
|
|
|
230
|
-
const
|
|
237
|
+
const parseErrors = [];
|
|
238
|
+
const [constraints, flags] = parseSchema(schema, parseErrors);
|
|
231
239
|
|
|
232
240
|
if (flags.length > 0) {
|
|
233
241
|
console.error("Flags can't be used at schema's root")
|
|
234
242
|
}
|
|
235
243
|
|
|
236
244
|
const errors = checkValue("", target, constraints, options);
|
|
237
|
-
|
|
245
|
+
errors.push(...parseErrors)
|
|
238
246
|
errors.forEach(e => {
|
|
239
247
|
if (e.propertyName?.startsWith("."))
|
|
240
248
|
e.propertyName = e.propertyName.slice(1);
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arstotzka",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"description": "JS validation utility",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "
|
|
8
|
+
"test": "node tests.js"
|
|
9
9
|
},
|
|
10
10
|
"keywords": [
|
|
11
11
|
"JSON",
|
package/tests.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as Arstotzka from "./index.js";
|
|
2
|
+
|
|
3
|
+
const schema = {
|
|
4
|
+
title: "string",
|
|
5
|
+
array: [Arstotzka.ARRAY_OF("number"), x => x.length > 1],
|
|
6
|
+
arrayOfObjs: Arstotzka.ARRAY_OF({
|
|
7
|
+
id: "number",
|
|
8
|
+
name: "string"
|
|
9
|
+
}),
|
|
10
|
+
notArray: Arstotzka.ARRAY_OF("string"),
|
|
11
|
+
arrayOof: Arstotzka.ARRAY_OF(Arstotzka.ARRAY_OF("number")),
|
|
12
|
+
positiveArray: Arstotzka.ARRAY_OF(x => x > 0),
|
|
13
|
+
nested: {
|
|
14
|
+
parseableNumber: [x => !isNaN(parseInt(x, 10))],
|
|
15
|
+
anotherNest: [{
|
|
16
|
+
phrase: "string",
|
|
17
|
+
wordCount: "number"
|
|
18
|
+
}, x => x.phrase.split(" ").length == x.wordCount],
|
|
19
|
+
missing: []
|
|
20
|
+
},
|
|
21
|
+
invalidValidator: [x => null.invalid],
|
|
22
|
+
optional: ["number", Arstotzka.OPTIONAL]
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const testSubject0 = {
|
|
26
|
+
title: "1337",
|
|
27
|
+
array: [1, 2, 3],
|
|
28
|
+
arrayOfObjs: [
|
|
29
|
+
{id: 0, name: "_"},
|
|
30
|
+
{id: 1, name: "second"},
|
|
31
|
+
{id: 2, name: "3"},
|
|
32
|
+
],
|
|
33
|
+
notArray: [],
|
|
34
|
+
arrayOof: [
|
|
35
|
+
[1, 2, 3],
|
|
36
|
+
[4, 5, 6],
|
|
37
|
+
[1, 2]
|
|
38
|
+
],
|
|
39
|
+
positiveArray: [Infinity, 1, 4, 5],
|
|
40
|
+
nested: {
|
|
41
|
+
parseableNumber: "777",
|
|
42
|
+
anotherNest: {
|
|
43
|
+
phrase:"henlo",
|
|
44
|
+
wordCount: 1
|
|
45
|
+
},
|
|
46
|
+
missing: 0
|
|
47
|
+
},
|
|
48
|
+
invalidValidator: "uhm"
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const testSubject1 = {
|
|
52
|
+
title: 1337,
|
|
53
|
+
array: [1, null, 3],
|
|
54
|
+
arrayOfObjs: [
|
|
55
|
+
{id: 0, name: "_"},
|
|
56
|
+
{id: 0,},
|
|
57
|
+
{id: "0", name: "_"}
|
|
58
|
+
],
|
|
59
|
+
notArray: "[1, 2, 3]",
|
|
60
|
+
arrayOof: [
|
|
61
|
+
[1, 2, 3],
|
|
62
|
+
[4, "5", 6],
|
|
63
|
+
null,
|
|
64
|
+
[1, 2]
|
|
65
|
+
],
|
|
66
|
+
positiveArray: [0, 1, 4, -5],
|
|
67
|
+
nested: {
|
|
68
|
+
parseableNumber: "seven",
|
|
69
|
+
extraProperty: "hey",
|
|
70
|
+
anotherNest: {
|
|
71
|
+
phrase:"henlo",
|
|
72
|
+
wordCount: 2
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
invalidValidator: "uhm"
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const schema1 = Arstotzka.DYNAMIC(x => dynSchema[x.type]);
|
|
79
|
+
const dynSchema = [
|
|
80
|
+
{
|
|
81
|
+
type: "number",
|
|
82
|
+
zero: "string"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "number",
|
|
86
|
+
one: ["number", x => x === 1]
|
|
87
|
+
}
|
|
88
|
+
];
|
|
89
|
+
const testSubject2 = {
|
|
90
|
+
type: 0,
|
|
91
|
+
zero: "0"
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const parseableIntSchema = Arstotzka.ANY_OF(
|
|
95
|
+
"number",
|
|
96
|
+
["string", x => !isNaN(parseInt(x))]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// [target, schema, error count]
|
|
100
|
+
const tests = [
|
|
101
|
+
[null, "array", 1], // null values and non-object constraints
|
|
102
|
+
[testSubject0, schema, 1], // Only an error caused by invalid validator
|
|
103
|
+
[testSubject1, schema, 13], // Full of errors
|
|
104
|
+
["miles", "string", 0], // Any value can be validated, not only objects
|
|
105
|
+
[7, "string", 1], // Same but failed
|
|
106
|
+
[testSubject2, schema1, 0], // Dynamic schema
|
|
107
|
+
["7", parseableIntSchema, 0], // Schema with ANY_OF
|
|
108
|
+
[testSubject0, null, 1] // Invalid schema
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const testResults = tests
|
|
112
|
+
.map(t => Arstotzka.validate(t[0], t[1]).length == t[2])
|
|
113
|
+
.map((testPassed, i) => `Test ${i}: ${testPassed ? "PASSED" : "FAILED"}`)
|
|
114
|
+
.join("\n");
|
|
115
|
+
console.log(testResults);
|
package/usage.js
CHANGED
|
@@ -1,28 +1,7 @@
|
|
|
1
1
|
import * as Arstotzka from "./index.js";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
array: [Arstotzka.ARRAY_OF("number"), x => x.length > 1],
|
|
6
|
-
arrayOfObjs: Arstotzka.ARRAY_OF({
|
|
7
|
-
id: "number",
|
|
8
|
-
name: "string"
|
|
9
|
-
}),
|
|
10
|
-
notArray: Arstotzka.ARRAY_OF("string"),
|
|
11
|
-
arrayOof: Arstotzka.ARRAY_OF(Arstotzka.ARRAY_OF("number")),
|
|
12
|
-
positiveArray: Arstotzka.ARRAY_OF(x => x > 0),
|
|
13
|
-
nested: {
|
|
14
|
-
parseableNumber: [x => !isNaN(parseInt(x, 10))],
|
|
15
|
-
anotherNest: [{
|
|
16
|
-
phrase: "string",
|
|
17
|
-
wordCount: "number"
|
|
18
|
-
}, x => x.phrase.split(" ").length == x.wordCount],
|
|
19
|
-
missing: []
|
|
20
|
-
},
|
|
21
|
-
invalidValidator: [x => null.invalid],
|
|
22
|
-
optional: ["number", Arstotzka.OPTIONAL]
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const testSubject0 = {
|
|
3
|
+
// This value will be validated against schema described below
|
|
4
|
+
const value = {
|
|
26
5
|
title: "1337",
|
|
27
6
|
array: [1, 2, 3],
|
|
28
7
|
arrayOfObjs: [
|
|
@@ -30,81 +9,72 @@ const testSubject0 = {
|
|
|
30
9
|
{id: 1, name: "second"},
|
|
31
10
|
{id: 2, name: "3"},
|
|
32
11
|
],
|
|
33
|
-
|
|
34
|
-
arrayOof: [
|
|
12
|
+
matrix: [
|
|
35
13
|
[1, 2, 3],
|
|
36
14
|
[4, 5, 6],
|
|
37
|
-
[1, 2]
|
|
15
|
+
[1, 2, 0]
|
|
38
16
|
],
|
|
39
17
|
positiveArray: [Infinity, 1, 4, 5],
|
|
40
18
|
nested: {
|
|
41
19
|
parseableNumber: "777",
|
|
42
20
|
anotherNest: {
|
|
43
|
-
phrase:"henlo",
|
|
44
|
-
wordCount:
|
|
21
|
+
phrase:"henlo there",
|
|
22
|
+
wordCount: 2
|
|
45
23
|
},
|
|
46
|
-
|
|
24
|
+
justBeThere: 0
|
|
47
25
|
},
|
|
48
|
-
|
|
26
|
+
booleanOrNumber: 0,
|
|
27
|
+
dynamic: 69
|
|
49
28
|
};
|
|
50
29
|
|
|
51
|
-
const
|
|
52
|
-
title
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
30
|
+
const schema = { // Require value to be an object
|
|
31
|
+
// Require typeof value.title to be "string"
|
|
32
|
+
title: "string",
|
|
33
|
+
|
|
34
|
+
//Require value.array to be an array of numbers AND to be longer than 1 element
|
|
35
|
+
array: [Arstotzka.ARRAY_OF("number"), x => x.length > 1],
|
|
36
|
+
|
|
37
|
+
// Require each element of value.arrayOfObjs to be valid according to provided schema
|
|
38
|
+
arrayOfObjs: Arstotzka.ARRAY_OF({
|
|
39
|
+
id: "number",
|
|
40
|
+
name: "string"
|
|
41
|
+
}),
|
|
42
|
+
|
|
43
|
+
// Require each element of value.matrix to be an array of numbers
|
|
44
|
+
matrix: Arstotzka.ARRAY_OF(Arstotzka.ARRAY_OF("number")),
|
|
45
|
+
|
|
46
|
+
// Require each element of value.positiveArray be larger than zero, regardless of type
|
|
47
|
+
positiveArray: Arstotzka.ARRAY_OF(x => x > 0),
|
|
48
|
+
|
|
49
|
+
// Require value.nested to be an object
|
|
67
50
|
nested: {
|
|
68
|
-
parseableNumber
|
|
69
|
-
|
|
70
|
-
anotherNest
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
51
|
+
// Require value.nested.parseableNumber to make this function return true
|
|
52
|
+
parseableNumber: x => !isNaN(parseInt(x, 10)),
|
|
53
|
+
// Require value.nested.anotherNest to be an object AND make provided function return true
|
|
54
|
+
anotherNest: [{
|
|
55
|
+
phrase: "string",
|
|
56
|
+
wordCount: "number"
|
|
57
|
+
}, x => x.phrase.split(" ").length == x.wordCount],
|
|
58
|
+
// Require value.nested.justBeThere to be present
|
|
59
|
+
justBeThere: []
|
|
74
60
|
},
|
|
75
|
-
invalidValidator: "uhm"
|
|
76
|
-
};
|
|
77
61
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
one: ["number", x => x === 1]
|
|
87
|
-
}
|
|
88
|
-
];
|
|
89
|
-
const testSubject2 = {
|
|
90
|
-
type: 0,
|
|
91
|
-
zero: "0"
|
|
62
|
+
// Require value.optional to be of type "number", but only if present
|
|
63
|
+
optional: ["number", Arstotzka.OPTIONAL],
|
|
64
|
+
|
|
65
|
+
// Require value.booleanOrNumber to be either boolean, or 0 or 1
|
|
66
|
+
booleanOrNumber: Arstotzka.ANY_OF("boolean", x => x === 0 || x === 1),
|
|
67
|
+
|
|
68
|
+
// Require value.dynamic to be valid according to schema returned from provided callback (always "number" in that case)
|
|
69
|
+
dynamic: Arstotzka.DYNAMIC(x => "number")
|
|
92
70
|
};
|
|
93
71
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
72
|
+
// You can call validate() without providing any options. Below are all options with default values
|
|
73
|
+
const optionalValidationOptions = {
|
|
74
|
+
allowExtraProperties: true,
|
|
75
|
+
allErrors: true
|
|
76
|
+
};
|
|
98
77
|
|
|
99
|
-
const
|
|
100
|
-
[null, "array"],
|
|
101
|
-
[testSubject0, schema], // Only an error caused by invalid validator
|
|
102
|
-
[testSubject1, schema], // Full of errors
|
|
103
|
-
["miles", "string"], // Any value can be validated, not only objects
|
|
104
|
-
[7, "string"],
|
|
105
|
-
[testSubject2, schema1],
|
|
106
|
-
["7", parseableIntSchema]
|
|
107
|
-
];
|
|
78
|
+
const errors = Arstotzka.validate(value, schema, optionalValidationOptions);
|
|
108
79
|
|
|
109
|
-
|
|
110
|
-
console.log(Arstotzka.validate(tests[TEST_SELECTOR][0], tests[TEST_SELECTOR][1], {allowExtraProperties: false}));
|
|
80
|
+
console.log(errors); // Empty, meaning validation is passed
|