@jarenjs/validate 0.8.4 → 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.
Files changed (53) hide show
  1. package/ARCHITECTURE.md +1131 -0
  2. package/LICENSE +21 -0
  3. package/README.md +796 -2
  4. package/dist/types/array.d.ts +2 -0
  5. package/dist/types/bigint.d.ts +1 -0
  6. package/dist/types/combine.d.ts +1 -0
  7. package/dist/types/condition.d.ts +1 -0
  8. package/dist/types/content.d.ts +3 -0
  9. package/dist/types/data.d.ts +7 -0
  10. package/dist/types/dollar-data.d.ts +11 -0
  11. package/dist/types/dynamic-ref.d.ts +44 -0
  12. package/dist/types/enum.d.ts +1 -0
  13. package/dist/types/format.d.ts +21 -0
  14. package/dist/types/index.d.ts +972 -0
  15. package/dist/types/messages.d.ts +142 -0
  16. package/dist/types/normalize.d.ts +107 -0
  17. package/dist/types/number.d.ts +1 -0
  18. package/dist/types/object.d.ts +3 -0
  19. package/dist/types/query-keyword.d.ts +19 -0
  20. package/dist/types/query.d.ts +29 -0
  21. package/dist/types/schema.d.ts +1 -0
  22. package/dist/types/string.d.ts +1 -0
  23. package/dist/types/tools.d.ts +109 -0
  24. package/dist/types/traverse.d.ts +32 -0
  25. package/dist/types/unevaluated.d.ts +12 -0
  26. package/docs/ERROR-MESSAGES.md +251 -0
  27. package/package.json +37 -7
  28. package/src/array.js +610 -0
  29. package/src/bigint.js +108 -0
  30. package/src/combine.js +276 -0
  31. package/src/condition.js +129 -0
  32. package/src/content.js +83 -0
  33. package/src/data.js +101 -0
  34. package/src/dollar-data.js +212 -0
  35. package/src/dynamic-ref.js +121 -0
  36. package/src/enum.js +147 -0
  37. package/src/format.js +108 -0
  38. package/src/index.js +1896 -0
  39. package/src/messages.js +497 -0
  40. package/src/normalize.js +585 -0
  41. package/src/number.js +169 -0
  42. package/src/object.js +848 -0
  43. package/src/query-keyword.js +99 -0
  44. package/src/query.js +85 -0
  45. package/src/schema.js +690 -0
  46. package/src/string.js +164 -0
  47. package/src/tools.js +397 -0
  48. package/src/traverse.js +442 -0
  49. package/src/unevaluated.js +173 -0
  50. package/dist/index.js +0 -1998
  51. package/dist/index.js.map +0 -7
  52. package/dist/index.min.js +0 -2
  53. package/dist/index.min.js.map +0 -7
package/src/bigint.js ADDED
@@ -0,0 +1,108 @@
1
+ //@ts-check
2
+
3
+ import {
4
+ isBigIntType,
5
+ getBigIntType,
6
+ getInclusiveExclusiveBounds,
7
+ } from '@jarenjs/core';
8
+
9
+ import {
10
+ trueThat,
11
+ } from '@jarenjs/core/function';
12
+
13
+ function compileBigIntMaximum(schemaObj, jsonSchema) {
14
+ const [max, emax] = getInclusiveExclusiveBounds(
15
+ getBigIntType,
16
+ jsonSchema.maximum,
17
+ jsonSchema.exclusiveMaximum,
18
+ );
19
+
20
+ if (emax != null) {
21
+ const addError = schemaObj.createErrorHandler(emax, 'exclusiveMaximum');
22
+
23
+ return function validateExclusiveMaximumBigInt(data, dataPath) {
24
+ return data < emax
25
+ || addError(data, dataPath);
26
+ };
27
+ }
28
+ else if (max != null) {
29
+ const addError = schemaObj.createErrorHandler(max, 'maximum');
30
+
31
+ return function validateMaximumBigInt(data, dataPath) {
32
+ return data <= max
33
+ || addError(data, dataPath);
34
+ };
35
+ }
36
+
37
+ return undefined;
38
+ }
39
+
40
+ function compileBigIntMinimum(schemaObj, jsonSchema) {
41
+ const [min, emin] = getInclusiveExclusiveBounds(
42
+ getBigIntType,
43
+ jsonSchema.minimum,
44
+ jsonSchema.exclusiveMinimum,
45
+ );
46
+
47
+ if (emin != null) {
48
+ const addError = schemaObj.createErrorHandler(emin, 'exclusiveMinimum');
49
+
50
+ return function validateExclusiveMinimumBigInt(data, dataPath) {
51
+ return data > emin
52
+ || addError(data, dataPath);
53
+ };
54
+ }
55
+ else if (min != null) {
56
+ const addError = schemaObj.createErrorHandler(min, 'minimum');
57
+
58
+ return function validateMinimumBigInt(data, dataPath) {
59
+ return data >= min
60
+ || addError(data, dataPath);
61
+ };
62
+ }
63
+
64
+ return undefined;
65
+ }
66
+
67
+ function compileBigIntMultipleOf(schemaObj, jsonSchema) {
68
+ const mulOf = getBigIntType(jsonSchema.multipleOf);
69
+ if (mulOf == null) return undefined;
70
+
71
+ const addError = schemaObj.createErrorHandler(mulOf, 'multipleOf');
72
+
73
+ return function validateMultipleOfBigInt(data, dataPath) {
74
+ return data % mulOf === BigInt(0)
75
+ || addError(data, dataPath);
76
+ };
77
+ }
78
+
79
+ export function compileBigIntBasic(schemaObj, jsonSchema) {
80
+ const maximum = compileBigIntMaximum(schemaObj, jsonSchema);
81
+ const minimum = compileBigIntMinimum(schemaObj, jsonSchema);
82
+ const multipleOf = compileBigIntMultipleOf(schemaObj, jsonSchema);
83
+ if (maximum == null && minimum == null && multipleOf == null) return undefined;
84
+
85
+ const isMax = maximum || trueThat;
86
+ const isMin = minimum || trueThat;
87
+ const isMul = multipleOf || trueThat;
88
+
89
+ if (schemaObj.options.skipErrors) {
90
+ return function validateBigIntSchema(data, dataPath) {
91
+ if (isBigIntType(data)) {
92
+ return isMax(data, dataPath)
93
+ && isMin(data, dataPath)
94
+ && isMul(data, dataPath);
95
+ }
96
+ return true;
97
+ };
98
+ }
99
+
100
+ // The type guard stays a precondition; the three assertions inside it are
101
+ // independent and each must get to report its own fault.
102
+ return function validateBigIntSchemaAll(data, dataPath) {
103
+ if (!isBigIntType(data)) return true;
104
+ let valid = isMax(data, dataPath);
105
+ valid = isMin(data, dataPath) && valid;
106
+ return isMul(data, dataPath) && valid;
107
+ };
108
+ }
package/src/combine.js ADDED
@@ -0,0 +1,276 @@
1
+ //@ts-check
2
+
3
+ import {
4
+ isFn,
5
+ } from '@jarenjs/core';
6
+
7
+ import {
8
+ falseThat,
9
+ trueThat,
10
+ } from '@jarenjs/core/function';
11
+
12
+ import {
13
+ combineIndependent,
14
+ getBoolOrObjectClass,
15
+ getArrayClassMinItems,
16
+ } from './tools.js';
17
+
18
+ function compileAllOf(schemaObj, jsonSchema) {
19
+ const allOf = getArrayClassMinItems(jsonSchema.allOf, 1);
20
+ if (allOf == null) return undefined;
21
+
22
+ const validators = [];
23
+ for (let i = 0; i < allOf.length; ++i) {
24
+ const child = allOf[i];
25
+ if (child === true)
26
+ validators.push(trueThat);
27
+ else if (child === false)
28
+ validators.push(falseThat);
29
+ else {
30
+ const validator = schemaObj.createValidator(child, 'allOf', i);
31
+ validators.push(validator);
32
+ }
33
+ }
34
+ if (validators.length === 0) return undefined;
35
+
36
+ const addError = schemaObj.createErrorHandler(allOf, 'allOf');
37
+
38
+ const stopAtFirst = schemaObj.options.skipErrors;
39
+ return function validateAllOf(data, dataPath, dataRoot, dataKey) {
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;
44
+ for (let i = 0; i < validators.length; ++i) {
45
+ const validator = validators[i];
46
+ if (validator(data, dataPath, dataRoot, dataKey) === false) {
47
+ valid = addError(data, dataPath);
48
+ if (stopAtFirst) return valid;
49
+ }
50
+ }
51
+ return valid;
52
+ }
53
+ return true;
54
+ };
55
+ }
56
+
57
+ function compileAnyOf(schemaObj, jsonSchema) {
58
+ const anyOf = getArrayClassMinItems(jsonSchema.anyOf, 1);
59
+ if (anyOf == null) return undefined;
60
+
61
+ const validators = [];
62
+ for (let i = 0; i < anyOf.length; ++i) {
63
+ const child = anyOf[i];
64
+ if (child === true)
65
+ validators.push(trueThat);
66
+ else if (child === false)
67
+ validators.push(falseThat);
68
+ else {
69
+ const validator = schemaObj.createValidator(child, 'anyOf', i);
70
+ validators.push(validator);
71
+ }
72
+ }
73
+ if (validators.length === 0) return undefined;
74
+
75
+ const addError = schemaObj.createErrorHandler(anyOf, 'anyOf');
76
+
77
+ // With annotation tracking every branch must run (annotations from ALL
78
+ // successful branches count for unevaluated*), and annotations from
79
+ // failed branches are rolled back.
80
+ const root = schemaObj.root;
81
+ if (root.usesUnevaluated) {
82
+ return function validateAnyOfTracked(data, dataPath, dataRoot, dataKey) {
83
+ if (data === undefined) return true;
84
+ const log = root.evalLog;
85
+ const errors = root.errorMark();
86
+ let found = false;
87
+ for (let i = 0; i < validators.length; ++i) {
88
+ const mark = log.mark();
89
+ if (validators[i](data, dataPath, dataRoot, dataKey) === true)
90
+ found = true;
91
+ else
92
+ log.rollback(mark);
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);
97
+ return found || addError(data, dataPath);
98
+ };
99
+ }
100
+
101
+ return function validateAnyOf(data, dataPath, dataRoot, dataKey) {
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();
107
+ for (let i = 0; i < validators.length; ++i) {
108
+ if (validators[i](data, dataPath, dataRoot, dataKey) === true) {
109
+ root.rollbackErrors(mark);
110
+ return true;
111
+ }
112
+ }
113
+ return addError(data, dataPath);
114
+ }
115
+ return true;
116
+ };
117
+ }
118
+
119
+ function compileOneOf(schemaObj, jsonSchema) {
120
+ const oneOf = getArrayClassMinItems(jsonSchema.oneOf, 1);
121
+ if (oneOf == null)
122
+ return undefined;
123
+
124
+ const validators = [];
125
+ for (let i = 0; i < oneOf.length; ++i) {
126
+ const child = oneOf[i];
127
+ if (child === true)
128
+ validators.push(trueThat);
129
+ else if (child === false)
130
+ validators.push(falseThat);
131
+ else {
132
+ const validator = schemaObj.createValidator(child, 'oneOf', i);
133
+ validators.push(validator);
134
+ }
135
+ }
136
+ if (validators.length === 0)
137
+ return undefined;
138
+
139
+ const addError = schemaObj.createErrorHandler(oneOf, 'oneOf');
140
+
141
+ // With annotation tracking, annotations from failed branches are rolled back.
142
+ const root = schemaObj.root;
143
+ if (root.usesUnevaluated) {
144
+ return function validateOneOfTracked(data, dataPath, dataRoot, dataKey) {
145
+ const log = root.evalLog;
146
+ const errors = root.errorMark();
147
+ let found = false;
148
+ for (let i = 0; i < validators.length; ++i) {
149
+ const mark = log.mark();
150
+ if (validators[i](data, dataPath, dataRoot, dataKey) === true) {
151
+ if (found === true) {
152
+ root.rollbackErrors(errors);
153
+ return addError(data, dataPath);
154
+ }
155
+ found = true;
156
+ }
157
+ else {
158
+ log.rollback(mark);
159
+ }
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);
164
+ return found || addError(data, dataPath);
165
+ };
166
+ }
167
+
168
+ return function validateOneOf(data, dataPath, dataRoot, dataKey) {
169
+ const errors = root.errorMark();
170
+ let found = false;
171
+ for (let i = 0; i < validators.length; ++i) {
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);
177
+ return addError(data, dataPath);
178
+ }
179
+ found = true;
180
+ }
181
+ }
182
+ if (found) root.rollbackErrors(errors);
183
+ return found || addError(data, dataPath);
184
+ };
185
+ }
186
+
187
+ function compileNotOf(schemaObj, jsonSchema) {
188
+ const notOf = getBoolOrObjectClass(jsonSchema.not);
189
+ if (notOf == null) return undefined;
190
+ if (notOf === false) return trueThat;
191
+
192
+ const addError = schemaObj.createErrorHandler(notOf, 'not');
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
+
205
+ // Annotations produced inside a 'not' are never kept, whatever the outcome.
206
+ const root = schemaObj.root;
207
+ if (root.usesUnevaluated) {
208
+ return function validateNotOfTracked(data, dataPath, dataRoot, dataKey) {
209
+ if (data === undefined) return true;
210
+ const log = root.evalLog;
211
+ const mark = log.mark();
212
+ const errors = root.errorMark();
213
+ const valid = validate(data, dataPath, dataRoot, dataKey);
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);
218
+ return valid === false
219
+ ? true
220
+ : addError(data, dataPath);
221
+ };
222
+ }
223
+
224
+ return function validateNotOf(data, dataPath, dataRoot, dataKey) {
225
+ if (data === undefined) return true;
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
232
+ ? true
233
+ : addError(data, dataPath);
234
+ };
235
+ }
236
+
237
+ export function compileCombineSchema(schemaObj, jsonSchema) {
238
+ const validators = [];
239
+ function addValidator(compiler) {
240
+ if (isFn(compiler))
241
+ validators.push(compiler);
242
+ }
243
+
244
+ addValidator(compileAllOf(schemaObj, jsonSchema));
245
+ addValidator(compileAnyOf(schemaObj, jsonSchema));
246
+ addValidator(compileOneOf(schemaObj, jsonSchema));
247
+ addValidator(compileNotOf(schemaObj, jsonSchema));
248
+
249
+ if (validators.length === 0)
250
+ return undefined;
251
+ if (validators.length === 1)
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
+
258
+ if (validators.length === 2) {
259
+ const first = validators[0];
260
+ const second = validators[1];
261
+ return function validateCombinaSchemaPair(data, dataPath, dataRoot, dataKey) {
262
+ return first(data, dataPath, dataRoot, dataKey)
263
+ && second(data, dataPath, dataRoot, dataKey);
264
+ };
265
+ }
266
+ else {
267
+ return function validateCombineSchema(data, dataPath, dataRoot, dataKey) {
268
+ for (let i = 0; i < validators.length; ++i) {
269
+ const validator = validators[i];
270
+ if (validator(data, dataPath, dataRoot, dataKey) === false)
271
+ return false;
272
+ }
273
+ return true;
274
+ };
275
+ }
276
+ }
@@ -0,0 +1,129 @@
1
+ //@ts-check
2
+
3
+ import {
4
+ fallbackFn,
5
+ } from '@jarenjs/core/function';
6
+
7
+ import {
8
+ isObjectClass,
9
+ } from '@jarenjs/core';
10
+
11
+ import {
12
+ hasRecursiveAnchor,
13
+ getDynamicAnchorName,
14
+ } from './dynamic-ref.js';
15
+
16
+ export function compileConditionSchema(schemaObj, jsonSchema) {
17
+ const root = schemaObj.root;
18
+ const validateIf = schemaObj.createValidator(jsonSchema.if, 'if');
19
+ const tmpThen = schemaObj.createValidator(jsonSchema.then, 'then');
20
+ const tmpElse = schemaObj.createValidator(jsonSchema.else, 'else');
21
+
22
+ if (validateIf == null) return undefined;
23
+ if (tmpThen == null && tmpElse == null) {
24
+ // A lone 'if' asserts nothing, but with annotation tracking its
25
+ // annotations (when it passes) must stay visible to unevaluated*.
26
+ if (root.usesUnevaluated) {
27
+ return function validateLoneIf(data, dataPath, dataRoot, dataKey) {
28
+ const log = root.evalLog;
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();
34
+ if (validateIf(data, dataPath, dataRoot, dataKey) === false)
35
+ log.rollback(mark);
36
+ root.rollbackErrors(errors);
37
+ return true;
38
+ };
39
+ }
40
+ return undefined;
41
+ }
42
+
43
+ // Check if then/else schemas have dynamic anchors
44
+ const thenSchema = jsonSchema.then;
45
+ const elseSchema = jsonSchema.else;
46
+
47
+ const thenHasRecAnchor = isObjectClass(thenSchema) && hasRecursiveAnchor(thenSchema);
48
+ const thenDynAnchorName = isObjectClass(thenSchema) ? getDynamicAnchorName(thenSchema) : null;
49
+ const elseHasRecAnchor = isObjectClass(elseSchema) && hasRecursiveAnchor(elseSchema);
50
+ const elseDynAnchorName = isObjectClass(elseSchema) ? getDynamicAnchorName(elseSchema) : null;
51
+
52
+ const thenHasAnchor = thenHasRecAnchor || thenDynAnchorName;
53
+ const elseHasAnchor = elseHasRecAnchor || elseDynAnchorName;
54
+
55
+ const validateThen = fallbackFn(tmpThen);
56
+ const validateElse = fallbackFn(tmpElse);
57
+
58
+ // With annotation tracking, annotations produced by a failing 'if' are
59
+ // rolled back; annotations from a passing 'if' are kept for unevaluated*.
60
+ if (root.usesUnevaluated && !thenHasAnchor && !elseHasAnchor) {
61
+ return function validateConditionTracked(data, dataPath, dataRoot, dataKey) {
62
+ const log = root.evalLog;
63
+ const mark = log.mark();
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);
71
+ log.rollback(mark);
72
+ return validateElse(data, dataPath, dataRoot, dataKey);
73
+ };
74
+ }
75
+
76
+ // If neither then nor else has dynamic anchors, use simple validation
77
+ if (!thenHasAnchor && !elseHasAnchor) {
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);
91
+ };
92
+ }
93
+
94
+ // If then or else has dynamic anchors, wrap validation to register them
95
+ const track = root.usesUnevaluated;
96
+ return function validateConditionWithDynamicAnchors(data, dataPath, dataRoot, dataKey) {
97
+ const mark = track ? root.evalLog.mark() : 0;
98
+ const conditionErrors = root.errorMark();
99
+ const taken = validateIf(data, dataPath, dataRoot, dataKey);
100
+ root.rollbackErrors(conditionErrors);
101
+ if (taken) {
102
+ // Validate then branch with dynamic anchor registration
103
+ if (thenHasAnchor && tmpThen) {
104
+ const anchorName = thenDynAnchorName || '';
105
+ root.pushDynamicAnchorValidator(anchorName, tmpThen);
106
+ try {
107
+ return validateThen(data, dataPath, dataRoot, dataKey);
108
+ } finally {
109
+ root.popDynamicAnchorValidator(anchorName);
110
+ }
111
+ }
112
+ return validateThen(data, dataPath, dataRoot, dataKey);
113
+ } else {
114
+ // Annotations from the failed 'if' must not leak to unevaluated*
115
+ if (track) root.evalLog.rollback(mark);
116
+ // Validate else branch with dynamic anchor registration
117
+ if (elseHasAnchor && tmpElse) {
118
+ const anchorName = elseDynAnchorName || '';
119
+ root.pushDynamicAnchorValidator(anchorName, tmpElse);
120
+ try {
121
+ return validateElse(data, dataPath, dataRoot, dataKey);
122
+ } finally {
123
+ root.popDynamicAnchorValidator(anchorName);
124
+ }
125
+ }
126
+ return validateElse(data, dataPath, dataRoot, dataKey);
127
+ }
128
+ };
129
+ }
package/src/content.js ADDED
@@ -0,0 +1,83 @@
1
+ import {
2
+ getStringType,
3
+ isStringType,
4
+ } from '@jarenjs/core';
5
+
6
+ import {
7
+ isValidBase64,
8
+ } from '@jarenjs/core/text';
9
+
10
+ import {
11
+ isValidJSON,
12
+ } from '@jarenjs/json';
13
+
14
+ export function compileContentEncoding(schemaObj, jsonSchema) {
15
+ const encoding = getStringType(jsonSchema.contentEncoding);
16
+ if (encoding == null) return undefined;
17
+
18
+ const addError = schemaObj.createErrorHandler(encoding, 'contentEncoding');
19
+
20
+ if (encoding === 'base64') {
21
+ return function validateBase64(data, dataPath) {
22
+ // contentEncoding only applies to strings - ignore non-strings
23
+ if (!isStringType(data)) return true;
24
+ return isValidBase64(data)
25
+ || addError(data, dataPath);
26
+ };
27
+ }
28
+
29
+ // Unknown encoding - treat as valid (per JSON Schema spec)
30
+ return undefined;
31
+ }
32
+
33
+ export function compileContentMediaType(schemaObj, jsonSchema) {
34
+ const mediaType = getStringType(jsonSchema.contentMediaType);
35
+ if (mediaType == null) return undefined;
36
+
37
+ // Check if contentEncoding is also specified
38
+ const encoding = getStringType(jsonSchema.contentEncoding);
39
+
40
+ const addError = schemaObj.createErrorHandler(mediaType, 'contentMediaType');
41
+
42
+ if (mediaType === 'application/json') {
43
+ if (encoding == null) {
44
+ return function validateJsonContent(data, dataPath) {
45
+ if (!isStringType(data)) return true;
46
+ return isValidJSON(data) || addError(data, dataPath);
47
+ };
48
+ }
49
+ else {
50
+ return function validateBase64JsonContent(data, dataPath) {
51
+ if (!isStringType(data)) return true;
52
+ // Check valid base64 first before decoding
53
+ if (!isValidBase64(data)) return addError(data, dataPath);
54
+ const decoded = Buffer.from(data, 'base64').toString('utf8');
55
+ return isValidJSON(decoded) || addError(data, dataPath);
56
+ }
57
+ }
58
+ }
59
+
60
+ // Unknown media type - treat as valid (per JSON Schema spec)
61
+ return undefined;
62
+ }
63
+
64
+ export function compileContentSchema(schemaObj, jsonSchema) {
65
+ const encoding = getStringType(jsonSchema.contentEncoding);
66
+ const mediaType = getStringType(jsonSchema.contentMediaType);
67
+
68
+ if (encoding == null && mediaType == null) return undefined;
69
+
70
+ // Per JSON Schema 2019-09/2020-12 spec, contentEncoding and contentMediaType
71
+ // are annotation keywords by default, not assertion keywords.
72
+ // They only validate when contentValidation option is explicitly enabled.
73
+ if (!schemaObj.options.contentValidation) {
74
+ return undefined;
75
+ }
76
+
77
+ const contentEncodingValidator = compileContentEncoding(schemaObj, jsonSchema);
78
+ const contentMediaTypeValidator = compileContentMediaType(schemaObj, jsonSchema);
79
+
80
+ if (!contentEncodingValidator && !contentMediaTypeValidator) return undefined;
81
+
82
+ return contentMediaTypeValidator || contentEncodingValidator;
83
+ }
package/src/data.js ADDED
@@ -0,0 +1,101 @@
1
+ //@ts-check
2
+
3
+ /**
4
+ * @fileoverview Data keyword implementation for referencing instance data.
5
+ *
6
+ * This implements the "data" keyword from json-everything's data-2022 meta-schema,
7
+ * allowing schema values to be sourced from the instance data at validation time.
8
+ *
9
+ * Example:
10
+ * {
11
+ * "type": "object",
12
+ * "properties": {
13
+ * "A": { "type": "number" },
14
+ * "B": {
15
+ * "type": "number",
16
+ * "data": {
17
+ * "minimum": "/A"
18
+ * }
19
+ * }
20
+ * }
21
+ * }
22
+ *
23
+ * Supports both absolute JSON Pointers (starting with /) and relative JSON Pointers.
24
+ * @see https://docs.json-everything.net/schema/examples/data-ref/
25
+ */
26
+
27
+ import {
28
+ isObjectClass,
29
+ isStringType,
30
+ } from '@jarenjs/core';
31
+
32
+ import {
33
+ compileDataRef,
34
+ } from '@jarenjs/json';
35
+
36
+ import {
37
+ resolveNothing,
38
+ createDataRefCompilers,
39
+ } from './tools.js';
40
+
41
+ function compileRefResolver(ref) {
42
+ try {
43
+ return compileDataRef(ref);
44
+ }
45
+ catch {
46
+ return resolveNothing;
47
+ }
48
+ }
49
+
50
+ // The fifteen keyword validators are shared with the `$data` keyword;
51
+ // only `compileRefResolver` above differs (see tools.js).
52
+ const KEYWORD_COMPILERS = createDataRefCompilers(compileRefResolver);
53
+
54
+ /**
55
+ * Compile the data keyword schema
56
+ * @param {object} schemaObj - The validation object
57
+ * @param {object} jsonSchema - The JSON schema containing the data keyword
58
+ * @returns {function|undefined} The compiled validator function or undefined
59
+ */
60
+ export function compileDataSchema(schemaObj, jsonSchema) {
61
+ const dataSchema = jsonSchema.data;
62
+ if (!isObjectClass(dataSchema)) {
63
+ return undefined;
64
+ }
65
+
66
+ const validators = [];
67
+
68
+ // Compile each supported keyword within the data object
69
+ for (const [keyword, ref] of Object.entries(dataSchema)) {
70
+ if (!isStringType(ref)) {
71
+ continue; // Skip non-string references
72
+ }
73
+
74
+ const compileKeyword = KEYWORD_COMPILERS[keyword];
75
+ if (compileKeyword === undefined) {
76
+ continue; // Unknown keyword in data, skip
77
+ }
78
+
79
+ const validator = compileKeyword(schemaObj, ref);
80
+ if (validator) {
81
+ validators.push(validator);
82
+ }
83
+ }
84
+
85
+ if (validators.length === 0) {
86
+ return undefined;
87
+ }
88
+
89
+ if (validators.length === 1) {
90
+ return validators[0];
91
+ }
92
+
93
+ return function validateDataSchema(data, dataPath, dataRoot) {
94
+ for (let i = 0; i < validators.length; i++) {
95
+ if (!validators[i](data, dataPath, dataRoot)) {
96
+ return false;
97
+ }
98
+ }
99
+ return true;
100
+ };
101
+ }