@amritk/generate-validators 0.13.1 → 0.14.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/AI.md +3 -2
- package/README.md +42 -13
- package/dist/generators/assert-unevaluated-generatable.js +9 -8
- package/dist/generators/build-schema.d.ts +1 -1
- package/dist/generators/build-schema.js +135 -9
- package/dist/generators/collect-emitted-refs.js +12 -10
- package/dist/generators/collect-validator-imports.d.ts +22 -0
- package/dist/generators/collect-validator-imports.js +13 -3
- package/dist/generators/generate-files.js +12 -5
- package/dist/generators/generate-validator-function.js +194 -136
- package/dist/generators/tuple-shape.js +7 -5
- package/dist/generators/unevaluated-match.js +33 -28
- package/package.json +3 -3
|
@@ -1,17 +1,19 @@
|
|
|
1
|
+
import { declaresKey, readKey } from "@amritk/helpers/read-key";
|
|
1
2
|
const tupleShapeOf = (schema) => {
|
|
2
|
-
const items = schema
|
|
3
|
-
const prefix = schema
|
|
3
|
+
const items = readKey(schema, "items");
|
|
4
|
+
const prefix = readKey(schema, "prefixItems");
|
|
4
5
|
if (Array.isArray(prefix)) {
|
|
5
6
|
return {
|
|
6
7
|
tuple: prefix,
|
|
7
|
-
tail: "items"
|
|
8
|
+
tail: declaresKey(schema, "items") && !Array.isArray(items) ? items : void 0,
|
|
8
9
|
tailIsClosed: items === false
|
|
9
10
|
};
|
|
10
11
|
}
|
|
11
12
|
if (Array.isArray(items)) {
|
|
12
|
-
|
|
13
|
+
const additionalItems = readKey(schema, "additionalItems");
|
|
14
|
+
return { tuple: items, tail: additionalItems, tailIsClosed: additionalItems === false };
|
|
13
15
|
}
|
|
14
|
-
return { tuple: void 0, tail: "items"
|
|
16
|
+
return { tuple: void 0, tail: declaresKey(schema, "items") ? items : void 0, tailIsClosed: items === false };
|
|
15
17
|
};
|
|
16
18
|
export {
|
|
17
19
|
tupleShapeOf
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { regexLiteral } from "@amritk/helpers/escape-regex-pattern";
|
|
2
|
+
import { declaresKey, readKey } from "@amritk/helpers/read-key";
|
|
2
3
|
import { resolveRef } from "@amritk/helpers/resolve-ref";
|
|
3
4
|
import { isSchemaObject } from "@amritk/helpers/schema-guards";
|
|
4
5
|
const NONE = { all: false, terms: [] };
|
|
@@ -21,19 +22,19 @@ const conditioned = (coverage, condition) => {
|
|
|
21
22
|
};
|
|
22
23
|
const merge = (into, from) => into.all || from.all ? ALL : { all: false, terms: [...into.terms, ...from.terms] };
|
|
23
24
|
const ownPropertyCoverage = (schema, keyVar, ignoreOwnUnevaluated) => {
|
|
24
|
-
if ("additionalProperties"
|
|
25
|
+
if (declaresKey(schema, "additionalProperties"))
|
|
25
26
|
return ALL;
|
|
26
|
-
if (!ignoreOwnUnevaluated && "unevaluatedProperties"
|
|
27
|
+
if (!ignoreOwnUnevaluated && declaresKey(schema, "unevaluatedProperties") && schema["unevaluatedProperties"] !== false) {
|
|
27
28
|
return ALL;
|
|
28
29
|
}
|
|
29
30
|
const terms = [];
|
|
30
|
-
const properties = schema
|
|
31
|
+
const properties = readKey(schema, "properties");
|
|
31
32
|
if (typeof properties === "object" && properties !== null && !Array.isArray(properties)) {
|
|
32
33
|
const keys = Object.keys(properties);
|
|
33
34
|
if (keys.length > 0)
|
|
34
35
|
terms.push(`${JSON.stringify(keys)}.includes(${keyVar})`);
|
|
35
36
|
}
|
|
36
|
-
const patterns = schema
|
|
37
|
+
const patterns = readKey(schema, "patternProperties");
|
|
37
38
|
if (typeof patterns === "object" && patterns !== null && !Array.isArray(patterns)) {
|
|
38
39
|
for (const pattern of Object.keys(patterns)) {
|
|
39
40
|
terms.push(`${regexLiteral(pattern)}.test(${keyVar})`);
|
|
@@ -42,20 +43,21 @@ const ownPropertyCoverage = (schema, keyVar, ignoreOwnUnevaluated) => {
|
|
|
42
43
|
return { all: false, terms };
|
|
43
44
|
};
|
|
44
45
|
const ownItemCoverage = (schema, indexVar, itemVar, ignoreOwnUnevaluated, ctx, match) => {
|
|
45
|
-
const tupleItems = Array.isArray(schema
|
|
46
|
-
if (!tupleItems && "items"
|
|
46
|
+
const tupleItems = Array.isArray(readKey(schema, "items"));
|
|
47
|
+
if (!tupleItems && declaresKey(schema, "items"))
|
|
47
48
|
return ALL;
|
|
48
|
-
if (tupleItems && "additionalItems"
|
|
49
|
+
if (tupleItems && declaresKey(schema, "additionalItems"))
|
|
49
50
|
return ALL;
|
|
50
|
-
if (!ignoreOwnUnevaluated && "unevaluatedItems"
|
|
51
|
+
if (!ignoreOwnUnevaluated && declaresKey(schema, "unevaluatedItems") && schema["unevaluatedItems"] !== false) {
|
|
51
52
|
return ALL;
|
|
52
53
|
}
|
|
53
54
|
const terms = [];
|
|
54
|
-
const
|
|
55
|
+
const declaredPrefix = readKey(schema, "prefixItems");
|
|
56
|
+
const prefix = Array.isArray(declaredPrefix) ? declaredPrefix : tupleItems ? readKey(schema, "items") : null;
|
|
55
57
|
if (prefix && prefix.length > 0)
|
|
56
58
|
terms.push(`${indexVar} < ${prefix.length}`);
|
|
57
|
-
if ("contains"
|
|
58
|
-
const contained = match(itemVar, schema
|
|
59
|
+
if (declaresKey(schema, "contains")) {
|
|
60
|
+
const contained = match(itemVar, readKey(schema, "contains"), ctx.depth);
|
|
59
61
|
if (contained === "true")
|
|
60
62
|
return ALL;
|
|
61
63
|
if (contained !== "false")
|
|
@@ -71,10 +73,10 @@ const coverageOf = (kind, schema, indexVar, itemVar, acc, ctx, sink, match, igno
|
|
|
71
73
|
if (ctx.depth > MAX_COVERAGE_DEPTH)
|
|
72
74
|
return null;
|
|
73
75
|
const s = schema;
|
|
74
|
-
if (typeof s
|
|
76
|
+
if (typeof readKey(s, "$dynamicRef") === "string")
|
|
75
77
|
return null;
|
|
76
78
|
let coverage = kind === "properties" ? ownPropertyCoverage(s, indexVar, ignoreOwnUnevaluated) : ownItemCoverage(s, indexVar, itemVar, ignoreOwnUnevaluated, ctx, match);
|
|
77
|
-
const ref = s
|
|
79
|
+
const ref = readKey(s, "$ref");
|
|
78
80
|
if (typeof ref === "string") {
|
|
79
81
|
if (ctx.rootSchema === void 0 || ctx.seen.has(ref))
|
|
80
82
|
return null;
|
|
@@ -88,8 +90,9 @@ const coverageOf = (kind, schema, indexVar, itemVar, acc, ctx, sink, match, igno
|
|
|
88
90
|
return null;
|
|
89
91
|
coverage = merge(coverage, refCoverage);
|
|
90
92
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
const allOf = readKey(s, "allOf");
|
|
94
|
+
if (Array.isArray(allOf)) {
|
|
95
|
+
for (const member of allOf) {
|
|
93
96
|
const memberCoverage = coverageOf(kind, member, indexVar, itemVar, acc, nest(ctx), sink, match);
|
|
94
97
|
if (memberCoverage === null)
|
|
95
98
|
return null;
|
|
@@ -97,9 +100,10 @@ const coverageOf = (kind, schema, indexVar, itemVar, acc, ctx, sink, match, igno
|
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
for (const keyword of ["anyOf", "oneOf"]) {
|
|
100
|
-
|
|
103
|
+
const branches = readKey(s, keyword);
|
|
104
|
+
if (!Array.isArray(branches))
|
|
101
105
|
continue;
|
|
102
|
-
for (const branch of
|
|
106
|
+
for (const branch of branches) {
|
|
103
107
|
const branchCoverage = coverageOf(kind, branch, indexVar, itemVar, acc, nest(ctx), sink, match);
|
|
104
108
|
if (branchCoverage === null)
|
|
105
109
|
return null;
|
|
@@ -108,16 +112,16 @@ const coverageOf = (kind, schema, indexVar, itemVar, acc, ctx, sink, match, igno
|
|
|
108
112
|
coverage = merge(coverage, conditioned(branchCoverage, hoistCondition(sink, match(acc, branch, ctx.depth))));
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
|
-
if ("if"
|
|
115
|
+
if (declaresKey(s, "if")) {
|
|
112
116
|
const branches = [];
|
|
113
117
|
for (const [keyword, negated] of [
|
|
114
118
|
["if", false],
|
|
115
119
|
["then", false],
|
|
116
120
|
["else", true]
|
|
117
121
|
]) {
|
|
118
|
-
if (!(keyword
|
|
122
|
+
if (!declaresKey(s, keyword))
|
|
119
123
|
continue;
|
|
120
|
-
const branchCoverage = coverageOf(kind, s
|
|
124
|
+
const branchCoverage = coverageOf(kind, readKey(s, keyword), indexVar, itemVar, acc, nest(ctx), sink, match);
|
|
121
125
|
if (branchCoverage === null)
|
|
122
126
|
return null;
|
|
123
127
|
if (!branchCoverage.all && branchCoverage.terms.length === 0)
|
|
@@ -125,14 +129,14 @@ const coverageOf = (kind, schema, indexVar, itemVar, acc, ctx, sink, match, igno
|
|
|
125
129
|
branches.push({ coverage: branchCoverage, negated });
|
|
126
130
|
}
|
|
127
131
|
if (branches.length > 0) {
|
|
128
|
-
const ifMatch = hoistCondition(sink, match(acc, s
|
|
132
|
+
const ifMatch = hoistCondition(sink, match(acc, readKey(s, "if"), ctx.depth));
|
|
129
133
|
for (const branch of branches) {
|
|
130
134
|
coverage = merge(coverage, conditioned(branch.coverage, branch.negated ? `!${ifMatch}` : ifMatch));
|
|
131
135
|
}
|
|
132
136
|
}
|
|
133
137
|
}
|
|
134
138
|
for (const keyword of ["dependentSchemas", "dependencies"]) {
|
|
135
|
-
const dependents = s
|
|
139
|
+
const dependents = readKey(s, keyword);
|
|
136
140
|
if (typeof dependents !== "object" || dependents === null || Array.isArray(dependents))
|
|
137
141
|
continue;
|
|
138
142
|
for (const [trigger, sub] of Object.entries(dependents)) {
|
|
@@ -153,9 +157,9 @@ const unevaluatedPropertiesExpr = (acc, schema, rootSchema, depth, match) => {
|
|
|
153
157
|
if (!isSchemaObject(schema))
|
|
154
158
|
return void 0;
|
|
155
159
|
const s = schema;
|
|
156
|
-
if (!("unevaluatedProperties"
|
|
160
|
+
if (!declaresKey(s, "unevaluatedProperties"))
|
|
157
161
|
return void 0;
|
|
158
|
-
const unevaluated = s
|
|
162
|
+
const unevaluated = readKey(s, "unevaluatedProperties");
|
|
159
163
|
if (unevaluated === true)
|
|
160
164
|
return void 0;
|
|
161
165
|
const keyVar = `_uk${depth}`;
|
|
@@ -186,9 +190,9 @@ const unevaluatedItemsExpr = (acc, schema, rootSchema, depth, match) => {
|
|
|
186
190
|
if (!isSchemaObject(schema))
|
|
187
191
|
return void 0;
|
|
188
192
|
const s = schema;
|
|
189
|
-
if (!("unevaluatedItems"
|
|
193
|
+
if (!declaresKey(s, "unevaluatedItems"))
|
|
190
194
|
return void 0;
|
|
191
|
-
const unevaluated = s
|
|
195
|
+
const unevaluated = readKey(s, "unevaluatedItems");
|
|
192
196
|
if (unevaluated === true)
|
|
193
197
|
return void 0;
|
|
194
198
|
const indexVar = `_un${depth}`;
|
|
@@ -201,15 +205,16 @@ const unevaluatedItemsExpr = (acc, schema, rootSchema, depth, match) => {
|
|
|
201
205
|
if (coverage.all)
|
|
202
206
|
return void 0;
|
|
203
207
|
const elements = `(${acc} as unknown[])`;
|
|
208
|
+
const visited = `Array.from(${elements})`;
|
|
204
209
|
const covered = coverage.terms.length > 0 ? orJoin(coverage.terms) : null;
|
|
205
210
|
if (unevaluated === false) {
|
|
206
|
-
const expr2 = covered === null ? `${elements}.length === 0` : `${
|
|
211
|
+
const expr2 = covered === null ? `${elements}.length === 0` : `${visited}.every((${itemVar}, ${indexVar}) => ${covered})`;
|
|
207
212
|
return { setup: sink.setup, expr: expr2 };
|
|
208
213
|
}
|
|
209
214
|
const valueMatch = match(itemVar, unevaluated, depth);
|
|
210
215
|
if (valueMatch === "true")
|
|
211
216
|
return void 0;
|
|
212
|
-
const expr = covered === null ? `${
|
|
217
|
+
const expr = covered === null ? `${visited}.every((${itemVar}) => ${valueMatch})` : `${visited}.every((${itemVar}, ${indexVar}) => ${covered} || ${valueMatch})`;
|
|
213
218
|
return { setup: sink.setup, expr };
|
|
214
219
|
};
|
|
215
220
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amritk/generate-validators",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Generate TypeScript validation functions from JSON Schemas.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"json-schema-typed": "^8.0.1",
|
|
57
|
-
"@amritk/helpers": "^0.
|
|
57
|
+
"@amritk/helpers": "^0.16.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@amritk/runtime-validators": "^0.
|
|
60
|
+
"@amritk/runtime-validators": "^0.12.0",
|
|
61
61
|
"@ryoppippi/unplugin-typia": "^2.6.5",
|
|
62
62
|
"@scalar/openapi-parser": "^0.26.1",
|
|
63
63
|
"@sinclair/typebox": "^0.34.49",
|