@jarenjs/validate 0.9.2 → 0.34.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/ARCHITECTURE.md +81 -17
- package/README.md +462 -5
- package/dist/types/dollar-data.d.ts +0 -9
- package/dist/types/index.d.ts +167 -69
- package/dist/types/messages.d.ts +142 -0
- package/dist/types/normalize.d.ts +107 -0
- package/dist/types/tools.d.ts +58 -0
- package/docs/ERROR-MESSAGES.md +251 -0
- package/package.json +9 -4
- package/src/array.js +68 -23
- package/src/bigint.js +18 -7
- package/src/combine.js +61 -11
- package/src/condition.js +34 -14
- package/src/content.js +3 -3
- package/src/data.js +11 -387
- package/src/dollar-data.js +55 -472
- package/src/enum.js +0 -1
- package/src/format.js +46 -4
- package/src/index.js +280 -238
- package/src/messages.js +497 -0
- package/src/normalize.js +585 -0
- package/src/number.js +19 -9
- package/src/object.js +126 -33
- package/src/query.js +28 -2
- package/src/schema.js +72 -27
- package/src/string.js +18 -6
- package/src/tools.js +192 -0
- package/src/traverse.js +13 -4
- package/src/unevaluated.js +27 -5
package/src/combine.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '@jarenjs/core/function';
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
|
+
combineIndependent,
|
|
13
14
|
getBoolOrObjectClass,
|
|
14
15
|
getArrayClassMinItems,
|
|
15
16
|
} from './tools.js';
|
|
@@ -34,14 +35,20 @@ function compileAllOf(schemaObj, jsonSchema) {
|
|
|
34
35
|
|
|
35
36
|
const addError = schemaObj.createErrorHandler(allOf, 'allOf');
|
|
36
37
|
|
|
38
|
+
const stopAtFirst = schemaObj.options.skipErrors;
|
|
37
39
|
return function validateAllOf(data, dataPath, dataRoot, dataKey) {
|
|
38
40
|
if (data !== undefined) {
|
|
41
|
+
// EVERY allOf branch applies, so every branch's faults are real. Only
|
|
42
|
+
// the boolean answer may stop at the first failing branch.
|
|
43
|
+
let valid = true;
|
|
39
44
|
for (let i = 0; i < validators.length; ++i) {
|
|
40
45
|
const validator = validators[i];
|
|
41
46
|
if (validator(data, dataPath, dataRoot, dataKey) === false) {
|
|
42
|
-
|
|
47
|
+
valid = addError(data, dataPath);
|
|
48
|
+
if (stopAtFirst) return valid;
|
|
43
49
|
}
|
|
44
50
|
}
|
|
51
|
+
return valid;
|
|
45
52
|
}
|
|
46
53
|
return true;
|
|
47
54
|
};
|
|
@@ -75,6 +82,7 @@ function compileAnyOf(schemaObj, jsonSchema) {
|
|
|
75
82
|
return function validateAnyOfTracked(data, dataPath, dataRoot, dataKey) {
|
|
76
83
|
if (data === undefined) return true;
|
|
77
84
|
const log = root.evalLog;
|
|
85
|
+
const errors = root.errorMark();
|
|
78
86
|
let found = false;
|
|
79
87
|
for (let i = 0; i < validators.length; ++i) {
|
|
80
88
|
const mark = log.mark();
|
|
@@ -83,15 +91,24 @@ function compileAnyOf(schemaObj, jsonSchema) {
|
|
|
83
91
|
else
|
|
84
92
|
log.rollback(mark);
|
|
85
93
|
}
|
|
94
|
+
// Annotation tracking runs every branch, so a match leaves the failed
|
|
95
|
+
// branches' errors behind unless they are rolled back here too.
|
|
96
|
+
if (found) root.rollbackErrors(errors);
|
|
86
97
|
return found || addError(data, dataPath);
|
|
87
98
|
};
|
|
88
99
|
}
|
|
89
100
|
|
|
90
101
|
return function validateAnyOf(data, dataPath, dataRoot, dataKey) {
|
|
91
102
|
if (data !== undefined) {
|
|
103
|
+
// Every branch tried before a match is a SPECULATIVE probe: the document
|
|
104
|
+
// was never required to satisfy it. Its errors are kept only if no
|
|
105
|
+
// branch matched at all, where they are the explanation.
|
|
106
|
+
const mark = root.errorMark();
|
|
92
107
|
for (let i = 0; i < validators.length; ++i) {
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
if (validators[i](data, dataPath, dataRoot, dataKey) === true) {
|
|
109
|
+
root.rollbackErrors(mark);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
95
112
|
}
|
|
96
113
|
return addError(data, dataPath);
|
|
97
114
|
}
|
|
@@ -126,32 +143,43 @@ function compileOneOf(schemaObj, jsonSchema) {
|
|
|
126
143
|
if (root.usesUnevaluated) {
|
|
127
144
|
return function validateOneOfTracked(data, dataPath, dataRoot, dataKey) {
|
|
128
145
|
const log = root.evalLog;
|
|
146
|
+
const errors = root.errorMark();
|
|
129
147
|
let found = false;
|
|
130
148
|
for (let i = 0; i < validators.length; ++i) {
|
|
131
149
|
const mark = log.mark();
|
|
132
150
|
if (validators[i](data, dataPath, dataRoot, dataKey) === true) {
|
|
133
|
-
if (found === true)
|
|
151
|
+
if (found === true) {
|
|
152
|
+
root.rollbackErrors(errors);
|
|
134
153
|
return addError(data, dataPath);
|
|
154
|
+
}
|
|
135
155
|
found = true;
|
|
136
156
|
}
|
|
137
157
|
else {
|
|
138
158
|
log.rollback(mark);
|
|
139
159
|
}
|
|
140
160
|
}
|
|
161
|
+
// Non-matching branches were speculative; their complaints survive only
|
|
162
|
+
// when nothing matched and they are the explanation.
|
|
163
|
+
if (found) root.rollbackErrors(errors);
|
|
141
164
|
return found || addError(data, dataPath);
|
|
142
165
|
};
|
|
143
166
|
}
|
|
144
167
|
|
|
145
168
|
return function validateOneOf(data, dataPath, dataRoot, dataKey) {
|
|
169
|
+
const errors = root.errorMark();
|
|
146
170
|
let found = false;
|
|
147
171
|
for (let i = 0; i < validators.length; ++i) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
172
|
+
if (validators[i](data, dataPath, dataRoot, dataKey) === true) {
|
|
173
|
+
if (found === true) {
|
|
174
|
+
// Matching twice is the failure; the branches' own errors explain
|
|
175
|
+
// nothing about it.
|
|
176
|
+
root.rollbackErrors(errors);
|
|
151
177
|
return addError(data, dataPath);
|
|
178
|
+
}
|
|
152
179
|
found = true;
|
|
153
180
|
}
|
|
154
181
|
}
|
|
182
|
+
if (found) root.rollbackErrors(errors);
|
|
155
183
|
return found || addError(data, dataPath);
|
|
156
184
|
};
|
|
157
185
|
}
|
|
@@ -159,13 +187,21 @@ function compileOneOf(schemaObj, jsonSchema) {
|
|
|
159
187
|
function compileNotOf(schemaObj, jsonSchema) {
|
|
160
188
|
const notOf = getBoolOrObjectClass(jsonSchema.not);
|
|
161
189
|
if (notOf == null) return undefined;
|
|
162
|
-
if (notOf === true) return falseThat;
|
|
163
190
|
if (notOf === false) return trueThat;
|
|
164
191
|
|
|
165
|
-
const validate = schemaObj.createValidator(notOf, 'not');
|
|
166
|
-
|
|
167
192
|
const addError = schemaObj.createErrorHandler(notOf, 'not');
|
|
168
193
|
|
|
194
|
+
// `not: true` rejects every instance. It still has to SAY so: returning a
|
|
195
|
+
// bare false made the only schema that can fail without explanation, and a
|
|
196
|
+
// collector that reports "invalid" with an empty error list is unusable by
|
|
197
|
+
// a caller trying to render why.
|
|
198
|
+
if (notOf === true)
|
|
199
|
+
return function validateNotOfTrue(data, dataPath) {
|
|
200
|
+
return data === undefined ? true : addError(data, dataPath);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const validate = schemaObj.createValidator(notOf, 'not');
|
|
204
|
+
|
|
169
205
|
// Annotations produced inside a 'not' are never kept, whatever the outcome.
|
|
170
206
|
const root = schemaObj.root;
|
|
171
207
|
if (root.usesUnevaluated) {
|
|
@@ -173,8 +209,12 @@ function compileNotOf(schemaObj, jsonSchema) {
|
|
|
173
209
|
if (data === undefined) return true;
|
|
174
210
|
const log = root.evalLog;
|
|
175
211
|
const mark = log.mark();
|
|
212
|
+
const errors = root.errorMark();
|
|
176
213
|
const valid = validate(data, dataPath, dataRoot, dataKey);
|
|
177
214
|
log.rollback(mark);
|
|
215
|
+
// A `not` whose subschema FAILS is a `not` that passed, so the
|
|
216
|
+
// subschema's complaints are never the caller's business either way.
|
|
217
|
+
root.rollbackErrors(errors);
|
|
178
218
|
return valid === false
|
|
179
219
|
? true
|
|
180
220
|
: addError(data, dataPath);
|
|
@@ -183,7 +223,12 @@ function compileNotOf(schemaObj, jsonSchema) {
|
|
|
183
223
|
|
|
184
224
|
return function validateNotOf(data, dataPath, dataRoot, dataKey) {
|
|
185
225
|
if (data === undefined) return true;
|
|
186
|
-
|
|
226
|
+
// The subschema is a probe: its failure is what makes `not` succeed, so
|
|
227
|
+
// its errors are discarded whichever way the probe went.
|
|
228
|
+
const errors = root.errorMark();
|
|
229
|
+
const valid = validate(data, dataPath, dataRoot, dataKey);
|
|
230
|
+
root.rollbackErrors(errors);
|
|
231
|
+
return valid === false
|
|
187
232
|
? true
|
|
188
233
|
: addError(data, dataPath);
|
|
189
234
|
};
|
|
@@ -205,6 +250,11 @@ export function compileCombineSchema(schemaObj, jsonSchema) {
|
|
|
205
250
|
return undefined;
|
|
206
251
|
if (validators.length === 1)
|
|
207
252
|
return validators[0];
|
|
253
|
+
// allOf/anyOf/oneOf/not are independent applicator groups: a failing
|
|
254
|
+
// `oneOf` says nothing about whether `not` also failed.
|
|
255
|
+
if (!schemaObj.options.skipErrors)
|
|
256
|
+
return combineIndependent(validators);
|
|
257
|
+
|
|
208
258
|
if (validators.length === 2) {
|
|
209
259
|
const first = validators[0];
|
|
210
260
|
const second = validators[1];
|
package/src/condition.js
CHANGED
|
@@ -27,8 +27,13 @@ export function compileConditionSchema(schemaObj, jsonSchema) {
|
|
|
27
27
|
return function validateLoneIf(data, dataPath, dataRoot, dataKey) {
|
|
28
28
|
const log = root.evalLog;
|
|
29
29
|
const mark = log.mark();
|
|
30
|
+
// A lone `if` asserts NOTHING, so it can never explain a failure and
|
|
31
|
+
// its errors are always discarded. Only its annotations survive, and
|
|
32
|
+
// only when it passed.
|
|
33
|
+
const errors = root.errorMark();
|
|
30
34
|
if (validateIf(data, dataPath, dataRoot, dataKey) === false)
|
|
31
35
|
log.rollback(mark);
|
|
36
|
+
root.rollbackErrors(errors);
|
|
32
37
|
return true;
|
|
33
38
|
};
|
|
34
39
|
}
|
|
@@ -56,9 +61,13 @@ export function compileConditionSchema(schemaObj, jsonSchema) {
|
|
|
56
61
|
return function validateConditionTracked(data, dataPath, dataRoot, dataKey) {
|
|
57
62
|
const log = root.evalLog;
|
|
58
63
|
const mark = log.mark();
|
|
59
|
-
if
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
// `if` decides WHICH branch applies; the document was never required to
|
|
65
|
+
// satisfy it, so its complaints are discarded either way. Only `then`
|
|
66
|
+
// and `else` produce errors a caller should see.
|
|
67
|
+
const errors = root.errorMark();
|
|
68
|
+
const taken = validateIf(data, dataPath, dataRoot, dataKey);
|
|
69
|
+
root.rollbackErrors(errors);
|
|
70
|
+
if (taken) return validateThen(data, dataPath, dataRoot, dataKey);
|
|
62
71
|
log.rollback(mark);
|
|
63
72
|
return validateElse(data, dataPath, dataRoot, dataKey);
|
|
64
73
|
};
|
|
@@ -66,30 +75,41 @@ export function compileConditionSchema(schemaObj, jsonSchema) {
|
|
|
66
75
|
|
|
67
76
|
// If neither then nor else has dynamic anchors, use simple validation
|
|
68
77
|
if (!thenHasAnchor && !elseHasAnchor) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
// Every call site passes (data, dataPath, dataRoot, dataKey). Declaring
|
|
79
|
+
// fewer parameters does not drop the extra arguments, it MISBINDS them:
|
|
80
|
+
// a two-parameter form named its second parameter `dataRoot` and was
|
|
81
|
+
// handed the dataPath, so the branch validators ran with no root at all
|
|
82
|
+
// and a `$data` reference inside `then` silently resolved to nothing —
|
|
83
|
+
// a wrong verdict rather than an error.
|
|
84
|
+
return function validateCondition(data, dataPath, dataRoot, dataKey) {
|
|
85
|
+
const errors = root.errorMark();
|
|
86
|
+
const taken = validateIf(data, dataPath, dataRoot, dataKey);
|
|
87
|
+
root.rollbackErrors(errors);
|
|
88
|
+
return taken
|
|
89
|
+
? validateThen(data, dataPath, dataRoot, dataKey)
|
|
90
|
+
: validateElse(data, dataPath, dataRoot, dataKey);
|
|
74
91
|
};
|
|
75
92
|
}
|
|
76
93
|
|
|
77
94
|
// If then or else has dynamic anchors, wrap validation to register them
|
|
78
95
|
const track = root.usesUnevaluated;
|
|
79
|
-
return function validateConditionWithDynamicAnchors(data, dataPath, dataRoot) {
|
|
96
|
+
return function validateConditionWithDynamicAnchors(data, dataPath, dataRoot, dataKey) {
|
|
80
97
|
const mark = track ? root.evalLog.mark() : 0;
|
|
81
|
-
|
|
98
|
+
const conditionErrors = root.errorMark();
|
|
99
|
+
const taken = validateIf(data, dataPath, dataRoot, dataKey);
|
|
100
|
+
root.rollbackErrors(conditionErrors);
|
|
101
|
+
if (taken) {
|
|
82
102
|
// Validate then branch with dynamic anchor registration
|
|
83
103
|
if (thenHasAnchor && tmpThen) {
|
|
84
104
|
const anchorName = thenDynAnchorName || '';
|
|
85
105
|
root.pushDynamicAnchorValidator(anchorName, tmpThen);
|
|
86
106
|
try {
|
|
87
|
-
return validateThen(data, dataRoot);
|
|
107
|
+
return validateThen(data, dataPath, dataRoot, dataKey);
|
|
88
108
|
} finally {
|
|
89
109
|
root.popDynamicAnchorValidator(anchorName);
|
|
90
110
|
}
|
|
91
111
|
}
|
|
92
|
-
return validateThen(data, dataRoot);
|
|
112
|
+
return validateThen(data, dataPath, dataRoot, dataKey);
|
|
93
113
|
} else {
|
|
94
114
|
// Annotations from the failed 'if' must not leak to unevaluated*
|
|
95
115
|
if (track) root.evalLog.rollback(mark);
|
|
@@ -98,12 +118,12 @@ export function compileConditionSchema(schemaObj, jsonSchema) {
|
|
|
98
118
|
const anchorName = elseDynAnchorName || '';
|
|
99
119
|
root.pushDynamicAnchorValidator(anchorName, tmpElse);
|
|
100
120
|
try {
|
|
101
|
-
return validateElse(data, dataRoot);
|
|
121
|
+
return validateElse(data, dataPath, dataRoot, dataKey);
|
|
102
122
|
} finally {
|
|
103
123
|
root.popDynamicAnchorValidator(anchorName);
|
|
104
124
|
}
|
|
105
125
|
}
|
|
106
|
-
return validateElse(data, dataRoot);
|
|
126
|
+
return validateElse(data, dataPath, dataRoot, dataKey);
|
|
107
127
|
}
|
|
108
128
|
};
|
|
109
129
|
}
|
package/src/content.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from '@jarenjs/core';
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
isValidBase64,
|
|
8
8
|
} from '@jarenjs/core/text';
|
|
9
9
|
|
|
10
10
|
import {
|
|
@@ -21,7 +21,7 @@ export function compileContentEncoding(schemaObj, jsonSchema) {
|
|
|
21
21
|
return function validateBase64(data, dataPath) {
|
|
22
22
|
// contentEncoding only applies to strings - ignore non-strings
|
|
23
23
|
if (!isStringType(data)) return true;
|
|
24
|
-
return
|
|
24
|
+
return isValidBase64(data)
|
|
25
25
|
|| addError(data, dataPath);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
@@ -50,7 +50,7 @@ export function compileContentMediaType(schemaObj, jsonSchema) {
|
|
|
50
50
|
return function validateBase64JsonContent(data, dataPath) {
|
|
51
51
|
if (!isStringType(data)) return true;
|
|
52
52
|
// Check valid base64 first before decoding
|
|
53
|
-
if (!
|
|
53
|
+
if (!isValidBase64(data)) return addError(data, dataPath);
|
|
54
54
|
const decoded = Buffer.from(data, 'base64').toString('utf8');
|
|
55
55
|
return isValidJSON(decoded) || addError(data, dataPath);
|
|
56
56
|
}
|