@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
@@ -0,0 +1,212 @@
1
+ //@ts-check
2
+
3
+ /**
4
+ * @fileoverview Ajv $data keyword implementation for referencing instance data.
5
+ *
6
+ * This implements the Ajv-style "$data" keyword, allowing constraint keyword values
7
+ * to be sourced from instance data at validation time using Relative JSON Pointers.
8
+ *
9
+ * Example:
10
+ * {
11
+ * "type": "object",
12
+ * "properties": {
13
+ * "smaller": {
14
+ * "type": "number",
15
+ * "maximum": { "$data": "1/larger" }
16
+ * },
17
+ * "larger": {
18
+ * "type": "number"
19
+ * }
20
+ * }
21
+ * }
22
+ *
23
+ * The "$data" value is a Relative JSON Pointer that resolves from the current data location.
24
+ * Format: <non-negative-integer>("#" | <json-pointer>)
25
+ * - "0" - The current value itself
26
+ * - "0#" - The property name/index of the current value
27
+ * - "0/foo" - The "foo" property of the current value
28
+ * - "1" - The parent value
29
+ * - "1/foo" - The "foo" property of the parent value
30
+ * - "2/bar" - Go up 2 levels, then look for "bar"
31
+ *
32
+ * @see https://github.com/ajv-validator/ajv/tree/master/spec/extras/%24data
33
+ * @see https://datatracker.ietf.org/doc/html/draft-luff-relative-json-pointer-00
34
+ */
35
+
36
+ import {
37
+ isObjectClass,
38
+ isStringType,
39
+ isObjectType,
40
+ } from '@jarenjs/core';
41
+
42
+ import {
43
+ equalsDeep,
44
+ } from '@jarenjs/core/object';
45
+
46
+ import {
47
+ NUMERIC_CONSTRAINTS, STRING_CONSTRAINTS,
48
+ ARRAY_CONSTRAINTS, OBJECT_CONSTRAINTS,
49
+ } from '@jarenjs/core/schema';
50
+
51
+ import {
52
+ compileDataRef,
53
+ JSONPOINTER_NOTHING,
54
+ } from '@jarenjs/json';
55
+
56
+ import {
57
+ createDataRefCompilers,
58
+ } from './tools.js';
59
+
60
+ /**
61
+ * Resolve a `$data` reference.
62
+ *
63
+ * Ajv's `$data` takes the same three forms the `data` keyword does — empty for
64
+ * the root, a Relative JSON Pointer, or an absolute JSON Pointer — and this
65
+ * used to compile only the relative form. An absolute `$data: '/limit'` threw,
66
+ * was swallowed by the catch, and became `resolveNothing`: the constraint
67
+ * silently never asserted, so a document that should have failed passed. A
68
+ * disabled constraint is worse than a rejected schema, which is why an
69
+ * uncompilable reference is now a compile-time error.
70
+ * @param {string} ref
71
+ * @returns {(dataRoot: any, dataPath: string) => any}
72
+ */
73
+ function compileRefResolver(ref) {
74
+ return compileDataRef(ref);
75
+ }
76
+
77
+ /**
78
+ * Check if a value is a $data reference object
79
+ * @param {*} value - The value to check
80
+ * @returns {boolean} True if the value is a $data reference object
81
+ */
82
+ function isDollarDataRef(value) {
83
+ return isObjectClass(value) && isStringType(value.$data);
84
+ }
85
+
86
+ // The fifteen keyword validators are shared with the `data` keyword;
87
+ // only `compileRefResolver` above differs (see tools.js). uniqueItems
88
+ // and required below are $data-only and stay local.
89
+ const KEYWORD_COMPILERS = createDataRefCompilers(compileRefResolver);
90
+
91
+ //#region $data-only Validators
92
+
93
+ /**
94
+ * Compile $data reference for uniqueItems constraint
95
+ * @param {object} schemaObj - The validation object
96
+ * @param {string} ref - The $data reference (relative JSON Pointer)
97
+ * @returns {function|undefined} The compiled validator function
98
+ */
99
+ function compileDollarDataUniqueItems(schemaObj, ref) {
100
+ const addError = schemaObj.createErrorHandler(ref, 'uniqueItems');
101
+ const resolveRef = compileRefResolver(ref);
102
+
103
+ return function validateDollarDataUniqueItems(data, dataPath, dataRoot) {
104
+ if (!Array.isArray(data)) return true;
105
+
106
+ const shouldBeUnique = resolveRef(dataRoot, dataPath);
107
+ if (shouldBeUnique === JSONPOINTER_NOTHING || !shouldBeUnique) return true;
108
+
109
+ // Check for duplicates using deep equality
110
+ for (let i = 0; i < data.length; i++) {
111
+ for (let j = i + 1; j < data.length; j++) {
112
+ if (equalsDeep(data[i], data[j])) {
113
+ return addError(data, dataPath);
114
+ }
115
+ }
116
+ }
117
+ return true;
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Compile $data reference for required constraint
123
+ * @param {object} schemaObj - The validation object
124
+ * @param {string} ref - The $data reference (relative JSON Pointer)
125
+ * @returns {function|undefined} The compiled validator function
126
+ */
127
+ function compileDollarDataRequired(schemaObj, ref) {
128
+ // Keyed handler: the missing property name travels as the dataKey, so
129
+ // error conversion extracts params.missingProperty like static required.
130
+ const addError = schemaObj.createErrorHandler(ref, ['required']);
131
+ const resolveRef = compileRefResolver(ref);
132
+
133
+ return function validateDollarDataRequired(data, dataPath, dataRoot) {
134
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) return true;
135
+
136
+ const requiredProps = resolveRef(dataRoot, dataPath);
137
+ if (requiredProps === JSONPOINTER_NOTHING || !Array.isArray(requiredProps)) return true;
138
+
139
+ for (const prop of requiredProps) {
140
+ if (!(prop in data)) {
141
+ return addError(prop, data, dataPath);
142
+ }
143
+ }
144
+ return true;
145
+ };
146
+ }
147
+
148
+ //#endregion
149
+
150
+ //#region Main Compilation
151
+
152
+ /** Application order of the $data-capable keywords (error order is part
153
+ * of the observable behavior; the shared groups' internal order IS the
154
+ * historical per-keyword dispatch sequence — `@jarenjs/core/schema`
155
+ * documents that order as contract). */
156
+ const DOLLAR_KEYWORD_ORDER = [
157
+ ...NUMERIC_CONSTRAINTS,
158
+ ...STRING_CONSTRAINTS,
159
+ ...ARRAY_CONSTRAINTS,
160
+ ...OBJECT_CONSTRAINTS, 'required',
161
+ 'enum', 'const',
162
+ ];
163
+
164
+ const DOLLAR_ONLY_COMPILERS = {
165
+ __proto__: null,
166
+ uniqueItems: compileDollarDataUniqueItems,
167
+ required: compileDollarDataRequired,
168
+ };
169
+
170
+ /**
171
+ * Compile $data keyword validators for a schema object
172
+ * This detects when keyword values are { $data: "..." } objects and creates
173
+ * dynamic validators that resolve the reference at validation time.
174
+ *
175
+ * @param {object} schemaObj - The validation object
176
+ * @param {object} jsonSchema - The JSON schema to compile
177
+ * @returns {function|undefined} The compiled validator function or undefined
178
+ */
179
+ export function compileDollarDataSchema(schemaObj, jsonSchema) {
180
+ const validators = [];
181
+
182
+ for (const keyword of DOLLAR_KEYWORD_ORDER) {
183
+ const value = jsonSchema[keyword];
184
+ if (!isDollarDataRef(value)) continue;
185
+
186
+ const compileKeyword = KEYWORD_COMPILERS[keyword] ?? DOLLAR_ONLY_COMPILERS[keyword];
187
+ const validator = compileKeyword(schemaObj, value.$data);
188
+ if (validator) validators.push(validator);
189
+ }
190
+
191
+ if (validators.length === 0) {
192
+ return undefined;
193
+ }
194
+
195
+ if (validators.length === 1) {
196
+ return validators[0];
197
+ }
198
+
199
+ return function validateDollarDataSchema(data, dataPath, dataRoot) {
200
+ for (let i = 0; i < validators.length; i++) {
201
+ if (!validators[i](data, dataPath, dataRoot)) {
202
+ return false;
203
+ }
204
+ }
205
+ return true;
206
+ };
207
+ }
208
+
209
+ export function isDollarDataReference(jsonSchema) {
210
+ return (jsonSchema !== null && (isObjectType(jsonSchema) && jsonSchema.$data));
211
+ }
212
+ //#endregion
@@ -0,0 +1,121 @@
1
+ //@ts-check
2
+
3
+ /**
4
+ * Dynamic Reference Resolution Module
5
+ *
6
+ * This module handles $recursiveRef (draft 2019-09) and $dynamicRef (draft 2020-12)
7
+ * which require runtime resolution based on dynamic scope.
8
+ *
9
+ * Key concepts:
10
+ * - $recursiveRef: References the nearest parent schema with $recursiveAnchor: true
11
+ * - $dynamicRef: References the nearest parent schema with matching $dynamicAnchor
12
+ *
13
+ * Unlike regular $ref, these require runtime resolution because the target depends
14
+ * on the dynamic context where the schema is used.
15
+ */
16
+
17
+ import { isObjectClass, isStringType } from '@jarenjs/core';
18
+
19
+ /**
20
+ * Check if a schema has $recursiveAnchor: true.
21
+ * @param {object} schema - The schema object
22
+ * @returns {boolean}
23
+ */
24
+ export function hasRecursiveAnchor(schema) {
25
+ return isObjectClass(schema) && schema.$recursiveAnchor === true;
26
+ }
27
+
28
+ /**
29
+ * Get the $dynamicAnchor name from a schema.
30
+ * @param {object} schema - The schema object
31
+ * @returns {string|null}
32
+ */
33
+ export function getDynamicAnchorName(schema) {
34
+ if (!isObjectClass(schema)) return null;
35
+ return isStringType(schema.$dynamicAnchor) ? schema.$dynamicAnchor : null;
36
+ }
37
+
38
+ /**
39
+ * Collect ALL dynamic anchors of a schema RESOURCE: every $dynamicAnchor
40
+ * reachable from the given schema without crossing into an embedded
41
+ * resource (a subschema that declares its own $id).
42
+ *
43
+ * Per draft 2020-12, entering a schema resource during evaluation brings
44
+ * every $dynamicAnchor of that resource into the dynamic scope - wherever
45
+ * it sits ($defs, allOf branches, properties, ...), not just at the root.
46
+ *
47
+ * @param {object} schema - The resource root schema object
48
+ * @returns {Array<{name: string, schema: object, validator: (function|null)}>}
49
+ */
50
+ export function collectDynamicAnchorsDeep(schema) {
51
+ const anchors = [];
52
+ if (!isObjectClass(schema)) return anchors;
53
+
54
+ const seen = new Set();
55
+ const queue = [{ node: schema, isRoot: true }];
56
+ while (queue.length > 0) {
57
+ const { node, isRoot } = queue.shift();
58
+ if (!isObjectClass(node) && !Array.isArray(node)) continue;
59
+ if (seen.has(node)) continue;
60
+ seen.add(node);
61
+
62
+ if (Array.isArray(node)) {
63
+ for (let i = 0; i < node.length; ++i) {
64
+ queue.push({ node: node[i], isRoot: false });
65
+ }
66
+ continue;
67
+ }
68
+
69
+ // A nested $id starts a new (embedded) resource - its anchors enter
70
+ // the dynamic scope only when that resource itself is entered.
71
+ if (!isRoot && isStringType(node.$id)) continue;
72
+
73
+ const anchorName = getDynamicAnchorName(node);
74
+ if (anchorName) {
75
+ anchors.push({ name: anchorName, schema: node, validator: null });
76
+ }
77
+
78
+ for (const key of Object.keys(node)) {
79
+ queue.push({ node: node[key], isRoot: false });
80
+ }
81
+ }
82
+
83
+ return anchors;
84
+ }
85
+
86
+ /**
87
+ * Collect all dynamic anchors from a schema's immediate definitions ($defs/definitions).
88
+ * This is used to find all $dynamicAnchor definitions that should be in scope
89
+ * when following a $ref from this schema.
90
+ *
91
+ * IMPORTANT: This only collects from the IMMEDIATE $defs of the given schema,
92
+ * not recursively.
93
+ *
94
+ * @param {object} schema - The schema object
95
+ * @returns {Array<{name: string, schema: object}>} Array of {name, schema} objects
96
+ */
97
+ export function collectDynamicAnchors(schema) {
98
+ const anchors = [];
99
+ if (!isObjectClass(schema)) return anchors;
100
+
101
+ // Check $defs first (draft 2020-12), then definitions (draft 7 and earlier)
102
+ const defs = schema.$defs || schema.definitions;
103
+ if (isObjectClass(defs)) {
104
+ for (const key of Object.keys(defs)) {
105
+ const def = defs[key];
106
+ if (isObjectClass(def)) {
107
+ // Check if this definition has a $dynamicAnchor
108
+ const anchorName = getDynamicAnchorName(def);
109
+ if (anchorName) {
110
+ anchors.push({ name: anchorName, schema: def, key });
111
+ }
112
+ // Note: We do NOT recurse into nested $defs here.
113
+ // Dynamic anchors from nested $defs of a $ref target should NOT be
114
+ // automatically in scope - they should only be registered when that
115
+ // schema is actually evaluated.
116
+ }
117
+ }
118
+ }
119
+
120
+ return anchors;
121
+ }
package/src/enum.js ADDED
@@ -0,0 +1,147 @@
1
+ //@ts-check
2
+
3
+ import {
4
+ isScalarType,
5
+ } from '@jarenjs/core';
6
+
7
+ import {
8
+ equalsDeep,
9
+ } from '@jarenjs/core/object';
10
+
11
+ import {
12
+ getArrayClassMinItems,
13
+ } from './tools.js';
14
+ import { isDollarDataReference } from './dollar-data.js';
15
+
16
+ // export const exampleEnumDataStructure = {
17
+ // allOf: [
18
+ // {
19
+ // enum: {
20
+ // source: false, // jsonpath!!
21
+ // data: [
22
+ // { color: 0xFFFFFF, name: 'white', type: 'greys' },
23
+ // { color: 0x000000, name: 'black', type: 'greys' },
24
+ // null, // this null will not be included in resulting enum array.
25
+ // { color: 0xFF0000, name: 'red', type: 'reds' },
26
+ // { color: 0x00FF00, name: 'green', type: 'greens' },
27
+ // { color: 0x0000FF, name: 'blue', type: 'blues' },
28
+ // ],
29
+ // group: 'type',
30
+ // label: 'name',
31
+ // value: 'color',
32
+ // },
33
+ // },
34
+ // {
35
+ // enum: {
36
+ // source: false, // jsonpath!!
37
+ // data: [
38
+ // [0xFFFFFF, 'white', 'greys'],
39
+ // [0x000000, 'black', 'greys'],
40
+ // null, // null will be ignored
41
+ // [0xFF0000, 'red', 'reds'],
42
+ // [0x00FF00, 'green', 'greens'],
43
+ // [0x0000FF, 'blue', 'blues'],
44
+ // ],
45
+ // group: 2,
46
+ // label: 1,
47
+ // value: 0,
48
+ // },
49
+ // },
50
+ // {
51
+ // enum: {
52
+ // data: [
53
+ // { value: 0xFFFFFF, label: 'white', group: 'greys' },
54
+ // { value: 0x000000, label: 'black', group: 'greys' },
55
+ // null, // null will be ignored
56
+ // { value: 0xFF0000, label: 'red', group: 'reds' },
57
+ // { value: 0x00FF00, label: 'green', group: 'greens' },
58
+ // { value: 0x0000FF, label: 'blue', group: 'blues' },
59
+ // ],
60
+ // },
61
+ // },
62
+ // {
63
+ // enum: {
64
+ // data: [
65
+ // [0xFFFFFF, 'white', 'greys'],
66
+ // [0x000000, 'black', 'greys'],
67
+ // null, // null will be ignored
68
+ // [0xFF0000, 'red', 'reds'],
69
+ // [0x00FF00, 'green', 'greens'],
70
+ // [0x0000FF, 'blue', 'blues'],
71
+ // ],
72
+ // },
73
+ // },
74
+ // ],
75
+ // };
76
+
77
+ function compileConst(schemaObj, jsonSchema) {
78
+ const constant = jsonSchema.const;
79
+ if (constant === undefined)
80
+ return undefined;
81
+
82
+ if (isDollarDataReference(constant))
83
+ return undefined;
84
+
85
+ const addError = schemaObj.createErrorHandler(constant, 'const');
86
+
87
+ if (constant === null || isScalarType(constant)) {
88
+ return function validatePrimitiveConst(data, dataPath) {
89
+ return constant === data
90
+ || addError(data, dataPath);
91
+ };
92
+ }
93
+ else {
94
+ return function validateComplexConst(data, dataPath) {
95
+ return equalsDeep(constant, data)
96
+ || addError(data, dataPath);
97
+ };
98
+ }
99
+ }
100
+
101
+ function compileEnum(schemaObj, jsonSchema) {
102
+ const enums = getArrayClassMinItems(jsonSchema.enum, 1);
103
+ if (enums == null) return undefined;
104
+
105
+ let hasObjects = false;
106
+ for (let i = 0; i < enums.length; ++i) {
107
+ const e = enums[i];
108
+ if (e != null && typeof e === 'object') {
109
+ hasObjects = true;
110
+ break;
111
+ }
112
+ }
113
+
114
+ const addError = schemaObj.createErrorHandler(enums, 'enum');
115
+
116
+ if (hasObjects === false) {
117
+ return function validateEnumSimple(data, dataPath) {
118
+ return data === undefined
119
+ ? true
120
+ : enums.includes(data)
121
+ ? true
122
+ : addError(data, dataPath);
123
+ };
124
+ }
125
+ else {
126
+ return function validateEnumDeep(data, dataPath) {
127
+ if (data === undefined) return true;
128
+ if (data === null || typeof data !== 'object')
129
+ return enums.includes(data)
130
+ ? true
131
+ : addError(data, dataPath);
132
+
133
+ for (let i = 0; i < enums.length; ++i) {
134
+ if (equalsDeep(enums[i], data) === true)
135
+ return true;
136
+ }
137
+ return addError(data, dataPath);
138
+ };
139
+ }
140
+ }
141
+
142
+ export function compileEnumBasic(schemaObj, jsonSchema) {
143
+ return [
144
+ compileConst(schemaObj, jsonSchema),
145
+ compileEnum(schemaObj, jsonSchema),
146
+ ];
147
+ }
package/src/format.js ADDED
@@ -0,0 +1,108 @@
1
+ import {
2
+ isStringType,
3
+ isFn,
4
+ } from '@jarenjs/core';
5
+
6
+ /** @typedef {import('./index.js').FormatCompiler} FormatCompiler */
7
+
8
+ /**
9
+ * Registers a single format compiler under a name.
10
+ * Existing registrations are never overwritten.
11
+ * @param {Record<string, FormatCompiler>} registered - The formats registry object
12
+ * @param {string} name - The format name (e.g. 'email', 'uri', 'date-time')
13
+ * @param {FormatCompiler} formatCompiler - The compiler to register
14
+ * @returns {boolean} True when the compiler was registered
15
+ */
16
+ export function registerFormatCompiler(registered, name, formatCompiler) {
17
+ if (registered[name] == null) {
18
+ if (isFn(formatCompiler)) {
19
+ registered[name] = formatCompiler;
20
+ return true;
21
+ }
22
+ }
23
+ return false;
24
+ }
25
+
26
+ /**
27
+ * Registers multiple format compilers at once.
28
+ * Existing registrations are never overwritten.
29
+ * @param {Record<string, FormatCompiler>} registered - The formats registry object
30
+ * @param {Record<string, FormatCompiler>} formatCompilers - Object mapping format names to compiler functions
31
+ * @returns {Record<string, FormatCompiler>} The registry object passed in
32
+ */
33
+ export function registerFormatCompilers(registered, formatCompilers) {
34
+ const keys = Object.keys(formatCompilers);
35
+ for (let i = 0; i < keys.length; ++i) {
36
+ const key = keys[i];
37
+ const item = formatCompilers[key];
38
+ registerFormatCompiler(registered, key, item);
39
+ }
40
+ return registered;
41
+ }
42
+
43
+ /** Named once: both wrong-shape paths end at the same mistake. */
44
+ const WRONG_SHAPE_HINT = 'Register the format COMPILERS (stringFormats, dateTimeFormats, '
45
+ + 'numberFormats, jsonFormats, geoFormats) rather than the raw testers (formatTesters) — '
46
+ + 'both are objects full of functions, and only the compilers take (schemaObj, jsonSchema).';
47
+
48
+ export function getSchemaFormatCompiler(registered, name) {
49
+ if (isStringType(name))
50
+ return registered[name];
51
+ else
52
+ return undefined;
53
+ }
54
+
55
+ export function compileFormatBasic(schemaObj, jsonSchema) {
56
+ if (!isStringType(jsonSchema.format))
57
+ return undefined;
58
+
59
+ // From draft 2020-12 on, format is annotation-only unless assertion is
60
+ // enabled (formatAssertion option or format-assertion vocabulary).
61
+ // Nothing is lost by an unregistered name here — the keyword would not
62
+ // have asserted even with a compiler — so the unknownFormats check
63
+ // below deliberately does not run in this branch. Complaining here
64
+ // would be complaining about the spec.
65
+ if (schemaObj.options.formatAssertion === false)
66
+ return undefined;
67
+ const compiler = getSchemaFormatCompiler(
68
+ schemaObj.formats,
69
+ jsonSchema.format);
70
+
71
+ // An unregistered name with assertion ON is the silent failure this
72
+ // check exists to end: the author asked for the value to be checked,
73
+ // the registry has nothing to check it with, and without this the
74
+ // schema compiles to a validator that accepts everything. Raising it
75
+ // at COMPILE time keeps instance validation spec-exact — no data is
76
+ // ever invalidated by an unknown format, the schema's AUTHOR is told.
77
+ if (compiler == null) {
78
+ if (schemaObj.options.unknownFormats === 'ignore')
79
+ return undefined;
80
+ throw new Error(`Unknown format '${jsonSchema.format}': no compiler is registered for it, `
81
+ + 'so this schema would accept every value for that keyword. Register one '
82
+ + '(addFormats(dateTimeFormats) etc. from @jarenjs/formats, or addFormat(name, compiler) '
83
+ + "for your own), or pass { unknownFormats: 'ignore' } to accept it as an annotation.");
84
+ }
85
+
86
+ // A format COMPILER is called once per schema location and returns the
87
+ // per-value validator. Handing it a bare tester — `formatTesters`
88
+ // instead of `dateTimeFormats`, an easy mistake since both are objects
89
+ // full of functions — either returns a boolean (which the caller
90
+ // silently drops, so the format checks nothing again) or throws deep
91
+ // inside the tester on an argument it never expected. Both become one
92
+ // legible complaint here.
93
+ let validator;
94
+ try {
95
+ validator = compiler(schemaObj, jsonSchema);
96
+ }
97
+ catch (err) {
98
+ throw new Error(`Format '${jsonSchema.format}' failed to compile: ${err?.message ?? err}. `
99
+ + `${WRONG_SHAPE_HINT}`, { cause: err });
100
+ }
101
+ // Nullish stays legal: a compiler may decline a schema it cannot serve.
102
+ if (validator != null && !isFn(validator) && !Array.isArray(validator)) {
103
+ throw new Error(`Format '${jsonSchema.format}' is registered with something that is not a `
104
+ + `format compiler: calling it returned ${typeof validator}, not a validator function. `
105
+ + `${WRONG_SHAPE_HINT}`);
106
+ }
107
+ return validator;
108
+ }