@amritk/generate-parsers 0.23.0 → 0.25.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 +37 -463
- package/dist/index.d.ts +22 -2
- package/dist/index.js +18 -1
- package/package.json +15 -40
- package/AI.md +0 -52
- package/dist/generators/assert-supported-keywords.d.ts +0 -30
- package/dist/generators/assert-supported-keywords.js +0 -87
- package/dist/generators/build-schema.d.ts +0 -111
- package/dist/generators/build-schema.js +0 -74
- package/dist/generators/generate-deep-equal-check.d.ts +0 -24
- package/dist/generators/generate-deep-equal-check.js +0 -35
- package/dist/generators/generate-default-from-pattern.d.ts +0 -26
- package/dist/generators/generate-default-from-pattern.js +0 -223
- package/dist/generators/generate-discriminated-union-validation.d.ts +0 -15
- package/dist/generators/generate-discriminated-union-validation.js +0 -28
- package/dist/generators/generate-enum-check.d.ts +0 -35
- package/dist/generators/generate-enum-check.js +0 -31
- package/dist/generators/generate-files.d.ts +0 -138
- package/dist/generators/generate-files.js +0 -79
- package/dist/generators/generate-non-discriminated-union-validation.d.ts +0 -6
- package/dist/generators/generate-non-discriminated-union-validation.js +0 -26
- package/dist/generators/generate-parser-function.d.ts +0 -120
- package/dist/generators/generate-parser-function.js +0 -1955
- package/dist/generators/generate-schema-checks.d.ts +0 -5
- package/dist/generators/generate-schema-checks.js +0 -260
- package/dist/generators/generate-strict-assertion.d.ts +0 -155
- package/dist/generators/generate-strict-assertion.js +0 -759
- package/dist/generators/generate-type-checks.d.ts +0 -148
- package/dist/generators/generate-type-checks.js +0 -454
- package/dist/generators/generate-unique-items-check.d.ts +0 -16
- package/dist/generators/generate-unique-items-check.js +0 -32
- package/dist/generators/generate-validation-expression.d.ts +0 -79
- package/dist/generators/generate-validation-expression.js +0 -416
- package/dist/generators/subschema-match.d.ts +0 -48
- package/dist/generators/subschema-match.js +0 -485
- package/dist/generators/unevaluated-match.d.ts +0 -19
- package/dist/generators/unevaluated-match.js +0 -206
- package/dist/helpers/apply-schema-extensions.d.ts +0 -35
- package/dist/helpers/apply-schema-extensions.js +0 -20
- package/dist/helpers/collect-helpers.d.ts +0 -15
- package/dist/helpers/collect-helpers.js +0 -64
- package/dist/helpers/collect-imports.d.ts +0 -93
- package/dist/helpers/collect-imports.js +0 -197
- package/dist/helpers/find-discriminator.d.ts +0 -6
- package/dist/helpers/find-discriminator.js +0 -27
- package/dist/helpers/get-default-value.d.ts +0 -7
- package/dist/helpers/get-default-value.js +0 -155
- package/dist/helpers/get-discriminator-value.d.ts +0 -6
- package/dist/helpers/get-discriminator-value.js +0 -18
- package/dist/types/schema-extensions.d.ts +0 -34
- package/dist/types/schema-extensions.js +0 -0
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
|
2
|
-
/**
|
|
3
|
-
* A boolean type check for an array element whose schema is a single scalar type
|
|
4
|
-
* (the cases the element coercion below can handle). `integer` is treated as
|
|
5
|
-
* `number` because the generated TS element type is `number`. Returns `null` for
|
|
6
|
-
* objects, arrays, unions, `$ref`s, and anything richer.
|
|
7
|
-
*/
|
|
8
|
-
export declare const scalarItemTypeCheck: (itemSchema: JSONSchema, accessor: string) => string | null;
|
|
9
|
-
/**
|
|
10
|
-
* {@link scalarItemTypeCheck}, but only for an item schema whose `type` is the
|
|
11
|
-
* *whole* constraint — the one case where the bare `typeof` test is an exact
|
|
12
|
-
* match rather than a weakening of it. Emit sites that need a check they can
|
|
13
|
-
* trust in both directions (a true-sound fast-path guard, a strict assertion
|
|
14
|
-
* that throws) take this one; the coercion path keeps the looser
|
|
15
|
-
* {@link isCoercibleItemSchema}, which coerces each element through the full
|
|
16
|
-
* per-element expression and so applies the constraints anyway.
|
|
17
|
-
*/
|
|
18
|
-
export declare const plainScalarItemCheck: (itemSchema: JSONSchema, accessor: string) => string | null;
|
|
19
|
-
/**
|
|
20
|
-
* True when an array's `items` schema can be coerced element-by-element by the
|
|
21
|
-
* mapping slow path: single scalar types (via {@link scalarItemTypeCheck}) and
|
|
22
|
-
* enums (whose validation expression coerces a non-member to the first member).
|
|
23
|
-
* Anything richer (objects, unions, `$ref`s) needs a real item parser instead.
|
|
24
|
-
*/
|
|
25
|
-
export declare const isCoercibleItemSchema: (itemSchema: JSONSchema) => boolean;
|
|
26
|
-
/**
|
|
27
|
-
* The tuple positions a schema declares, else `null`. `prefixItems` (JSON
|
|
28
|
-
* Schema 2020-12) positions each element against its own subschema; draft-07
|
|
29
|
-
* spelled the same construct as an array-valued `items`, which every other item
|
|
30
|
-
* path skips (they all read `items` as a single schema), so it is normalized
|
|
31
|
-
* here rather than left unchecked.
|
|
32
|
-
*/
|
|
33
|
-
export declare const getPrefixItems: (schema: JSONSchema) => readonly JSONSchema[] | null;
|
|
34
|
-
/**
|
|
35
|
-
* Wraps a per-element boolean check into the `.every(...)` call that proves it
|
|
36
|
-
* for a whole array, skipping the positions `prefixItems` already owns.
|
|
37
|
-
*
|
|
38
|
-
* In 2020-12 `items` is only the *tail* schema: it applies from
|
|
39
|
-
* `prefixItems.length` onward. Checking every element against it made
|
|
40
|
-
* `{ prefixItems: [{type:'string'}], items: {type:'number'} }` reject
|
|
41
|
-
* `["a", 1, 2]` — a value Ajv accepts, and one the `[string?, ...number[]]` type
|
|
42
|
-
* this generator emits admits, so the strict parser threw on its own declared
|
|
43
|
-
* type. The index guard is short-circuited rather than sliced so the check stays
|
|
44
|
-
* allocation-free on the hot path.
|
|
45
|
-
*
|
|
46
|
-
* The draft-07 tuple spelling needs no guard: there `items` *is* the tuple and
|
|
47
|
-
* the tail lives in `additionalItems`, which no item check reads.
|
|
48
|
-
*/
|
|
49
|
-
export declare const everyTailItem: (accessor: string, itemCheck: string, schema: JSONSchema) => string;
|
|
50
|
-
/**
|
|
51
|
-
* True when a sibling `items: false` (2020-12) or `additionalItems: false`
|
|
52
|
-
* (draft) caps a tuple's length — any element past the declared prefix is
|
|
53
|
-
* disallowed, so the coercer drops it and strict mode rejects it.
|
|
54
|
-
*/
|
|
55
|
-
export declare const prefixItemsCapsLength: (schema: JSONSchema) => boolean;
|
|
56
|
-
/**
|
|
57
|
-
* A boolean type check for a single JSON Schema primitive type name. Unlike
|
|
58
|
-
* {@link scalarItemTypeCheck} this also covers `array`/`object` (as inline shape
|
|
59
|
-
* checks, so no `isObject` import is assumed) and enforces `integer` with
|
|
60
|
-
* `Number.isInteger` — a bare `typeof === "number"` accepts `1.5`. Used to build
|
|
61
|
-
* the disjunction for an array-form `type` (e.g. `["string","null"]`).
|
|
62
|
-
*/
|
|
63
|
-
export declare const singleTypeCheck: (accessor: string, type: string) => string | null;
|
|
64
|
-
/**
|
|
65
|
-
* Generates a validation expression that checks if a value matches the expected type.
|
|
66
|
-
* Returns the value if valid, coerces to the correct type if invalid, or uses default if required and missing.
|
|
67
|
-
* Supports type checking, pattern validation, enum validation, union types, and $ref resolution.
|
|
68
|
-
*/
|
|
69
|
-
export declare const generateValidationExpression: (key: string, schema: JSONSchema, defaultValue: string, isRequired: boolean, rootSchema?: Record<string, unknown>, visitedRefs?: Set<string>, accessorOverride?: string, knownNotUndefined?: boolean, caseInsensitive?: boolean) => string;
|
|
70
|
-
/**
|
|
71
|
-
* Builds the coercion transform for a tuple `prefixItems` array: an index-matched
|
|
72
|
-
* `.map` where each declared position runs through its own subschema's coercion
|
|
73
|
-
* (present positions only — a shorter input keeps its absent trailing positions),
|
|
74
|
-
* and every element past the tuple passes through unchanged. When `capLength` is
|
|
75
|
-
* set (a sibling `items: false`), a trailing `.slice(0, N)` drops those extras.
|
|
76
|
-
* `accessor` must already be known to be an array (the caller guards with
|
|
77
|
-
* `Array.isArray`). `$ref` positions resolve against `rootSchema` when provided.
|
|
78
|
-
*/
|
|
79
|
-
export declare const generatePrefixItemsMap: (accessor: string, prefix: readonly JSONSchema[], capLength: boolean, rootSchema?: Record<string, unknown>, visitedRefs?: Set<string>, caseInsensitive?: boolean) => string;
|
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
import { regexLiteral } from "@amritk/helpers/escape-regex-pattern";
|
|
2
|
-
import { getMjstInstanceOf, getMjstPrimitive } from "@amritk/helpers/mjst-extension";
|
|
3
|
-
import { multipleOfPassExpr } from "@amritk/helpers/multiple-of-check";
|
|
4
|
-
import { resolveRef } from "@amritk/helpers/resolve-ref";
|
|
5
|
-
import { hasOwnCheck, safeAccessor } from "@amritk/helpers/safe-accessor";
|
|
6
|
-
import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasOneOf, hasPattern, hasProperties, hasRef, hasRequired, hasType, hasUniqueItems, isSchemaObject } from "@amritk/helpers/schema-guards";
|
|
7
|
-
import { maxLengthPassExpr, minLengthPassExpr } from "@amritk/helpers/string-length-check";
|
|
8
|
-
import { findDiscriminator } from "../helpers/find-discriminator.js";
|
|
9
|
-
import { getDefaultValue } from "../helpers/get-default-value.js";
|
|
10
|
-
import { generateDiscriminatedUnionValidation } from "./generate-discriminated-union-validation.js";
|
|
11
|
-
import { generateEnumCaseInsensitiveCoercion, generateEnumCheck } from "./generate-enum-check.js";
|
|
12
|
-
import { generateNonDiscriminatedUnionValidation } from "./generate-non-discriminated-union-validation.js";
|
|
13
|
-
import { generateSchemaChecks } from "./generate-schema-checks.js";
|
|
14
|
-
import { generateUniqueItemsCheck } from "./generate-unique-items-check.js";
|
|
15
|
-
const getInstanceCoercion = (accessor, instanceOf, defaultValue) => {
|
|
16
|
-
if (instanceOf === "Date")
|
|
17
|
-
return `((_d) => Number.isNaN(_d.getTime()) ? ${defaultValue} : _d)(new Date(${accessor} as string | number | Date))`;
|
|
18
|
-
return null;
|
|
19
|
-
};
|
|
20
|
-
const scalarItemTypeCheck = (itemSchema, accessor) => {
|
|
21
|
-
if (!isSchemaObject(itemSchema) || !hasType(itemSchema) || hasEnum(itemSchema))
|
|
22
|
-
return null;
|
|
23
|
-
switch (itemSchema.type) {
|
|
24
|
-
case "string":
|
|
25
|
-
return `typeof ${accessor} === "string"`;
|
|
26
|
-
case "number":
|
|
27
|
-
return `typeof ${accessor} === "number"`;
|
|
28
|
-
case "integer":
|
|
29
|
-
return `typeof ${accessor} === "number" && Number.isInteger(${accessor})`;
|
|
30
|
-
case "boolean":
|
|
31
|
-
return `typeof ${accessor} === "boolean"`;
|
|
32
|
-
case "null":
|
|
33
|
-
return `${accessor} === null`;
|
|
34
|
-
default:
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
const SCALAR_ITEM_CONSTRAINTS = [
|
|
39
|
-
"pattern",
|
|
40
|
-
"minLength",
|
|
41
|
-
"maxLength",
|
|
42
|
-
"minimum",
|
|
43
|
-
"maximum",
|
|
44
|
-
"exclusiveMinimum",
|
|
45
|
-
"exclusiveMaximum",
|
|
46
|
-
"multipleOf",
|
|
47
|
-
"const",
|
|
48
|
-
"$ref",
|
|
49
|
-
"not",
|
|
50
|
-
"allOf",
|
|
51
|
-
"anyOf",
|
|
52
|
-
"oneOf",
|
|
53
|
-
"if"
|
|
54
|
-
];
|
|
55
|
-
const plainScalarItemCheck = (itemSchema, accessor) => {
|
|
56
|
-
if (!isSchemaObject(itemSchema))
|
|
57
|
-
return null;
|
|
58
|
-
const record = itemSchema;
|
|
59
|
-
if (SCALAR_ITEM_CONSTRAINTS.some((keyword) => keyword in record))
|
|
60
|
-
return null;
|
|
61
|
-
return scalarItemTypeCheck(itemSchema, accessor);
|
|
62
|
-
};
|
|
63
|
-
const isCoercibleItemSchema = (itemSchema) => {
|
|
64
|
-
if (scalarItemTypeCheck(itemSchema, "_it") !== null)
|
|
65
|
-
return true;
|
|
66
|
-
if (!isSchemaObject(itemSchema))
|
|
67
|
-
return false;
|
|
68
|
-
const itemTypes = itemSchema["type"];
|
|
69
|
-
if (Array.isArray(itemTypes) && itemTypes.length > 0) {
|
|
70
|
-
return itemTypes.every((type) => typeof type === "string" && scalarItemTypeCheck({ type }, "_it") !== null);
|
|
71
|
-
}
|
|
72
|
-
if (hasConst(itemSchema))
|
|
73
|
-
return true;
|
|
74
|
-
return hasEnum(itemSchema) && itemSchema.enum.length > 0;
|
|
75
|
-
};
|
|
76
|
-
const getPrefixItems = (schema) => {
|
|
77
|
-
if (!isSchemaObject(schema))
|
|
78
|
-
return null;
|
|
79
|
-
const prefix = schema["prefixItems"];
|
|
80
|
-
if (Array.isArray(prefix))
|
|
81
|
-
return prefix;
|
|
82
|
-
return Array.isArray(schema.items) ? schema.items : null;
|
|
83
|
-
};
|
|
84
|
-
const everyTailItem = (accessor, itemCheck, schema) => {
|
|
85
|
-
const prefix = isSchemaObject(schema) ? schema["prefixItems"] : void 0;
|
|
86
|
-
const tailStart = Array.isArray(prefix) ? prefix.length : 0;
|
|
87
|
-
return tailStart === 0 ? `${accessor}.every((_it: unknown) => ${itemCheck})` : `${accessor}.every((_it: unknown, _ix: number) => _ix < ${tailStart} || (${itemCheck}))`;
|
|
88
|
-
};
|
|
89
|
-
const prefixItemsCapsLength = (schema) => {
|
|
90
|
-
if (!isSchemaObject(schema))
|
|
91
|
-
return false;
|
|
92
|
-
const s = schema;
|
|
93
|
-
return (Object.hasOwn(s, "items") ? s["items"] : void 0) === false || (Object.hasOwn(s, "additionalItems") ? s["additionalItems"] : void 0) === false;
|
|
94
|
-
};
|
|
95
|
-
const singleTypeCheck = (accessor, type) => {
|
|
96
|
-
switch (type) {
|
|
97
|
-
case "string":
|
|
98
|
-
return `typeof ${accessor} === "string"`;
|
|
99
|
-
case "number":
|
|
100
|
-
return `typeof ${accessor} === "number"`;
|
|
101
|
-
case "integer":
|
|
102
|
-
return `typeof ${accessor} === "number" && Number.isInteger(${accessor})`;
|
|
103
|
-
case "boolean":
|
|
104
|
-
return `typeof ${accessor} === "boolean"`;
|
|
105
|
-
case "null":
|
|
106
|
-
return `${accessor} === null`;
|
|
107
|
-
case "array":
|
|
108
|
-
return `Array.isArray(${accessor})`;
|
|
109
|
-
case "object":
|
|
110
|
-
return `typeof ${accessor} === "object" && ${accessor} !== null && !Array.isArray(${accessor})`;
|
|
111
|
-
default:
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
const getTypeArray = (schema) => {
|
|
116
|
-
if (!isSchemaObject(schema) || !("type" in schema) || !Array.isArray(schema.type))
|
|
117
|
-
return null;
|
|
118
|
-
return schema.type;
|
|
119
|
-
};
|
|
120
|
-
const stringBoundChecks = (valueExpr, schema) => {
|
|
121
|
-
const checks = [];
|
|
122
|
-
if (hasPattern(schema))
|
|
123
|
-
checks.push(`${regexLiteral(schema.pattern)}.test(${valueExpr})`);
|
|
124
|
-
if (hasMinLength(schema))
|
|
125
|
-
checks.push(minLengthPassExpr(valueExpr, schema.minLength));
|
|
126
|
-
if (hasMaxLength(schema))
|
|
127
|
-
checks.push(maxLengthPassExpr(valueExpr, schema.maxLength));
|
|
128
|
-
return checks;
|
|
129
|
-
};
|
|
130
|
-
const numberBoundChecks = (valueExpr, schema) => {
|
|
131
|
-
const checks = [];
|
|
132
|
-
if (hasMinimum(schema))
|
|
133
|
-
checks.push(`${valueExpr} >= ${schema.minimum}`);
|
|
134
|
-
if (hasMaximum(schema))
|
|
135
|
-
checks.push(`${valueExpr} <= ${schema.maximum}`);
|
|
136
|
-
if (hasExclusiveMinimum(schema))
|
|
137
|
-
checks.push(`${valueExpr} > ${schema.exclusiveMinimum}`);
|
|
138
|
-
if (hasExclusiveMaximum(schema))
|
|
139
|
-
checks.push(`${valueExpr} < ${schema.exclusiveMaximum}`);
|
|
140
|
-
if (hasMultipleOf(schema))
|
|
141
|
-
checks.push(multipleOfPassExpr(valueExpr, schema.multipleOf));
|
|
142
|
-
return checks;
|
|
143
|
-
};
|
|
144
|
-
const BOOLEAN_TRUE_TOKENS = ["true", "yes", "y", "on", "1"];
|
|
145
|
-
const BOOLEAN_FALSE_TOKENS = ["false", "no", "n", "off", "0", ""];
|
|
146
|
-
const getBooleanCoercion = (accessor, defaultValue) => {
|
|
147
|
-
const side = (tokens, literals) => [...tokens.map((token) => `"${token}"`), ...literals].map((term) => `_b === ${term}`).join(" || ");
|
|
148
|
-
const normalized = `typeof ${accessor} === "string" ? ${accessor}.trim().toLowerCase() : ${accessor}`;
|
|
149
|
-
const truthy = side(BOOLEAN_TRUE_TOKENS, ["1", "true"]);
|
|
150
|
-
const falsy = side(BOOLEAN_FALSE_TOKENS, ["0", "false"]);
|
|
151
|
-
return `((_b) => ${truthy} ? true : ${falsy} ? false : ${defaultValue})(${normalized})`;
|
|
152
|
-
};
|
|
153
|
-
const getTypeCoercion = (accessor, schema, defaultValue) => {
|
|
154
|
-
if (!hasType(schema)) {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
switch (schema.type) {
|
|
158
|
-
case "string": {
|
|
159
|
-
const coerced = `String(${accessor})`;
|
|
160
|
-
const bounds = stringBoundChecks("_s", schema);
|
|
161
|
-
if (bounds.length === 0)
|
|
162
|
-
return coerced;
|
|
163
|
-
return `((_s) => ${bounds.join(" && ")} ? _s : ${defaultValue})(${coerced})`;
|
|
164
|
-
}
|
|
165
|
-
case "number":
|
|
166
|
-
case "integer": {
|
|
167
|
-
const wellFormed = schema.type === "integer" ? "Number.isInteger" : "Number.isFinite";
|
|
168
|
-
const bounds = numberBoundChecks("_n", schema);
|
|
169
|
-
const n = `Number(${accessor})`;
|
|
170
|
-
if (bounds.length === 0)
|
|
171
|
-
return `(${wellFormed}(${n}) ? ${n} : ${defaultValue})`;
|
|
172
|
-
return `((_n) => ${[`${wellFormed}(_n)`, ...bounds].join(" && ")} ? _n : ${defaultValue})(${n})`;
|
|
173
|
-
}
|
|
174
|
-
case "boolean":
|
|
175
|
-
return getBooleanCoercion(accessor, defaultValue);
|
|
176
|
-
case "array":
|
|
177
|
-
return defaultValue;
|
|
178
|
-
case "object":
|
|
179
|
-
return `typeof ${accessor} === "object" && ${accessor} !== null ? ${accessor} : ${defaultValue}`;
|
|
180
|
-
default:
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
const generateValidationExpression = (key, schema, defaultValue, isRequired, rootSchema, visitedRefs, accessorOverride, knownNotUndefined, caseInsensitive) => {
|
|
185
|
-
const accessor = accessorOverride ?? safeAccessor("input?", key);
|
|
186
|
-
const checks = [];
|
|
187
|
-
if (!isSchemaObject(schema)) {
|
|
188
|
-
return isRequired ? `${accessor} ?? ${defaultValue}` : accessor;
|
|
189
|
-
}
|
|
190
|
-
const instanceOf = getMjstInstanceOf(schema);
|
|
191
|
-
if (instanceOf) {
|
|
192
|
-
const valid = `${accessor} instanceof ${instanceOf}`;
|
|
193
|
-
if (isRequired) {
|
|
194
|
-
const coercion2 = getInstanceCoercion(accessor, instanceOf, defaultValue);
|
|
195
|
-
return coercion2 ? `${valid} ? ${accessor} : (${accessor} !== undefined ? ${coercion2} : ${defaultValue})` : `${valid} ? ${accessor} : ${defaultValue}`;
|
|
196
|
-
}
|
|
197
|
-
const coercion = getInstanceCoercion(accessor, instanceOf, "undefined");
|
|
198
|
-
return coercion ? `${valid} ? ${accessor} : (${accessor} !== undefined ? ${coercion} : undefined)` : `${valid} ? ${accessor} : undefined`;
|
|
199
|
-
}
|
|
200
|
-
const primitive = getMjstPrimitive(schema);
|
|
201
|
-
if (primitive) {
|
|
202
|
-
const valid = `typeof ${accessor} === "${primitive}"`;
|
|
203
|
-
return isRequired ? `${valid} ? ${accessor} : ${defaultValue}` : `${valid} ? ${accessor} : undefined`;
|
|
204
|
-
}
|
|
205
|
-
if (hasRef(schema) && rootSchema) {
|
|
206
|
-
const ref = schema.$ref;
|
|
207
|
-
if (visitedRefs?.has(ref)) {
|
|
208
|
-
return isRequired ? `${accessor} ?? ${defaultValue}` : accessor;
|
|
209
|
-
}
|
|
210
|
-
const resolvedSchema = resolveRef(ref, rootSchema);
|
|
211
|
-
if (resolvedSchema) {
|
|
212
|
-
const visited = new Set(visitedRefs);
|
|
213
|
-
visited.add(ref);
|
|
214
|
-
return generateValidationExpression(key, resolvedSchema, defaultValue, isRequired, rootSchema, visited, void 0, void 0, caseInsensitive);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (hasOneOf(schema) && schema.oneOf.length > 0) {
|
|
218
|
-
const discriminator = findDiscriminator(schema.oneOf);
|
|
219
|
-
if (discriminator) {
|
|
220
|
-
return generateDiscriminatedUnionValidation(accessor, schema.oneOf, discriminator, defaultValue, isRequired);
|
|
221
|
-
}
|
|
222
|
-
return generateNonDiscriminatedUnionValidation(accessor, schema.oneOf, defaultValue, isRequired);
|
|
223
|
-
}
|
|
224
|
-
if (hasAnyOf(schema) && schema.anyOf.length > 0) {
|
|
225
|
-
const discriminator = findDiscriminator(schema.anyOf);
|
|
226
|
-
if (discriminator) {
|
|
227
|
-
return generateDiscriminatedUnionValidation(accessor, schema.anyOf, discriminator, defaultValue, isRequired);
|
|
228
|
-
}
|
|
229
|
-
return generateNonDiscriminatedUnionValidation(accessor, schema.anyOf, defaultValue, isRequired);
|
|
230
|
-
}
|
|
231
|
-
if (hasAllOf(schema) && schema.allOf.length > 0) {
|
|
232
|
-
const allChecks = [];
|
|
233
|
-
for (const subSchema of schema.allOf) {
|
|
234
|
-
const subChecks = generateSchemaChecks(accessor, subSchema);
|
|
235
|
-
allChecks.push(...subChecks);
|
|
236
|
-
}
|
|
237
|
-
if (allChecks.length > 0) {
|
|
238
|
-
let combinedCheck2 = allChecks[0];
|
|
239
|
-
for (let i = 1; i < allChecks.length; i++) {
|
|
240
|
-
combinedCheck2 += " && " + allChecks[i];
|
|
241
|
-
}
|
|
242
|
-
return `${combinedCheck2} ? ${accessor} : ${isRequired ? defaultValue : "undefined"}`;
|
|
243
|
-
}
|
|
244
|
-
return isRequired ? `${accessor} ?? ${defaultValue}` : accessor;
|
|
245
|
-
}
|
|
246
|
-
if ("not" in schema && schema.not) {
|
|
247
|
-
const notChecks = generateSchemaChecks(accessor, schema.not);
|
|
248
|
-
if (notChecks.length > 0) {
|
|
249
|
-
let combinedNotCheck = notChecks[0];
|
|
250
|
-
for (let i = 1; i < notChecks.length; i++) {
|
|
251
|
-
combinedNotCheck += " && " + notChecks[i];
|
|
252
|
-
}
|
|
253
|
-
return `!(${combinedNotCheck}) ? ${accessor} : ${isRequired ? defaultValue : "undefined"}`;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (hasConst(schema)) {
|
|
257
|
-
const constLiteral = JSON.stringify(schema.const);
|
|
258
|
-
return isRequired ? `${accessor} === ${constLiteral} ? ${accessor} : ${constLiteral}` : `${accessor} === ${constLiteral} ? ${accessor} : (${accessor} !== undefined ? ${constLiteral} : undefined)`;
|
|
259
|
-
}
|
|
260
|
-
const typeArray = getTypeArray(schema);
|
|
261
|
-
if (typeArray && !hasEnum(schema)) {
|
|
262
|
-
const checks2 = typeArray.map((t) => singleTypeCheck(accessor, t)).filter((c) => c !== null);
|
|
263
|
-
if (checks2.length > 0) {
|
|
264
|
-
const combined = checks2.map((c) => `(${c})`).join(" || ");
|
|
265
|
-
return isRequired ? `${combined} ? ${accessor} : ${defaultValue}` : `${combined} ? ${accessor} : undefined`;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if (!hasType(schema) && !hasEnum(schema)) {
|
|
269
|
-
return isRequired ? `${accessor} ?? ${defaultValue}` : accessor;
|
|
270
|
-
}
|
|
271
|
-
const prefixItems = getPrefixItems(schema);
|
|
272
|
-
if (hasType(schema) && schema.type === "array" && prefixItems) {
|
|
273
|
-
const mapped = generatePrefixItemsMap(accessor, prefixItems, prefixItemsCapsLength(schema), rootSchema, visitedRefs, caseInsensitive);
|
|
274
|
-
const built = `(Array.isArray(${accessor}) ? ${mapped} : ${defaultValue})`;
|
|
275
|
-
return isRequired ? built : `(${accessor} !== undefined ? ${built} : undefined)`;
|
|
276
|
-
}
|
|
277
|
-
if (hasType(schema) && schema.type === "array" && hasItems(schema) && !Array.isArray(schema.items) && isCoercibleItemSchema(schema.items)) {
|
|
278
|
-
const itemSchema = schema.items;
|
|
279
|
-
const itemExpr = generateValidationExpression("", itemSchema, getDefaultValue(itemSchema), true, rootSchema, visitedRefs, "_it", true, caseInsensitive);
|
|
280
|
-
const mapped = `(Array.isArray(${accessor}) ? (${accessor} as unknown[]).map((_it) => ${itemExpr}) : ${defaultValue})`;
|
|
281
|
-
return isRequired ? mapped : `(${accessor} !== undefined ? ${mapped} : undefined)`;
|
|
282
|
-
}
|
|
283
|
-
if (hasType(schema)) {
|
|
284
|
-
switch (schema.type) {
|
|
285
|
-
case "string": {
|
|
286
|
-
checks.push(`typeof ${accessor} === "string"`);
|
|
287
|
-
if (hasPattern(schema)) {
|
|
288
|
-
checks.push(`${regexLiteral(schema.pattern)}.test(${accessor})`);
|
|
289
|
-
}
|
|
290
|
-
if (hasMinLength(schema)) {
|
|
291
|
-
checks.push(minLengthPassExpr(accessor, schema.minLength));
|
|
292
|
-
}
|
|
293
|
-
if (hasMaxLength(schema)) {
|
|
294
|
-
checks.push(maxLengthPassExpr(accessor, schema.maxLength));
|
|
295
|
-
}
|
|
296
|
-
break;
|
|
297
|
-
}
|
|
298
|
-
case "number":
|
|
299
|
-
case "integer": {
|
|
300
|
-
checks.push(`typeof ${accessor} === "number"`);
|
|
301
|
-
if (schema.type === "integer") {
|
|
302
|
-
checks.push(`Number.isInteger(${accessor})`);
|
|
303
|
-
}
|
|
304
|
-
if (hasMinimum(schema)) {
|
|
305
|
-
checks.push(`${accessor} >= ${schema.minimum}`);
|
|
306
|
-
}
|
|
307
|
-
if (hasMaximum(schema)) {
|
|
308
|
-
checks.push(`${accessor} <= ${schema.maximum}`);
|
|
309
|
-
}
|
|
310
|
-
if (hasExclusiveMinimum(schema)) {
|
|
311
|
-
checks.push(`${accessor} > ${schema.exclusiveMinimum}`);
|
|
312
|
-
}
|
|
313
|
-
if (hasExclusiveMaximum(schema)) {
|
|
314
|
-
checks.push(`${accessor} < ${schema.exclusiveMaximum}`);
|
|
315
|
-
}
|
|
316
|
-
if (hasMultipleOf(schema)) {
|
|
317
|
-
checks.push(multipleOfPassExpr(accessor, schema.multipleOf));
|
|
318
|
-
}
|
|
319
|
-
break;
|
|
320
|
-
}
|
|
321
|
-
case "boolean":
|
|
322
|
-
checks.push(`typeof ${accessor} === "boolean"`);
|
|
323
|
-
break;
|
|
324
|
-
case "null":
|
|
325
|
-
checks.push(`${accessor} === null`);
|
|
326
|
-
break;
|
|
327
|
-
case "array": {
|
|
328
|
-
checks.push(`Array.isArray(${accessor})`);
|
|
329
|
-
if (hasMinItems(schema)) {
|
|
330
|
-
checks.push(`${accessor}.length >= ${schema.minItems}`);
|
|
331
|
-
}
|
|
332
|
-
if (hasMaxItems(schema)) {
|
|
333
|
-
checks.push(`${accessor}.length <= ${schema.maxItems}`);
|
|
334
|
-
}
|
|
335
|
-
if (hasUniqueItems(schema) && schema.uniqueItems === true) {
|
|
336
|
-
checks.push(generateUniqueItemsCheck(accessor, schema));
|
|
337
|
-
}
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
case "object": {
|
|
341
|
-
checks.push(`isObject(${accessor})`);
|
|
342
|
-
if (hasRequired(schema) && schema.required.length > 0) {
|
|
343
|
-
for (const requiredKey of schema.required) {
|
|
344
|
-
checks.push(hasOwnCheck(accessor, requiredKey));
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
if (hasMinProperties(schema)) {
|
|
348
|
-
checks.push(`Object.keys(${accessor}).length >= ${schema.minProperties}`);
|
|
349
|
-
}
|
|
350
|
-
if (hasMaxProperties(schema)) {
|
|
351
|
-
checks.push(`Object.keys(${accessor}).length <= ${schema.maxProperties}`);
|
|
352
|
-
}
|
|
353
|
-
if (hasAdditionalProperties(schema) && schema.additionalProperties === false && hasProperties(schema)) {
|
|
354
|
-
const allowedKeys = Object.keys(schema.properties);
|
|
355
|
-
const allowedKeysStr = JSON.stringify(allowedKeys);
|
|
356
|
-
checks.push(`Object.keys(${accessor}).every(k => ${allowedKeysStr}.includes(k))`);
|
|
357
|
-
}
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
if (hasEnum(schema) && schema.enum.length > 0) {
|
|
363
|
-
checks.push(generateEnumCheck(accessor, schema.enum));
|
|
364
|
-
}
|
|
365
|
-
if (checks.length === 0) {
|
|
366
|
-
return isRequired ? `${accessor} ?? ${defaultValue}` : accessor;
|
|
367
|
-
}
|
|
368
|
-
let combinedCheck = checks[0];
|
|
369
|
-
for (let i = 1; i < checks.length; i++) {
|
|
370
|
-
combinedCheck += " && " + checks[i];
|
|
371
|
-
}
|
|
372
|
-
const typeCoercion = hasEnum(schema) ? null : getTypeCoercion(accessor, schema, defaultValue);
|
|
373
|
-
const enumFallback = (fallback) => (caseInsensitive && hasEnum(schema) && schema.enum.length > 0 ? generateEnumCaseInsensitiveCoercion(accessor, schema.enum, fallback) : null) ?? fallback;
|
|
374
|
-
if (isRequired) {
|
|
375
|
-
if (typeCoercion) {
|
|
376
|
-
if (knownNotUndefined) {
|
|
377
|
-
return `${combinedCheck} ? ${accessor} : ${typeCoercion}`;
|
|
378
|
-
}
|
|
379
|
-
return `${combinedCheck} ? ${accessor} : (${accessor} !== undefined ? ${typeCoercion} : ${defaultValue})`;
|
|
380
|
-
}
|
|
381
|
-
return `${combinedCheck} ? ${accessor} : ${enumFallback(defaultValue)}`;
|
|
382
|
-
} else {
|
|
383
|
-
if (typeCoercion) {
|
|
384
|
-
if (knownNotUndefined) {
|
|
385
|
-
return `${combinedCheck} ? ${accessor} : ${typeCoercion}`;
|
|
386
|
-
}
|
|
387
|
-
return `${combinedCheck} ? ${accessor} : (${accessor} !== undefined ? ${typeCoercion} : undefined)`;
|
|
388
|
-
}
|
|
389
|
-
return `${combinedCheck} ? ${accessor} : ${enumFallback("undefined")}`;
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
const generatePrefixItemsMap = (accessor, prefix, capLength, rootSchema, visitedRefs, caseInsensitive) => {
|
|
393
|
-
const branches = prefix.map((rawPos, i) => {
|
|
394
|
-
let pos = rawPos;
|
|
395
|
-
if (isSchemaObject(pos) && hasRef(pos) && rootSchema) {
|
|
396
|
-
const resolved = resolveRef(pos.$ref, rootSchema);
|
|
397
|
-
if (resolved)
|
|
398
|
-
pos = resolved;
|
|
399
|
-
}
|
|
400
|
-
const posExpr = generateValidationExpression("", pos, getDefaultValue(pos), true, rootSchema, visitedRefs, "_el", true, caseInsensitive);
|
|
401
|
-
return `_i === ${i} ? ${posExpr}`;
|
|
402
|
-
});
|
|
403
|
-
const mapped = `(${accessor} as unknown[]).map((_el, _i) => ${[...branches, "_el"].join(" : ")})`;
|
|
404
|
-
return capLength ? `${mapped}.slice(0, ${prefix.length})` : mapped;
|
|
405
|
-
};
|
|
406
|
-
export {
|
|
407
|
-
everyTailItem,
|
|
408
|
-
generatePrefixItemsMap,
|
|
409
|
-
generateValidationExpression,
|
|
410
|
-
getPrefixItems,
|
|
411
|
-
isCoercibleItemSchema,
|
|
412
|
-
plainScalarItemCheck,
|
|
413
|
-
prefixItemsCapsLength,
|
|
414
|
-
scalarItemTypeCheck,
|
|
415
|
-
singleTypeCheck
|
|
416
|
-
};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
|
2
|
-
/**
|
|
3
|
-
* What the matcher needs beyond the schema node itself: the root document (to
|
|
4
|
-
* resolve `$ref`), the recursion depth (which seeds collision-free loop variable
|
|
5
|
-
* names and caps expression growth), and the `$ref` pointers already open on this
|
|
6
|
-
* path (a cycle cannot be inlined, so it bails rather than recursing forever).
|
|
7
|
-
*/
|
|
8
|
-
export type MatchContext = {
|
|
9
|
-
readonly rootSchema?: Record<string, unknown>;
|
|
10
|
-
readonly depth?: number;
|
|
11
|
-
readonly seen?: ReadonlySet<string>;
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* Builds a boolean expression that is `true` *exactly* when `accessor` matches
|
|
15
|
-
* `schema` — sound in **both** directions (true ⇒ valid, false ⇒ invalid) — or
|
|
16
|
-
* `null` when the schema uses a form this generator cannot prove inline.
|
|
17
|
-
*
|
|
18
|
-
* This is deliberately self-contained: it never emits a `$ref` shape-validator
|
|
19
|
-
* call (those can be conservative `=> false` stubs, which would break false-
|
|
20
|
-
* soundness) and never touches combinators. That keeps the expression usable
|
|
21
|
-
* both for *counting* matches (`contains`) and for *rejecting* non-matches
|
|
22
|
-
* (`propertyNames`, `dependentSchemas`) in strict mode, where a wrong verdict
|
|
23
|
-
* would silently accept or reject a document. Anything richer returns `null`,
|
|
24
|
-
* and `assertNoUnsupportedKeywords` turns that into a generation-time error.
|
|
25
|
-
*
|
|
26
|
-
* Constraint keywords apply per JSON Schema semantics: a `pattern`/`maxLength`
|
|
27
|
-
* with no `type` still constrains string instances (and is a no-op for other
|
|
28
|
-
* types), so a type-less schema guards each family by its runtime type rather
|
|
29
|
-
* than ignoring it — the subtlety `propertyNames: { maxLength: 3 }` depends on.
|
|
30
|
-
*
|
|
31
|
-
* `ctx.depth` seeds the loop variable names so nested walks never shadow one
|
|
32
|
-
* another, and caps how far the expression may grow.
|
|
33
|
-
*/
|
|
34
|
-
export declare const subschemaMatchExpr: (accessor: string, schema: JSONSchema, ctx?: MatchContext) => string | null;
|
|
35
|
-
/**
|
|
36
|
-
* True when a `$ref` node carries a keyword that constrains the value on its own.
|
|
37
|
-
* 2020-12 applies a `$ref`'s siblings alongside the target, so delegating the
|
|
38
|
-
* whole node to the referenced parser (which knows nothing about them) drops
|
|
39
|
-
* every one of them — and a fast path built from the target's shape validator
|
|
40
|
-
* skips them too.
|
|
41
|
-
*/
|
|
42
|
-
export declare const hasConstrainingRefSibling: (schema: JSONSchema) => boolean;
|
|
43
|
-
/**
|
|
44
|
-
* Whether {@link subschemaMatchExpr} can build an exact matcher for `schema`.
|
|
45
|
-
* The generation-time guard uses this to decide whether a `contains`,
|
|
46
|
-
* `propertyNames`, or `dependentSchemas` subschema is enforceable.
|
|
47
|
-
*/
|
|
48
|
-
export declare const canMatchSubschema: (schema: JSONSchema, rootSchema?: Record<string, unknown>) => boolean;
|