@jarenjs/validate 0.9.2 → 0.34.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/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
- return addError(data, dataPath);
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
- const validator = validators[i]; // TODO: how to silence errors of children?
94
- if (validator(data, dataPath, dataRoot, dataKey) === true) return true;
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
- const validator = validators[i];
149
- if (validator(data, dataPath, dataRoot, dataKey) === true) {
150
- if (found === true)
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
- return validate(data, dataPath, dataRoot, dataKey) === false
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 (validateIf(data, dataPath, dataRoot, dataKey)) {
60
- return validateThen(data, dataPath, dataRoot, dataKey);
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
- return function validateCondition(data, dataRoot) {
70
- if (validateIf(data))
71
- return validateThen(data, dataRoot);
72
- else
73
- return validateElse(data, dataRoot);
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
- if (validateIf(data)) {
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
- isValidBase64Fast,
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 isValidBase64Fast(data)
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 (!isValidBase64Fast(data)) return addError(data, dataPath);
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
  }