@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,148 +0,0 @@
|
|
|
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
|
-
* Matches an inline nested object property — an object schema written directly
|
|
23
|
-
* under `properties` with its own `properties`, rather than referenced via
|
|
24
|
-
* `$ref`. These get a private sub-parser (and shape predicate) in the same
|
|
25
|
-
* generated file so their fields are actually parsed; anything involving
|
|
26
|
-
* composition, conditionals, enums, or record semantics keeps the existing
|
|
27
|
-
* code paths.
|
|
28
|
-
*/
|
|
29
|
-
export declare const isInlineObjectProperty: (propSchema: JSONSchema) => propSchema is JSONSchema.Object;
|
|
30
|
-
/**
|
|
31
|
-
* Matches an array property whose `items` is an inline object schema — the
|
|
32
|
-
* array-items analogue of {@link isInlineObjectProperty}. These get a private
|
|
33
|
-
* item sub-parser (and shape predicate) so element values — including nested
|
|
34
|
-
* enums and `$ref`s — are actually validated instead of only passing an
|
|
35
|
-
* `Array.isArray` check.
|
|
36
|
-
*/
|
|
37
|
-
export declare const isInlineObjectArrayProperty: (propSchema: JSONSchema) => boolean;
|
|
38
|
-
/**
|
|
39
|
-
* Matches an array property whose `items` is a *union* of object branches — the
|
|
40
|
-
* shape a coercing parser needs a private item sub-parser for, so an element can
|
|
41
|
-
* be dispatched to (and repaired toward) a branch rather than passed through.
|
|
42
|
-
* {@link isInlineObjectArrayProperty} deliberately does not cover this: a union
|
|
43
|
-
* is not one inline object, and the trust walk reads that predicate to decide
|
|
44
|
-
* what a shape validator proves.
|
|
45
|
-
*/
|
|
46
|
-
export declare const isUnionArrayProperty: (propSchema: JSONSchema) => boolean;
|
|
47
|
-
/**
|
|
48
|
-
* Matches a union written directly as a property value — `logo: { anyOf: [...] }`
|
|
49
|
-
* rather than a `$ref`, a definition, or an array's `items`. This is the one
|
|
50
|
-
* union position the coercing dispatcher did not reach, so a value matching no
|
|
51
|
-
* branch was passed through unrepaired.
|
|
52
|
-
*
|
|
53
|
-
* The union's *own* keywords are the reason for the exclusions: 2020-12 applies
|
|
54
|
-
* them alongside the branches, and a dispatcher expresses none of them, so a
|
|
55
|
-
* constrained union keeps the general path rather than getting a parser that
|
|
56
|
-
* quietly drops the constraint.
|
|
57
|
-
*/
|
|
58
|
-
export declare const isInlineUnionProperty: (propSchema: JSONSchema) => boolean;
|
|
59
|
-
/**
|
|
60
|
-
* Extracts the branch list of a `oneOf`/`anyOf` union, or `null` when the
|
|
61
|
-
* schema is not a union (or mixes in other composition keywords we cannot
|
|
62
|
-
* turn into a membership check).
|
|
63
|
-
*/
|
|
64
|
-
export declare const getUnionBranches: (schema: JSONSchema) => readonly JSONSchema[] | null;
|
|
65
|
-
/**
|
|
66
|
-
* True when {@link getUnionBranches} came from a `oneOf` rather than an `anyOf` —
|
|
67
|
-
* i.e. the value must match *exactly one* branch, not at least one.
|
|
68
|
-
*
|
|
69
|
-
* The distinction was dropped before: both spellings produced a plain
|
|
70
|
-
* disjunction, so `{ oneOf: [{…a}, {…b}] }` accepted `{ a: 'x', b: 1 }`, which
|
|
71
|
-
* matches both branches. Ajv, the runtime interpreter, and `generate-validators`
|
|
72
|
-
* all reject it, so the two mjst engines disagreed on the same contract.
|
|
73
|
-
*/
|
|
74
|
-
export declare const isExclusiveUnion: (schema: JSONSchema) => boolean;
|
|
75
|
-
/**
|
|
76
|
-
* True when a schema carries a keyword the strict slow path enforces but which
|
|
77
|
-
* no flat type check can mirror: `contains` (array item counting),
|
|
78
|
-
* `dependentRequired` / `dependentSchemas` (cross-property presence),
|
|
79
|
-
* `propertyNames` (per-key constraints), the conditional and composition
|
|
80
|
-
* keywords (`if`/`then`/`else`, `not`, a constraining `allOf`), or per-key value
|
|
81
|
-
* schemas (`patternProperties`, a constraining `additionalProperties`). A fast
|
|
82
|
-
* path or shape validator that accepted such a value on a bare type check would
|
|
83
|
-
* skip the enforcement, so both must bail when this is true — the slow path then
|
|
84
|
-
* runs the real checks.
|
|
85
|
-
*/
|
|
86
|
-
export declare const hasFastPathBlockingKeyword: (schema: JSONSchema) => boolean;
|
|
87
|
-
/**
|
|
88
|
-
* The membership check for an array-form `type` (the multi-type / nullable
|
|
89
|
-
* idiom, e.g. `["string","null"]`): the disjunction of the per-type checks.
|
|
90
|
-
* Returns `undefined` when the schema has no array `type`, and `null` when it
|
|
91
|
-
* has one that cannot be checked soundly — a type name we do not model, or a
|
|
92
|
-
* constraint keyword a bare `typeof` would skip.
|
|
93
|
-
*
|
|
94
|
-
* Without this every nullable property fell through to `hasType`, which is false
|
|
95
|
-
* for an array `type`, and produced no check at all: no fast path (so the shape
|
|
96
|
-
* validator degraded to the `=> false` stub) and, in strict mode, nothing to
|
|
97
|
-
* throw on.
|
|
98
|
-
*/
|
|
99
|
-
export declare const multiTypeCheck: (varName: string, schema: JSONSchema, options?: {
|
|
100
|
-
readonly ignoreConstraints?: boolean;
|
|
101
|
-
}) => string | null | undefined;
|
|
102
|
-
/**
|
|
103
|
-
* Generates a fast-path type check expression for a property.
|
|
104
|
-
* Returns null if the schema is too complex for a simple type check.
|
|
105
|
-
*
|
|
106
|
-
* When `useRefImports` is true, $ref properties (and arrays of $refs) are
|
|
107
|
-
* checked by calling the imported shape predicate, allowing parent parsers
|
|
108
|
-
* to fast-path through nested ref types without recursing into their parsers.
|
|
109
|
-
*/
|
|
110
|
-
export declare const generatePropertyTypeCheck: (varName: string, schema: JSONSchema, useRefImports: boolean, suffix: string) => string | null;
|
|
111
|
-
/**
|
|
112
|
-
* Generates a boolean check that `varName` matches an inline object schema —
|
|
113
|
-
* `isObject` plus a per-property check for every declared property (and a
|
|
114
|
-
* presence check for `required` keys without a declared schema). Used for
|
|
115
|
-
* union branches written inline, where no named shape validator exists to
|
|
116
|
-
* call. Returns null when any property is too complex to check.
|
|
117
|
-
*/
|
|
118
|
-
export declare const generateInlineObjectCheck: (varName: string, schema: JSONSchema, useRefImports: boolean, suffix: string) => string | null;
|
|
119
|
-
/**
|
|
120
|
-
* Generates a union membership check. Inline object branches get a per-property
|
|
121
|
-
* check; everything else goes through {@link generatePropertyTypeCheck} ($refs
|
|
122
|
-
* call the imported shape validator, consts/enums/scalars check directly).
|
|
123
|
-
* Returns null when any branch cannot be checked.
|
|
124
|
-
*
|
|
125
|
-
* `exclusive` (a `oneOf` — see {@link isExclusiveUnion}) counts the matching
|
|
126
|
-
* branches and demands exactly one, instead of the `anyOf` disjunction. The count
|
|
127
|
-
* form is *tighter* than the disjunction it replaces — anything it accepts, the
|
|
128
|
-
* disjunction accepted too — so a true-sound caller (a fast path, a shape
|
|
129
|
-
* validator) can only get safer: a value it now declines merely takes the slow
|
|
130
|
-
* path, which re-validates. A caller that *throws* on a false result needs each
|
|
131
|
-
* branch check to be exact in both directions, which is precisely what
|
|
132
|
-
* {@link canEnforceUnion} certifies.
|
|
133
|
-
*/
|
|
134
|
-
export declare const generateUnionCheck: (varName: string, branches: readonly JSONSchema[], useRefImports: boolean, suffix: string, exclusive?: boolean) => string | null;
|
|
135
|
-
/**
|
|
136
|
-
* True when the union membership check emitted by {@link generateUnionCheck}
|
|
137
|
-
* is also *false-sound* — a false result definitely means the value is invalid
|
|
138
|
-
* — so strict mode may throw on it.
|
|
139
|
-
*
|
|
140
|
-
* The risk is `$ref`s: a branch (or a property inside one) whose check calls
|
|
141
|
-
* an imported `validate{X}Shape` is only trustworthy when that validator was
|
|
142
|
-
* generated as a real predicate. This walks every reachable ref (mirroring
|
|
143
|
-
* generateShapeValidator's stub conditions) and rejects the union when any of
|
|
144
|
-
* them would have produced the conservative `=> false` stub, or is itself
|
|
145
|
-
* built on one. Cycles (e.g. a recursive `expr` union) are assumed sound —
|
|
146
|
-
* the recursion bottoms out on the data, not the schema.
|
|
147
|
-
*/
|
|
148
|
-
export declare const canEnforceUnion: (branches: readonly JSONSchema[], rootSchema: Record<string, unknown> | undefined) => boolean;
|
|
@@ -1,454 +0,0 @@
|
|
|
1
|
-
import { regexLiteral } from "@amritk/helpers/escape-regex-pattern";
|
|
2
|
-
import { multipleOfPassExpr } from "@amritk/helpers/multiple-of-check";
|
|
3
|
-
import { refToName } from "@amritk/helpers/ref-to-name";
|
|
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, isObjectSchema, isSchemaObject } from "@amritk/helpers/schema-guards";
|
|
7
|
-
import { maxLengthPassExpr, minLengthPassExpr } from "@amritk/helpers/string-length-check";
|
|
8
|
-
import { generateEnumCheck } from "./generate-enum-check.js";
|
|
9
|
-
import { generateUniqueItemsCheck } from "./generate-unique-items-check.js";
|
|
10
|
-
import { everyTailItem, getPrefixItems, plainScalarItemCheck, prefixItemsCapsLength, singleTypeCheck } from "./generate-validation-expression.js";
|
|
11
|
-
import { hasConstrainingRefSibling, subschemaMatchExpr } from "./subschema-match.js";
|
|
12
|
-
const shapeValidatorName = (typeName) => `validate${typeName}Shape`;
|
|
13
|
-
const isInlineObjectProperty = (propSchema) => {
|
|
14
|
-
if (!isSchemaObject(propSchema))
|
|
15
|
-
return false;
|
|
16
|
-
if (hasRef(propSchema) || hasEnum(propSchema) || hasConst(propSchema))
|
|
17
|
-
return false;
|
|
18
|
-
if (Array.isArray(propSchema.type))
|
|
19
|
-
return false;
|
|
20
|
-
if (hasOneOf(propSchema) || hasAnyOf(propSchema) || hasAllOf(propSchema))
|
|
21
|
-
return false;
|
|
22
|
-
if ("patternProperties" in propSchema || "not" in propSchema)
|
|
23
|
-
return false;
|
|
24
|
-
if ("if" in propSchema || "then" in propSchema || "else" in propSchema)
|
|
25
|
-
return false;
|
|
26
|
-
if (hasAdditionalProperties(propSchema) && typeof propSchema.additionalProperties !== "boolean")
|
|
27
|
-
return false;
|
|
28
|
-
return isObjectSchema(propSchema) && hasProperties(propSchema);
|
|
29
|
-
};
|
|
30
|
-
const isInlineObjectArrayProperty = (propSchema) => {
|
|
31
|
-
if (!isSchemaObject(propSchema))
|
|
32
|
-
return false;
|
|
33
|
-
if (!("type" in propSchema) || propSchema.type !== "array")
|
|
34
|
-
return false;
|
|
35
|
-
if (!hasItems(propSchema) || Array.isArray(propSchema.items))
|
|
36
|
-
return false;
|
|
37
|
-
return isInlineObjectProperty(propSchema.items);
|
|
38
|
-
};
|
|
39
|
-
const isUnionArrayProperty = (propSchema) => {
|
|
40
|
-
if (!isSchemaObject(propSchema))
|
|
41
|
-
return false;
|
|
42
|
-
if (!("type" in propSchema) || propSchema.type !== "array")
|
|
43
|
-
return false;
|
|
44
|
-
if (!hasItems(propSchema) || Array.isArray(propSchema.items))
|
|
45
|
-
return false;
|
|
46
|
-
const items = propSchema.items;
|
|
47
|
-
if (!isSchemaObject(items))
|
|
48
|
-
return false;
|
|
49
|
-
const branches = getUnionBranches(items);
|
|
50
|
-
if (branches === null || branches.length < 2)
|
|
51
|
-
return false;
|
|
52
|
-
return branches.every((branch) => isSchemaObject(branch) && (hasRef(branch) || isInlineObjectProperty(branch)));
|
|
53
|
-
};
|
|
54
|
-
const isInlineUnionProperty = (propSchema) => {
|
|
55
|
-
if (!isSchemaObject(propSchema))
|
|
56
|
-
return false;
|
|
57
|
-
if (hasRef(propSchema) || hasAllOf(propSchema) || "not" in propSchema)
|
|
58
|
-
return false;
|
|
59
|
-
if ("if" in propSchema || "then" in propSchema || "else" in propSchema)
|
|
60
|
-
return false;
|
|
61
|
-
if ("patternProperties" in propSchema)
|
|
62
|
-
return false;
|
|
63
|
-
const branches = getUnionBranches(propSchema);
|
|
64
|
-
if (branches === null || branches.length < 2)
|
|
65
|
-
return false;
|
|
66
|
-
if (hasBranchSiblingConstraint(propSchema))
|
|
67
|
-
return false;
|
|
68
|
-
return branches.every((branch) => isSchemaObject(branch) && (hasRef(branch) || isInlineObjectProperty(branch)));
|
|
69
|
-
};
|
|
70
|
-
const getUnionBranches = (schema) => {
|
|
71
|
-
if (!isSchemaObject(schema))
|
|
72
|
-
return null;
|
|
73
|
-
if (hasAllOf(schema) || "not" in schema)
|
|
74
|
-
return null;
|
|
75
|
-
if (hasOneOf(schema) && schema.oneOf.length > 0)
|
|
76
|
-
return schema.oneOf;
|
|
77
|
-
if (hasAnyOf(schema) && schema.anyOf.length > 0)
|
|
78
|
-
return schema.anyOf;
|
|
79
|
-
return null;
|
|
80
|
-
};
|
|
81
|
-
const isExclusiveUnion = (schema) => isSchemaObject(schema) && !hasAllOf(schema) && !("not" in schema) && hasOneOf(schema) && schema.oneOf.length > 0;
|
|
82
|
-
const hasFastPathBlockingKeyword = (schema) => {
|
|
83
|
-
if (!isSchemaObject(schema))
|
|
84
|
-
return false;
|
|
85
|
-
const record = schema;
|
|
86
|
-
return "contains" in record || "dependentSchemas" in record || "propertyNames" in record || "dependentRequired" in record || // Composition and conditionals: the strict assertions now enforce these, but
|
|
87
|
-
// a guard built from per-property type checks cannot mirror them — it said
|
|
88
|
-
// "already in shape" for `{ kind: 'a' }` under `if: {kind: 'a'}, then:
|
|
89
|
-
// {required: ['n']}` and returned before the conditional was ever tested.
|
|
90
|
-
"if" in record || "then" in record || "else" in record || "not" in record || "patternProperties" in record || // `unevaluated*` is answered against the whole node's annotation coverage,
|
|
91
|
-
// which a per-property guard has no way to reproduce.
|
|
92
|
-
"unevaluatedProperties" in record && record["unevaluatedProperties"] !== true || "unevaluatedItems" in record && record["unevaluatedItems"] !== true || // A `$ref` guard calls the target's shape validator, which knows nothing
|
|
93
|
-
// about the siblings 2020-12 still applies: `{ $ref: …, minProperties: 2 }`
|
|
94
|
-
// was waved through on the target's shape alone.
|
|
95
|
-
hasConstrainingRefSibling(schema) || hasConstrainingAllOf(schema) || hasConstrainingAdditionalProperties(schema);
|
|
96
|
-
};
|
|
97
|
-
const hasConstrainingAllOf = (schema) => hasAllOf(schema) && schema.allOf.some((member) => !(isSchemaObject(member) && hasRef(member)));
|
|
98
|
-
const hasConstrainingAdditionalProperties = (schema) => {
|
|
99
|
-
if (!hasAdditionalProperties(schema))
|
|
100
|
-
return false;
|
|
101
|
-
const additional = schema.additionalProperties;
|
|
102
|
-
if (typeof additional === "boolean")
|
|
103
|
-
return false;
|
|
104
|
-
return subschemaMatchExpr("_x", additional) !== "true";
|
|
105
|
-
};
|
|
106
|
-
const CONSTRAINT_KEYWORDS = [
|
|
107
|
-
"pattern",
|
|
108
|
-
"minLength",
|
|
109
|
-
"maxLength",
|
|
110
|
-
"minimum",
|
|
111
|
-
"maximum",
|
|
112
|
-
"exclusiveMinimum",
|
|
113
|
-
"exclusiveMaximum",
|
|
114
|
-
"multipleOf",
|
|
115
|
-
"minItems",
|
|
116
|
-
"maxItems",
|
|
117
|
-
"uniqueItems",
|
|
118
|
-
"items",
|
|
119
|
-
"prefixItems",
|
|
120
|
-
"properties",
|
|
121
|
-
"required",
|
|
122
|
-
"additionalProperties",
|
|
123
|
-
"patternProperties",
|
|
124
|
-
"minProperties",
|
|
125
|
-
"maxProperties"
|
|
126
|
-
];
|
|
127
|
-
const hasBranchSiblingConstraint = (schema) => {
|
|
128
|
-
const record = schema;
|
|
129
|
-
return "type" in record || CONSTRAINT_KEYWORDS.some((keyword) => keyword in record);
|
|
130
|
-
};
|
|
131
|
-
const multiTypeCheck = (varName, schema, options = {}) => {
|
|
132
|
-
if (!isSchemaObject(schema) || !Array.isArray(schema.type))
|
|
133
|
-
return void 0;
|
|
134
|
-
const record = schema;
|
|
135
|
-
if (!options.ignoreConstraints && CONSTRAINT_KEYWORDS.some((keyword) => keyword in record))
|
|
136
|
-
return null;
|
|
137
|
-
const checks = [];
|
|
138
|
-
for (const type of schema.type) {
|
|
139
|
-
const check = singleTypeCheck(varName, type);
|
|
140
|
-
if (check === null)
|
|
141
|
-
return null;
|
|
142
|
-
checks.push(`(${check})`);
|
|
143
|
-
}
|
|
144
|
-
if (checks.length === 0)
|
|
145
|
-
return null;
|
|
146
|
-
return checks.length === 1 ? checks[0] : `(${checks.join(" || ")})`;
|
|
147
|
-
};
|
|
148
|
-
const generatePropertyTypeCheck = (varName, schema, useRefImports, suffix) => {
|
|
149
|
-
if (!isSchemaObject(schema))
|
|
150
|
-
return null;
|
|
151
|
-
if (hasFastPathBlockingKeyword(schema))
|
|
152
|
-
return null;
|
|
153
|
-
if (useRefImports && hasRef(schema)) {
|
|
154
|
-
const refName = refToName(schema.$ref, suffix);
|
|
155
|
-
return `${shapeValidatorName(refName)}(${varName})`;
|
|
156
|
-
}
|
|
157
|
-
if (hasRef(schema))
|
|
158
|
-
return null;
|
|
159
|
-
if (hasEnum(schema)) {
|
|
160
|
-
return schema.enum.length > 0 ? generateEnumCheck(varName, schema.enum) : null;
|
|
161
|
-
}
|
|
162
|
-
const branches = getUnionBranches(schema);
|
|
163
|
-
if (branches) {
|
|
164
|
-
if (hasBranchSiblingConstraint(schema))
|
|
165
|
-
return null;
|
|
166
|
-
return generateUnionCheck(varName, branches, useRefImports, suffix, isExclusiveUnion(schema));
|
|
167
|
-
}
|
|
168
|
-
if (hasOneOf(schema) || hasAnyOf(schema) || hasAllOf(schema) || "not" in schema) {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
if (hasConst(schema)) {
|
|
172
|
-
const c = schema.const;
|
|
173
|
-
return c === null || typeof c !== "object" ? `${varName} === ${JSON.stringify(c)}` : null;
|
|
174
|
-
}
|
|
175
|
-
const multiType = multiTypeCheck(varName, schema);
|
|
176
|
-
if (multiType !== void 0)
|
|
177
|
-
return multiType;
|
|
178
|
-
if (!hasType(schema))
|
|
179
|
-
return null;
|
|
180
|
-
if (useRefImports && schema.type === "array" && hasItems(schema) && isSchemaObject(schema.items) && hasRef(schema.items)) {
|
|
181
|
-
const refName = refToName(schema.items.$ref, suffix);
|
|
182
|
-
return `Array.isArray(${varName}) && ${varName}.every(${shapeValidatorName(refName)})`;
|
|
183
|
-
}
|
|
184
|
-
const checks = [];
|
|
185
|
-
switch (schema.type) {
|
|
186
|
-
case "string": {
|
|
187
|
-
checks.push(`typeof ${varName} === "string"`);
|
|
188
|
-
if (hasPattern(schema))
|
|
189
|
-
checks.push(`${regexLiteral(schema.pattern)}.test(${varName})`);
|
|
190
|
-
if (hasMinLength(schema))
|
|
191
|
-
checks.push(minLengthPassExpr(varName, schema.minLength));
|
|
192
|
-
if (hasMaxLength(schema))
|
|
193
|
-
checks.push(maxLengthPassExpr(varName, schema.maxLength));
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
case "number":
|
|
197
|
-
case "integer": {
|
|
198
|
-
checks.push(`typeof ${varName} === "number"`);
|
|
199
|
-
if (schema.type === "integer")
|
|
200
|
-
checks.push(`Number.isInteger(${varName})`);
|
|
201
|
-
if (hasMinimum(schema))
|
|
202
|
-
checks.push(`${varName} >= ${schema.minimum}`);
|
|
203
|
-
if (hasMaximum(schema))
|
|
204
|
-
checks.push(`${varName} <= ${schema.maximum}`);
|
|
205
|
-
if (hasExclusiveMinimum(schema))
|
|
206
|
-
checks.push(`${varName} > ${schema.exclusiveMinimum}`);
|
|
207
|
-
if (hasExclusiveMaximum(schema))
|
|
208
|
-
checks.push(`${varName} < ${schema.exclusiveMaximum}`);
|
|
209
|
-
if (hasMultipleOf(schema))
|
|
210
|
-
checks.push(multipleOfPassExpr(varName, schema.multipleOf));
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
case "boolean":
|
|
214
|
-
checks.push(`typeof ${varName} === "boolean"`);
|
|
215
|
-
break;
|
|
216
|
-
case "array": {
|
|
217
|
-
checks.push(`Array.isArray(${varName})`);
|
|
218
|
-
if (hasMinItems(schema))
|
|
219
|
-
checks.push(`${varName}.length >= ${schema.minItems}`);
|
|
220
|
-
if (hasMaxItems(schema))
|
|
221
|
-
checks.push(`${varName}.length <= ${schema.maxItems}`);
|
|
222
|
-
if (hasUniqueItems(schema) && schema.uniqueItems === true) {
|
|
223
|
-
checks.push(generateUniqueItemsCheck(varName, schema));
|
|
224
|
-
}
|
|
225
|
-
if (hasItems(schema) && !Array.isArray(schema.items)) {
|
|
226
|
-
const items = schema.items;
|
|
227
|
-
const itemCheck = plainScalarItemCheck(items, "_it") ?? (isSchemaObject(items) && hasEnum(items) && items.enum.length > 0 ? generateEnumCheck("_it", items.enum) : null);
|
|
228
|
-
if (itemCheck) {
|
|
229
|
-
checks.push(everyTailItem(varName, itemCheck, schema));
|
|
230
|
-
} else if (!(isSchemaObject(items) && (hasRef(items) || isInlineObjectProperty(items)))) {
|
|
231
|
-
const match = subschemaMatchExpr("_it", items);
|
|
232
|
-
if (match === null)
|
|
233
|
-
return null;
|
|
234
|
-
if (match !== "true")
|
|
235
|
-
checks.push(everyTailItem(varName, match, schema));
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
const prefix = getPrefixItems(schema);
|
|
239
|
-
if (prefix) {
|
|
240
|
-
for (let i = 0; i < prefix.length; i++) {
|
|
241
|
-
const posCheck = generatePropertyTypeCheck(`${varName}[${i}]`, prefix[i], false, suffix);
|
|
242
|
-
if (posCheck === null)
|
|
243
|
-
return null;
|
|
244
|
-
checks.push(`(${varName}.length <= ${i} || (${posCheck}))`);
|
|
245
|
-
}
|
|
246
|
-
if (prefixItemsCapsLength(schema))
|
|
247
|
-
checks.push(`${varName}.length <= ${prefix.length}`);
|
|
248
|
-
}
|
|
249
|
-
break;
|
|
250
|
-
}
|
|
251
|
-
case "object":
|
|
252
|
-
if (hasProperties(schema) || hasRequired(schema))
|
|
253
|
-
return null;
|
|
254
|
-
checks.push(`isObject(${varName})`);
|
|
255
|
-
if (hasMinProperties(schema))
|
|
256
|
-
checks.push(`Object.keys(${varName}).length >= ${schema.minProperties}`);
|
|
257
|
-
if (hasMaxProperties(schema))
|
|
258
|
-
checks.push(`Object.keys(${varName}).length <= ${schema.maxProperties}`);
|
|
259
|
-
break;
|
|
260
|
-
default:
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
if (checks.length === 0)
|
|
264
|
-
return null;
|
|
265
|
-
let result = checks[0];
|
|
266
|
-
for (let i = 1; i < checks.length; i++) {
|
|
267
|
-
result += " && " + checks[i];
|
|
268
|
-
}
|
|
269
|
-
return result;
|
|
270
|
-
};
|
|
271
|
-
const generateInlineObjectCheck = (varName, schema, useRefImports, suffix) => {
|
|
272
|
-
if (!isSchemaObject(schema) || !isObjectSchema(schema) || !hasProperties(schema))
|
|
273
|
-
return null;
|
|
274
|
-
if (hasAllOf(schema) || hasOneOf(schema) || hasAnyOf(schema) || "not" in schema)
|
|
275
|
-
return null;
|
|
276
|
-
if ("patternProperties" in schema || "if" in schema)
|
|
277
|
-
return null;
|
|
278
|
-
if (hasAdditionalProperties(schema) && typeof schema.additionalProperties !== "boolean")
|
|
279
|
-
return null;
|
|
280
|
-
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
281
|
-
const checks = [`isObject(${varName})`];
|
|
282
|
-
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
283
|
-
const acc = safeAccessor(varName, key);
|
|
284
|
-
const check = generatePropertyTypeCheck(acc, propSchema, useRefImports, suffix);
|
|
285
|
-
if (check === null)
|
|
286
|
-
return null;
|
|
287
|
-
checks.push(required.has(key) ? check : `(${acc} === undefined || ${check})`);
|
|
288
|
-
required.delete(key);
|
|
289
|
-
}
|
|
290
|
-
for (const key of required) {
|
|
291
|
-
checks.push(hasOwnCheck(varName, key));
|
|
292
|
-
}
|
|
293
|
-
if (hasAdditionalProperties(schema) && schema.additionalProperties === false) {
|
|
294
|
-
const allowedKeys = JSON.stringify(Object.keys(schema.properties));
|
|
295
|
-
checks.push(`Object.keys(${varName}).every((_k) => ${allowedKeys}.includes(_k))`);
|
|
296
|
-
}
|
|
297
|
-
if (hasMinProperties(schema))
|
|
298
|
-
checks.push(`Object.keys(${varName}).length >= ${schema.minProperties}`);
|
|
299
|
-
if (hasMaxProperties(schema))
|
|
300
|
-
checks.push(`Object.keys(${varName}).length <= ${schema.maxProperties}`);
|
|
301
|
-
return checks.join(" && ");
|
|
302
|
-
};
|
|
303
|
-
const generateUnionCheck = (varName, branches, useRefImports, suffix, exclusive = false) => {
|
|
304
|
-
if (branches.length === 0)
|
|
305
|
-
return null;
|
|
306
|
-
const parts = [];
|
|
307
|
-
for (const branch of branches) {
|
|
308
|
-
if (!isSchemaObject(branch))
|
|
309
|
-
return null;
|
|
310
|
-
const check = isObjectSchema(branch) && hasProperties(branch) && !hasRef(branch) ? generateInlineObjectCheck(varName, branch, useRefImports, suffix) : generatePropertyTypeCheck(varName, branch, useRefImports, suffix);
|
|
311
|
-
if (check === null)
|
|
312
|
-
return null;
|
|
313
|
-
parts.push(`(${check})`);
|
|
314
|
-
}
|
|
315
|
-
if (parts.length === 1)
|
|
316
|
-
return parts[0];
|
|
317
|
-
if (exclusive)
|
|
318
|
-
return `((${parts.map((part) => `(${part} ? 1 : 0)`).join(" + ")}) === 1)`;
|
|
319
|
-
return `(${parts.join(" || ")})`;
|
|
320
|
-
};
|
|
321
|
-
const canEnforceUnion = (branches, rootSchema) => {
|
|
322
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
323
|
-
return branches.length > 0 && branches.every((branch) => {
|
|
324
|
-
if (!isSchemaObject(branch))
|
|
325
|
-
return false;
|
|
326
|
-
return isObjectSchema(branch) && hasProperties(branch) && !hasRef(branch) ? canTrustInlineObjectCheck(branch, rootSchema, visiting) : canTrustPropertyCheck(branch, rootSchema, visiting);
|
|
327
|
-
});
|
|
328
|
-
};
|
|
329
|
-
const canTrustPropertyCheck = (schema, rootSchema, visiting) => {
|
|
330
|
-
if (!isSchemaObject(schema))
|
|
331
|
-
return false;
|
|
332
|
-
if (hasRef(schema)) {
|
|
333
|
-
return canTrustReferencedValidator(schema.$ref, rootSchema, visiting);
|
|
334
|
-
}
|
|
335
|
-
if (hasEnum(schema))
|
|
336
|
-
return schema.enum.length > 0;
|
|
337
|
-
const branches = getUnionBranches(schema);
|
|
338
|
-
if (branches) {
|
|
339
|
-
return branches.every((branch) => {
|
|
340
|
-
if (!isSchemaObject(branch))
|
|
341
|
-
return false;
|
|
342
|
-
return isObjectSchema(branch) && hasProperties(branch) && !hasRef(branch) ? canTrustInlineObjectCheck(branch, rootSchema, visiting) : canTrustPropertyCheck(branch, rootSchema, visiting);
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
if (hasOneOf(schema) || hasAnyOf(schema) || hasAllOf(schema) || "not" in schema)
|
|
346
|
-
return false;
|
|
347
|
-
if (hasConst(schema))
|
|
348
|
-
return schema.const === null || typeof schema.const !== "object";
|
|
349
|
-
const multiType = multiTypeCheck("_x", schema);
|
|
350
|
-
if (multiType !== void 0)
|
|
351
|
-
return multiType !== null;
|
|
352
|
-
if (!hasType(schema))
|
|
353
|
-
return false;
|
|
354
|
-
if (schema.type === "array" && hasItems(schema) && isSchemaObject(schema.items) && hasRef(schema.items)) {
|
|
355
|
-
return canTrustReferencedValidator(schema.items.$ref, rootSchema, visiting);
|
|
356
|
-
}
|
|
357
|
-
switch (schema.type) {
|
|
358
|
-
case "string":
|
|
359
|
-
case "number":
|
|
360
|
-
case "integer":
|
|
361
|
-
case "boolean":
|
|
362
|
-
case "array":
|
|
363
|
-
case "object":
|
|
364
|
-
return true;
|
|
365
|
-
default:
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
const canTrustShapeProperty = (propSchema, rootSchema, visiting) => {
|
|
370
|
-
if (!isSchemaObject(propSchema))
|
|
371
|
-
return false;
|
|
372
|
-
if (hasAdditionalProperties(propSchema) && typeof propSchema.additionalProperties !== "boolean")
|
|
373
|
-
return false;
|
|
374
|
-
if (isInlineObjectProperty(propSchema)) {
|
|
375
|
-
return Object.values(propSchema.properties ?? {}).every((sub) => canTrustShapeProperty(sub, rootSchema, visiting));
|
|
376
|
-
}
|
|
377
|
-
if (isInlineObjectArrayProperty(propSchema)) {
|
|
378
|
-
const items = propSchema.items;
|
|
379
|
-
return Object.values(items.properties ?? {}).every((sub) => canTrustShapeProperty(sub, rootSchema, visiting));
|
|
380
|
-
}
|
|
381
|
-
return canTrustPropertyCheck(propSchema, rootSchema, visiting);
|
|
382
|
-
};
|
|
383
|
-
const KEYWORDS_INLINE_CHECK_IGNORES = [
|
|
384
|
-
"propertyNames",
|
|
385
|
-
"dependentRequired",
|
|
386
|
-
"dependentSchemas",
|
|
387
|
-
"contains",
|
|
388
|
-
"unevaluatedProperties"
|
|
389
|
-
];
|
|
390
|
-
const canTrustInlineObjectCheck = (schema, rootSchema, visiting) => {
|
|
391
|
-
if (!isSchemaObject(schema) || !isObjectSchema(schema) || !hasProperties(schema))
|
|
392
|
-
return false;
|
|
393
|
-
if (hasAllOf(schema) || hasOneOf(schema) || hasAnyOf(schema) || "not" in schema)
|
|
394
|
-
return false;
|
|
395
|
-
if ("patternProperties" in schema || "if" in schema)
|
|
396
|
-
return false;
|
|
397
|
-
if (hasAdditionalProperties(schema) && typeof schema.additionalProperties !== "boolean")
|
|
398
|
-
return false;
|
|
399
|
-
const record = schema;
|
|
400
|
-
if (KEYWORDS_INLINE_CHECK_IGNORES.some((keyword) => keyword in record))
|
|
401
|
-
return false;
|
|
402
|
-
return Object.values(schema.properties).every((propSchema) => canTrustPropertyCheck(propSchema, rootSchema, visiting));
|
|
403
|
-
};
|
|
404
|
-
const canTrustReferencedValidator = (ref, rootSchema, visiting) => {
|
|
405
|
-
if (visiting.has(ref))
|
|
406
|
-
return true;
|
|
407
|
-
if (!rootSchema)
|
|
408
|
-
return false;
|
|
409
|
-
const resolved = resolveRef(ref, rootSchema);
|
|
410
|
-
if (!resolved || !isSchemaObject(resolved))
|
|
411
|
-
return false;
|
|
412
|
-
visiting.add(ref);
|
|
413
|
-
try {
|
|
414
|
-
if (hasRef(resolved) && !hasProperties(resolved)) {
|
|
415
|
-
return canTrustReferencedValidator(resolved.$ref, rootSchema, visiting);
|
|
416
|
-
}
|
|
417
|
-
if (!hasProperties(resolved)) {
|
|
418
|
-
const branches = getUnionBranches(resolved);
|
|
419
|
-
if (!branches) {
|
|
420
|
-
if ("patternProperties" in resolved || "if" in resolved)
|
|
421
|
-
return false;
|
|
422
|
-
return canTrustPropertyCheck(resolved, rootSchema, visiting);
|
|
423
|
-
}
|
|
424
|
-
if ("patternProperties" in resolved || "if" in resolved)
|
|
425
|
-
return false;
|
|
426
|
-
return branches.every((branch) => {
|
|
427
|
-
if (!isSchemaObject(branch))
|
|
428
|
-
return false;
|
|
429
|
-
return isObjectSchema(branch) && hasProperties(branch) && !hasRef(branch) ? canTrustInlineObjectCheck(branch, rootSchema, visiting) : canTrustPropertyCheck(branch, rootSchema, visiting);
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
if (hasOneOf(resolved) || hasAnyOf(resolved) || hasAllOf(resolved) || "not" in resolved || "patternProperties" in resolved || "if" in resolved || "then" in resolved || "else" in resolved) {
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
return Object.values(resolved.properties).every((propSchema) => canTrustShapeProperty(propSchema, rootSchema, visiting));
|
|
436
|
-
} finally {
|
|
437
|
-
visiting.delete(ref);
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
export {
|
|
441
|
-
canEnforceUnion,
|
|
442
|
-
generateInlineObjectCheck,
|
|
443
|
-
generatePropertyTypeCheck,
|
|
444
|
-
generateUnionCheck,
|
|
445
|
-
getUnionBranches,
|
|
446
|
-
hasFastPathBlockingKeyword,
|
|
447
|
-
isExclusiveUnion,
|
|
448
|
-
isInlineObjectArrayProperty,
|
|
449
|
-
isInlineObjectProperty,
|
|
450
|
-
isInlineUnionProperty,
|
|
451
|
-
isUnionArrayProperty,
|
|
452
|
-
multiTypeCheck,
|
|
453
|
-
shapeValidatorName
|
|
454
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
|
2
|
-
/**
|
|
3
|
-
* Builds the boolean expression that is TRUE when every element of `accessor` is
|
|
4
|
-
* distinct — the `uniqueItems: true` contract.
|
|
5
|
-
*
|
|
6
|
-
* Scalar-only arrays take the cheap native `Set` (exact for primitives, and
|
|
7
|
-
* allocation is a single pass). Anything that might hold an object or an array
|
|
8
|
-
* falls back to the canonical-JSON projection above, because JSON Schema compares
|
|
9
|
-
* items by *structural* equality and neither a plain `Set` (reference equality)
|
|
10
|
-
* nor a raw `JSON.stringify` key (order sensitive) answers that question.
|
|
11
|
-
*
|
|
12
|
-
* @param accessor - Expression yielding the array; the caller must already have
|
|
13
|
-
* proven `Array.isArray`.
|
|
14
|
-
* @param schema - The array schema, read for its item types.
|
|
15
|
-
*/
|
|
16
|
-
export declare const generateUniqueItemsCheck: (accessor: string, schema: JSONSchema) => string;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { hasType, isSchemaObject } from "@amritk/helpers/schema-guards";
|
|
2
|
-
const SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
|
|
3
|
-
const schemaIsScalarOnly = (schema) => {
|
|
4
|
-
if (schema === void 0 || !isSchemaObject(schema) || !hasType(schema))
|
|
5
|
-
return false;
|
|
6
|
-
const type = schema["type"];
|
|
7
|
-
const types = Array.isArray(type) ? type : [type];
|
|
8
|
-
return types.length > 0 && types.every((t) => typeof t === "string" && SCALAR_TYPES.has(t));
|
|
9
|
-
};
|
|
10
|
-
const arrayItemsAreScalarOnly = (schema) => {
|
|
11
|
-
if (!isSchemaObject(schema))
|
|
12
|
-
return false;
|
|
13
|
-
const record = schema;
|
|
14
|
-
const prefix = record["prefixItems"];
|
|
15
|
-
if (Array.isArray(prefix)) {
|
|
16
|
-
if (!prefix.every((position) => schemaIsScalarOnly(position)))
|
|
17
|
-
return false;
|
|
18
|
-
const tail = "items" in record ? record["items"] : record["additionalItems"];
|
|
19
|
-
return tail === false ? true : schemaIsScalarOnly(tail);
|
|
20
|
-
}
|
|
21
|
-
if (Array.isArray(record["items"])) {
|
|
22
|
-
if (!record["items"].every((position) => schemaIsScalarOnly(position)))
|
|
23
|
-
return false;
|
|
24
|
-
return record["additionalItems"] === false ? true : schemaIsScalarOnly(record["additionalItems"]);
|
|
25
|
-
}
|
|
26
|
-
return schemaIsScalarOnly(record["items"]);
|
|
27
|
-
};
|
|
28
|
-
const CANONICAL_JSON = 'JSON.stringify(_u, (_k, _v) => (_v !== null && typeof _v === "object" && !Array.isArray(_v)) ? Object.fromEntries(Object.keys(_v).sort().map((_sk) => [_sk, (_v as Record<string, unknown>)[_sk]])) : _v)';
|
|
29
|
-
const generateUniqueItemsCheck = (accessor, schema) => arrayItemsAreScalarOnly(schema) ? `new Set(${accessor}).size === ${accessor}.length` : `new Set((${accessor} as unknown[]).map((_u) => ${CANONICAL_JSON})).size === ${accessor}.length`;
|
|
30
|
-
export {
|
|
31
|
-
generateUniqueItemsCheck
|
|
32
|
-
};
|