@amritk/generate-parsers 0.12.2 → 0.13.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/README.md +4 -14
- package/dist/generators/build-schema.js +32 -7
- package/dist/generators/generate-discriminated-union-validation.d.ts +9 -0
- package/dist/generators/generate-discriminated-union-validation.js +33 -22
- package/dist/generators/generate-enum-check.d.ts +8 -0
- package/dist/generators/generate-enum-check.js +14 -0
- package/dist/generators/generate-files.js +1 -0
- package/dist/generators/generate-parser-function.d.ts +6 -0
- package/dist/generators/generate-parser-function.js +167 -124
- package/dist/generators/generate-schema-checks.js +7 -2
- package/dist/generators/generate-strict-assertion.d.ts +16 -1
- package/dist/generators/generate-strict-assertion.js +111 -26
- package/dist/generators/generate-type-checks.d.ts +65 -0
- package/dist/generators/generate-type-checks.js +370 -0
- package/dist/generators/generate-validation-expression.js +61 -2
- package/dist/helpers/collect-helpers.js +3 -1
- package/dist/helpers/collect-imports.js +5 -1
- package/package.json +3 -3
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { escapeRegexPattern } from '@amritk/helpers/escape-regex-pattern';
|
|
2
2
|
import { getMjstInstanceOf, getMjstPrimitive } from '@amritk/helpers/mjst-extension';
|
|
3
|
+
import { multipleOfFailExpr } from '@amritk/helpers/multiple-of-check';
|
|
3
4
|
import { safeAccessor } from '@amritk/helpers/safe-accessor';
|
|
4
|
-
import { hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasMaximum, hasMaxLength, hasMinimum, hasMinLength, hasMultipleOf, hasPattern, hasProperties, hasRef, hasRequired, hasType, isSchemaObject, } from '@amritk/helpers/schema-guards';
|
|
5
|
+
import { hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMinItems, hasMinimum, hasMinLength, hasMultipleOf, hasPattern, hasProperties, hasRef, hasRequired, hasType, hasUniqueItems, isSchemaObject, } from '@amritk/helpers/schema-guards';
|
|
6
|
+
import { generateEnumCheck } from './generate-enum-check.js';
|
|
7
|
+
import { canEnforceUnion, generateUnionCheck, getUnionBranches } from './generate-type-checks.js';
|
|
8
|
+
import { scalarItemTypeCheck } from './generate-validation-expression.js';
|
|
5
9
|
/**
|
|
6
10
|
* Returns the inline condition that is true when `accessor` is the wrong type
|
|
7
11
|
* for the given JSON Schema primitive type.
|
|
@@ -11,8 +15,10 @@ const wrongTypeCondition = (accessor, type) => {
|
|
|
11
15
|
case 'string':
|
|
12
16
|
return `typeof ${accessor} !== "string"`;
|
|
13
17
|
case 'number':
|
|
14
|
-
case 'integer':
|
|
15
18
|
return `typeof ${accessor} !== "number"`;
|
|
19
|
+
case 'integer':
|
|
20
|
+
// `integer` also rejects non-integral numbers; a bare typeof accepts `1.5`.
|
|
21
|
+
return `(typeof ${accessor} !== "number" || !Number.isInteger(${accessor}))`;
|
|
16
22
|
case 'boolean':
|
|
17
23
|
return `typeof ${accessor} !== "boolean"`;
|
|
18
24
|
case 'array':
|
|
@@ -28,6 +34,18 @@ const wrongTypeCondition = (accessor, type) => {
|
|
|
28
34
|
* `integer` collapses to `number` since both are validated via `typeof === "number"`.
|
|
29
35
|
*/
|
|
30
36
|
const typeLabel = (type) => (type === 'integer' ? 'number' : type);
|
|
37
|
+
/**
|
|
38
|
+
* Emits `throw new Error(<message>[ + <suffixExpr>])`. The static message may
|
|
39
|
+
* contain schema-controlled text (property names, patterns, enum values), so it
|
|
40
|
+
* is always serialized with `JSON.stringify` — a key like `it's` or a pattern
|
|
41
|
+
* containing a quote or newline can no longer break out of the string literal or
|
|
42
|
+
* inject code. `suffixExpr`, when given, is a runtime expression appended to the
|
|
43
|
+
* message (e.g. `typeof input`).
|
|
44
|
+
*/
|
|
45
|
+
const throwError = (message, suffixExpr) => {
|
|
46
|
+
const literal = JSON.stringify(message);
|
|
47
|
+
return suffixExpr ? `throw new Error(${literal} + (${suffixExpr}))` : `throw new Error(${literal})`;
|
|
48
|
+
};
|
|
31
49
|
/**
|
|
32
50
|
* Generates strict-mode constraint checks for a typed property
|
|
33
51
|
* (pattern, length, min/max, multipleOf).
|
|
@@ -37,69 +55,125 @@ const generateConstraintChecks = (acc, propSchema, typeName, key) => {
|
|
|
37
55
|
return [];
|
|
38
56
|
const t = propSchema.type;
|
|
39
57
|
const lines = [];
|
|
58
|
+
const field = `[${typeName}] field '${key}'`;
|
|
40
59
|
if (t === 'string') {
|
|
41
60
|
if (hasPattern(propSchema)) {
|
|
42
61
|
const pattern = escapeRegexPattern(propSchema.pattern);
|
|
43
|
-
lines.push(` if (typeof ${acc} === "string" && !/${pattern}/.test(${acc}))
|
|
62
|
+
lines.push(` if (typeof ${acc} === "string" && !/${pattern}/.test(${acc})) ${throwError(`${field} must match pattern ${propSchema.pattern}`)};`);
|
|
44
63
|
}
|
|
45
64
|
if (hasMinLength(propSchema)) {
|
|
46
|
-
lines.push(` if (typeof ${acc} === "string" && ${acc}.length < ${propSchema.minLength})
|
|
65
|
+
lines.push(` if (typeof ${acc} === "string" && ${acc}.length < ${propSchema.minLength}) ${throwError(`${field} must have at least ${propSchema.minLength} characters`)};`);
|
|
47
66
|
}
|
|
48
67
|
if (hasMaxLength(propSchema)) {
|
|
49
|
-
lines.push(` if (typeof ${acc} === "string" && ${acc}.length > ${propSchema.maxLength})
|
|
68
|
+
lines.push(` if (typeof ${acc} === "string" && ${acc}.length > ${propSchema.maxLength}) ${throwError(`${field} must have at most ${propSchema.maxLength} characters`)};`);
|
|
50
69
|
}
|
|
51
70
|
}
|
|
52
71
|
if (t === 'number' || t === 'integer') {
|
|
53
72
|
if (hasMinimum(propSchema)) {
|
|
54
|
-
lines.push(` if (typeof ${acc} === "number" && ${acc} < ${propSchema.minimum})
|
|
73
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} < ${propSchema.minimum}) ${throwError(`${field} must be >= ${propSchema.minimum}`)};`);
|
|
55
74
|
}
|
|
56
75
|
if (hasMaximum(propSchema)) {
|
|
57
|
-
lines.push(` if (typeof ${acc} === "number" && ${acc} > ${propSchema.maximum})
|
|
76
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} > ${propSchema.maximum}) ${throwError(`${field} must be <= ${propSchema.maximum}`)};`);
|
|
58
77
|
}
|
|
59
78
|
if (hasExclusiveMinimum(propSchema)) {
|
|
60
|
-
lines.push(` if (typeof ${acc} === "number" && ${acc} <= ${propSchema.exclusiveMinimum})
|
|
79
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} <= ${propSchema.exclusiveMinimum}) ${throwError(`${field} must be > ${propSchema.exclusiveMinimum}`)};`);
|
|
61
80
|
}
|
|
62
81
|
if (hasExclusiveMaximum(propSchema)) {
|
|
63
|
-
lines.push(` if (typeof ${acc} === "number" && ${acc} >= ${propSchema.exclusiveMaximum})
|
|
82
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} >= ${propSchema.exclusiveMaximum}) ${throwError(`${field} must be < ${propSchema.exclusiveMaximum}`)};`);
|
|
64
83
|
}
|
|
65
84
|
if (hasMultipleOf(propSchema)) {
|
|
66
|
-
lines.push(` if (typeof ${acc} === "number" && ${acc
|
|
85
|
+
lines.push(` if (typeof ${acc} === "number" && ${multipleOfFailExpr(acc, propSchema.multipleOf)}) ${throwError(`${field} must be a multiple of ${propSchema.multipleOf}`)};`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (t === 'array') {
|
|
89
|
+
if (hasMinItems(propSchema)) {
|
|
90
|
+
lines.push(` if (Array.isArray(${acc}) && ${acc}.length < ${propSchema.minItems}) ${throwError(`${field} must have at least ${propSchema.minItems} items`)};`);
|
|
91
|
+
}
|
|
92
|
+
if (hasMaxItems(propSchema)) {
|
|
93
|
+
lines.push(` if (Array.isArray(${acc}) && ${acc}.length > ${propSchema.maxItems}) ${throwError(`${field} must have at most ${propSchema.maxItems} items`)};`);
|
|
94
|
+
}
|
|
95
|
+
if (hasUniqueItems(propSchema) && propSchema.uniqueItems === true) {
|
|
96
|
+
// Deep-equality dedupe via JSON keys, matching the interpreter's semantics —
|
|
97
|
+
// a plain `new Set` would only catch primitive duplicates, not equal objects.
|
|
98
|
+
lines.push(` if (Array.isArray(${acc}) && new Set(${acc}.map((_u) => JSON.stringify(_u))).size !== ${acc}.length) ${throwError(`${field} must NOT have duplicate items`)};`);
|
|
99
|
+
}
|
|
100
|
+
// Item types: the fast path proves them via `.every`, but this slow path
|
|
101
|
+
// used to check only length/uniqueness, letting e.g. a number slip into a
|
|
102
|
+
// declared `string[]`. Enforce scalar and enum item schemas here; richer
|
|
103
|
+
// item schemas ($refs, objects) are validated by their own parsers.
|
|
104
|
+
const itemCheck = generateItemCheck(propSchema);
|
|
105
|
+
if (itemCheck) {
|
|
106
|
+
lines.push(` if (Array.isArray(${acc}) && !${acc}.every((_it) => ${itemCheck.check})) ${throwError(`${field} ${itemCheck.message}`)};`);
|
|
67
107
|
}
|
|
68
108
|
}
|
|
69
109
|
return lines;
|
|
70
110
|
};
|
|
111
|
+
/**
|
|
112
|
+
* Boolean per-item check (bound to `_it`) for an array schema's `items`, with
|
|
113
|
+
* the error-message fragment describing what was expected. Only scalar types
|
|
114
|
+
* and enums are checked — returns null for anything richer.
|
|
115
|
+
*/
|
|
116
|
+
const generateItemCheck = (schema) => {
|
|
117
|
+
if (!isSchemaObject(schema) || !hasItems(schema) || Array.isArray(schema.items))
|
|
118
|
+
return null;
|
|
119
|
+
const items = schema.items;
|
|
120
|
+
if (isSchemaObject(items) && hasEnum(items) && items.enum.length > 0) {
|
|
121
|
+
const label = items.enum.map((v) => JSON.stringify(v)).join(', ');
|
|
122
|
+
return { check: generateEnumCheck('_it', items.enum), message: `items must be one of: ${label}` };
|
|
123
|
+
}
|
|
124
|
+
const scalarCheck = scalarItemTypeCheck(items, '_it');
|
|
125
|
+
if (scalarCheck === null || !isSchemaObject(items) || !hasType(items))
|
|
126
|
+
return null;
|
|
127
|
+
return { check: scalarCheck, message: `items expected ${typeLabel(items.type)}` };
|
|
128
|
+
};
|
|
71
129
|
/**
|
|
72
130
|
* Generates strict-mode lines for a single property of an object schema.
|
|
73
131
|
* Properties with a `$ref` are skipped here — the nested parser handles its own
|
|
74
132
|
* strict check when called from the parent's slow path.
|
|
75
133
|
*/
|
|
76
|
-
const generatePropertyAssertion = (key, propSchema, isRequired, typeName) => {
|
|
134
|
+
const generatePropertyAssertion = (key, propSchema, isRequired, typeName, context = {}) => {
|
|
77
135
|
const acc = safeAccessor('input', key);
|
|
136
|
+
const field = `[${typeName}] field '${key}'`;
|
|
78
137
|
const lines = [];
|
|
79
138
|
if (isRequired) {
|
|
80
|
-
lines.push(` if (!(${JSON.stringify(key)} in input))
|
|
139
|
+
lines.push(` if (!(${JSON.stringify(key)} in input)) ${throwError(`[${typeName}] missing required property '${key}'`)};`);
|
|
81
140
|
}
|
|
82
141
|
if (!isSchemaObject(propSchema))
|
|
83
142
|
return lines;
|
|
84
143
|
if (hasRef(propSchema))
|
|
85
144
|
return lines;
|
|
145
|
+
// Union properties: enforce membership when every branch check is
|
|
146
|
+
// false-sound (canEnforceUnion), so a value matching no variant throws
|
|
147
|
+
// instead of passing through untyped. Left unenforced (pass-through, the
|
|
148
|
+
// historical behavior) when any branch is too complex to check safely.
|
|
149
|
+
const unionBranches = getUnionBranches(propSchema);
|
|
150
|
+
if (unionBranches) {
|
|
151
|
+
if (!context.stripUnknown && canEnforceUnion(unionBranches, context.rootSchema)) {
|
|
152
|
+
const check = generateUnionCheck(acc, unionBranches, context.useRefImports ?? false, context.suffix ?? '');
|
|
153
|
+
if (check !== null) {
|
|
154
|
+
const failure = throwError(`${field} does not match any allowed variant`);
|
|
155
|
+
lines.push(isRequired ? ` if (!(${check})) ${failure};` : ` if (${acc} !== undefined && !(${check})) ${failure};`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return lines;
|
|
159
|
+
}
|
|
86
160
|
const instanceOf = getMjstInstanceOf(propSchema);
|
|
87
161
|
if (instanceOf) {
|
|
88
162
|
if (isRequired) {
|
|
89
|
-
lines.push(` if (!(${acc} instanceof ${instanceOf}))
|
|
163
|
+
lines.push(` if (!(${acc} instanceof ${instanceOf})) ${throwError(`${field} must be ${instanceOf}`)};`);
|
|
90
164
|
}
|
|
91
165
|
else {
|
|
92
|
-
lines.push(` if (${acc} !== undefined && !(${acc} instanceof ${instanceOf}))
|
|
166
|
+
lines.push(` if (${acc} !== undefined && !(${acc} instanceof ${instanceOf})) ${throwError(`${field} must be ${instanceOf}`)};`);
|
|
93
167
|
}
|
|
94
168
|
return lines;
|
|
95
169
|
}
|
|
96
170
|
const primitive = getMjstPrimitive(propSchema);
|
|
97
171
|
if (primitive) {
|
|
98
172
|
if (isRequired) {
|
|
99
|
-
lines.push(` if (typeof ${acc} !== "${primitive}")
|
|
173
|
+
lines.push(` if (typeof ${acc} !== "${primitive}") ${throwError(`${field} must be ${primitive}`)};`);
|
|
100
174
|
}
|
|
101
175
|
else {
|
|
102
|
-
lines.push(` if (${acc} !== undefined && typeof ${acc} !== "${primitive}")
|
|
176
|
+
lines.push(` if (${acc} !== undefined && typeof ${acc} !== "${primitive}") ${throwError(`${field} must be ${primitive}`)};`);
|
|
103
177
|
}
|
|
104
178
|
return lines;
|
|
105
179
|
}
|
|
@@ -107,10 +181,10 @@ const generatePropertyAssertion = (key, propSchema, isRequired, typeName) => {
|
|
|
107
181
|
const allowed = JSON.stringify(propSchema.enum);
|
|
108
182
|
const label = propSchema.enum.map((v) => JSON.stringify(v)).join(', ');
|
|
109
183
|
if (isRequired) {
|
|
110
|
-
lines.push(` if (!(${allowed} as readonly unknown[]).includes(${acc}))
|
|
184
|
+
lines.push(` if (!(${allowed} as readonly unknown[]).includes(${acc})) ${throwError(`${field} must be one of: ${label}`)};`);
|
|
111
185
|
}
|
|
112
186
|
else {
|
|
113
|
-
lines.push(` if (${acc} !== undefined && !(${allowed} as readonly unknown[]).includes(${acc}))
|
|
187
|
+
lines.push(` if (${acc} !== undefined && !(${allowed} as readonly unknown[]).includes(${acc})) ${throwError(`${field} must be one of: ${label}`)};`);
|
|
114
188
|
}
|
|
115
189
|
return lines;
|
|
116
190
|
}
|
|
@@ -118,11 +192,12 @@ const generatePropertyAssertion = (key, propSchema, isRequired, typeName) => {
|
|
|
118
192
|
const t = propSchema.type;
|
|
119
193
|
const wrongType = wrongTypeCondition(acc, t);
|
|
120
194
|
if (wrongType) {
|
|
195
|
+
const expected = throwError(`${field} expected ${typeLabel(t)}, got `, `typeof ${acc}`);
|
|
121
196
|
if (isRequired) {
|
|
122
|
-
lines.push(` if (${wrongType})
|
|
197
|
+
lines.push(` if (${wrongType}) ${expected};`);
|
|
123
198
|
}
|
|
124
199
|
else {
|
|
125
|
-
lines.push(` if (${acc} !== undefined && (${wrongType}))
|
|
200
|
+
lines.push(` if (${acc} !== undefined && (${wrongType})) ${expected};`);
|
|
126
201
|
}
|
|
127
202
|
}
|
|
128
203
|
lines.push(...generateConstraintChecks(acc, propSchema, typeName, key));
|
|
@@ -140,14 +215,14 @@ const generatePropertyAssertion = (key, propSchema, isRequired, typeName) => {
|
|
|
140
215
|
* Properties with a `$ref` are validated by the nested parser's own strict
|
|
141
216
|
* check when that parser is invoked downstream.
|
|
142
217
|
*/
|
|
143
|
-
export const generateObjectStrictAssertion = (schema, typeName) => {
|
|
218
|
+
export const generateObjectStrictAssertion = (schema, typeName, context = {}) => {
|
|
144
219
|
const lines = [];
|
|
145
|
-
lines.push(` if (!isObject(input))
|
|
220
|
+
lines.push(` if (!isObject(input)) ${throwError(`[${typeName}] expected object, got `, 'input === null ? "null" : typeof input')};`);
|
|
146
221
|
if (!hasProperties(schema) || !isSchemaObject(schema))
|
|
147
222
|
return lines;
|
|
148
223
|
const required = new Set(hasRequired(schema) ? schema.required : []);
|
|
149
224
|
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
150
|
-
lines.push(...generatePropertyAssertion(key, propSchema, required.has(key), typeName));
|
|
225
|
+
lines.push(...generatePropertyAssertion(key, propSchema, required.has(key), typeName, context));
|
|
151
226
|
}
|
|
152
227
|
return lines;
|
|
153
228
|
};
|
|
@@ -156,13 +231,14 @@ export const generateObjectStrictAssertion = (schema, typeName) => {
|
|
|
156
231
|
* Returns null when the schema has no type information to assert on.
|
|
157
232
|
*/
|
|
158
233
|
export const generateScalarStrictAssertion = (schema, typeName) => {
|
|
234
|
+
const got = 'input === null ? "null" : typeof input';
|
|
159
235
|
const instanceOf = getMjstInstanceOf(schema);
|
|
160
236
|
if (instanceOf) {
|
|
161
|
-
return ` if (!(input instanceof ${instanceOf}))
|
|
237
|
+
return ` if (!(input instanceof ${instanceOf})) ${throwError(`[${typeName}] expected ${instanceOf}, got `, got)};`;
|
|
162
238
|
}
|
|
163
239
|
const primitive = getMjstPrimitive(schema);
|
|
164
240
|
if (primitive) {
|
|
165
|
-
return ` if (typeof input !== "${primitive}")
|
|
241
|
+
return ` if (typeof input !== "${primitive}") ${throwError(`[${typeName}] expected ${primitive}, got `, got)};`;
|
|
166
242
|
}
|
|
167
243
|
if (!isSchemaObject(schema) || !hasType(schema))
|
|
168
244
|
return null;
|
|
@@ -170,5 +246,14 @@ export const generateScalarStrictAssertion = (schema, typeName) => {
|
|
|
170
246
|
const wrongType = wrongTypeCondition('input', t);
|
|
171
247
|
if (!wrongType)
|
|
172
248
|
return null;
|
|
173
|
-
|
|
249
|
+
const lines = [` if (${wrongType}) ${throwError(`[${typeName}] expected ${typeLabel(t)}, got `, got)};`];
|
|
250
|
+
// Root-level arrays enforce scalar/enum item types too — the same gap the
|
|
251
|
+
// property path closes in generateConstraintChecks.
|
|
252
|
+
if (t === 'array') {
|
|
253
|
+
const itemCheck = generateItemCheck(schema);
|
|
254
|
+
if (itemCheck) {
|
|
255
|
+
lines.push(` if (!(input as readonly unknown[]).every((_it) => ${itemCheck.check})) ${throwError(`[${typeName}] ${itemCheck.message}`)};`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return lines.join('\n');
|
|
174
259
|
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
|
2
|
+
/**
|
|
3
|
+
* Boolean type-check expression builders shared by the shape validators, the
|
|
4
|
+
* parsers' fast paths, and the strict-mode assertions. They live in their own
|
|
5
|
+
* module (rather than generate-parser-function) so generate-strict-assertion
|
|
6
|
+
* can use them without an import cycle.
|
|
7
|
+
*
|
|
8
|
+
* Every expression built here is *true-sound*: when it evaluates to true the
|
|
9
|
+
* value definitely matches the schema fragment. That is all a fast path needs.
|
|
10
|
+
* The reverse direction — false definitely means invalid — only holds when
|
|
11
|
+
* {@link canEnforceUnion} approves the schema, because a check may call an
|
|
12
|
+
* imported `validate{X}Shape` that was generated as a conservative `=> false`
|
|
13
|
+
* stub. Strict-mode code that *throws* on a false check must gate on it.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Returns the shape-predicate function name for a given type name.
|
|
17
|
+
* The predicate is generated alongside each parser and tells callers
|
|
18
|
+
* whether `input` is already in the shape produced by the parser's fast path.
|
|
19
|
+
*/
|
|
20
|
+
export declare const shapeValidatorName: (typeName: string) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Extracts the branch list of a `oneOf`/`anyOf` union, or `null` when the
|
|
23
|
+
* schema is not a union (or mixes in other composition keywords we cannot
|
|
24
|
+
* turn into a membership check).
|
|
25
|
+
*/
|
|
26
|
+
export declare const getUnionBranches: (schema: JSONSchema) => readonly JSONSchema[] | null;
|
|
27
|
+
/**
|
|
28
|
+
* Generates a fast-path type check expression for a property.
|
|
29
|
+
* Returns null if the schema is too complex for a simple type check.
|
|
30
|
+
*
|
|
31
|
+
* When `useRefImports` is true, $ref properties (and arrays of $refs) are
|
|
32
|
+
* checked by calling the imported shape predicate, allowing parent parsers
|
|
33
|
+
* to fast-path through nested ref types without recursing into their parsers.
|
|
34
|
+
*/
|
|
35
|
+
export declare const generatePropertyTypeCheck: (varName: string, schema: JSONSchema, useRefImports: boolean, suffix: string) => string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Generates a boolean check that `varName` matches an inline object schema —
|
|
38
|
+
* `isObject` plus a per-property check for every declared property (and a
|
|
39
|
+
* presence check for `required` keys without a declared schema). Used for
|
|
40
|
+
* union branches written inline, where no named shape validator exists to
|
|
41
|
+
* call. Returns null when any property is too complex to check.
|
|
42
|
+
*/
|
|
43
|
+
export declare const generateInlineObjectCheck: (varName: string, schema: JSONSchema, useRefImports: boolean, suffix: string) => string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Generates a union membership check: true when `varName` matches at least one
|
|
46
|
+
* branch of a `oneOf`/`anyOf`. Inline object branches get a per-property check;
|
|
47
|
+
* everything else goes through {@link generatePropertyTypeCheck} ($refs call
|
|
48
|
+
* the imported shape validator, consts/enums/scalars check directly). Returns
|
|
49
|
+
* null when any branch cannot be checked.
|
|
50
|
+
*/
|
|
51
|
+
export declare const generateUnionCheck: (varName: string, branches: readonly JSONSchema[], useRefImports: boolean, suffix: string) => string | null;
|
|
52
|
+
/**
|
|
53
|
+
* True when the union membership check emitted by {@link generateUnionCheck}
|
|
54
|
+
* is also *false-sound* — a false result definitely means the value is invalid
|
|
55
|
+
* — so strict mode may throw on it.
|
|
56
|
+
*
|
|
57
|
+
* The risk is `$ref`s: a branch (or a property inside one) whose check calls
|
|
58
|
+
* an imported `validate{X}Shape` is only trustworthy when that validator was
|
|
59
|
+
* generated as a real predicate. This walks every reachable ref (mirroring
|
|
60
|
+
* generateShapeValidator's stub conditions) and rejects the union when any of
|
|
61
|
+
* them would have produced the conservative `=> false` stub, or is itself
|
|
62
|
+
* built on one. Cycles (e.g. a recursive `expr` union) are assumed sound —
|
|
63
|
+
* the recursion bottoms out on the data, not the schema.
|
|
64
|
+
*/
|
|
65
|
+
export declare const canEnforceUnion: (branches: readonly JSONSchema[], rootSchema: Record<string, unknown> | undefined) => boolean;
|