@toolproof-core/genesis 1.0.48 → 1.0.50
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/dist/src/implementations/tools.d.ts +100 -0
- package/dist/src/implementations/tools.js +135 -0
- package/dist/src/index.d.ts +9 -8
- package/dist/src/index.js +4 -3
- package/dist/src/utils/resourceTypes.d.ts +2 -1
- package/dist/src/utils/resourceTypes.js +22 -6
- package/dist/src/utils/schemaRefNormalization.js +3 -3
- package/dist/src/utils/standaloneTypes.js +2 -2
- package/dist/src/utils/timestampedResources.d.ts +16 -0
- package/dist/src/utils/timestampedResources.js +17 -0
- package/dist/src/utils/typeGenerationPostProcess.js +2 -2
- package/dist/src/utils/zodCodegen.js +3 -3
- package/generated-src/declarations/booleans.json +4 -0
- package/generated-src/declarations/booleans.ts +2 -0
- package/generated-src/declarations/naturals.json +13 -0
- package/generated-src/declarations/naturals.ts +2 -0
- package/generated-src/{resourceTypes → declarations}/resourceTypes.json +176 -39
- package/generated-src/declarations/resourceTypes.ts +2 -0
- package/generated-src/declarations/tools.json +705 -0
- package/generated-src/declarations/tools.ts +2 -0
- package/generated-src/implementations/tools.ts +214 -0
- package/generated-src/{derived → lookups}/constants.ts +4 -5
- package/generated-src/metadata/Core.json +51 -114
- package/generated-src/metadata/dependencyMap.json +16 -2
- package/generated-src/metadata/terminals.json +3 -4
- package/generated-src/schemas/schemas.json +198 -29
- package/generated-src/schemas/standalone/Resource.json +4 -8
- package/generated-src/schemas/standalone/ResourceType.json +21 -5
- package/generated-src/schemas/standalone/Strategy.json +4 -8
- package/generated-src/schemas/standalone/StrategyTrace.json +4 -8
- package/generated-src/schemas/standalone/Suite.json +646 -0
- package/generated-src/schemas/standalone/Suite.ts +2 -0
- package/generated-src/schemas/standalone/Tool.json +48 -23
- package/generated-src/schemas/zod/Resource.ts +2 -2
- package/generated-src/schemas/zod/ResourceType.ts +1 -1
- package/generated-src/schemas/zod/Strategy.ts +3 -3
- package/generated-src/schemas/zod/StrategyTrace.ts +3 -3
- package/generated-src/schemas/zod/Suite.ts +44 -0
- package/generated-src/schemas/zod/Tool.ts +7 -2
- package/generated-src/schemas/zod/index.ts +1 -0
- package/{src/genesis/resources → generated-src/timestampedResources}/booleans.json +19 -23
- package/generated-src/timestampedResources/booleans.ts +2 -0
- package/{src/genesis/resources → generated-src/timestampedResources}/naturals.json +100 -111
- package/generated-src/timestampedResources/naturals.ts +2 -0
- package/generated-src/{resources → timestampedResources}/resourceTypes.json +202 -66
- package/generated-src/timestampedResources/resourceTypes.ts +2 -0
- package/{src/genesis/resources → generated-src/timestampedResources}/tools.json +824 -839
- package/generated-src/timestampedResources/tools.ts +2 -0
- package/generated-src/types/standalone/BooleanResource.d.ts +2 -2
- package/generated-src/types/standalone/ErrorResource.d.ts +2 -2
- package/generated-src/types/standalone/GoalResource.d.ts +2 -2
- package/generated-src/types/standalone/NaturalResource.d.ts +2 -2
- package/generated-src/types/standalone/ResourceResource.d.ts +2 -2
- package/generated-src/types/standalone/ResourceTypeResource.d.ts +2 -2
- package/generated-src/types/standalone/StrategyResource.d.ts +2 -2
- package/generated-src/types/standalone/StrategyTraceResource.d.ts +2 -2
- package/generated-src/types/standalone/SuiteResource.d.ts +3 -0
- package/generated-src/types/standalone/SuiteResource.js +1 -0
- package/generated-src/types/standalone/ToolResource.d.ts +2 -2
- package/generated-src/types/types.d.ts +57 -14
- package/package.json +18 -13
- package/src/declarations/booleans.json +4 -0
- package/src/declarations/naturals.json +13 -0
- package/src/{genesis → declarations/resourceTypes}/resourceTypeShells.json +52 -46
- package/src/{genesis → declarations/resourceTypes}/schemas.json +1838 -1669
- package/src/declarations/tools.json +705 -0
- package/src/implementations/tools.ts +214 -0
- package/src/index.ts +131 -27
- package/src/utils/constantsAndMappings.ts +194 -194
- package/src/utils/coreProjection.ts +52 -52
- package/src/utils/resourceTypes.ts +70 -38
- package/src/utils/schemaDependencies.ts +114 -114
- package/src/utils/schemaObjectNormalization.ts +70 -70
- package/src/utils/schemaRefNormalization.ts +82 -82
- package/src/utils/schemaShims.ts +16 -16
- package/src/utils/standaloneSchemas.ts +113 -113
- package/src/utils/standaloneTypes.ts +27 -27
- package/src/utils/standaloneZodSchemas.ts +71 -71
- package/src/utils/timestampedResources.ts +42 -0
- package/src/utils/typeGeneration.ts +30 -30
- package/src/utils/typeGenerationPostProcess.ts +245 -245
- package/src/utils/typeGenerationPreflight.ts +118 -118
- package/src/utils/zodCodegen.ts +548 -548
- package/toolproof.json +19 -0
- package/dist/src/genesis/resources/implementations/foo.d.ts +0 -1
- package/dist/src/genesis/resources/implementations/foo.js +0 -184
- package/dist/src/utils/resources.d.ts +0 -5
- package/dist/src/utils/resources.js +0 -17
- package/generated-src/resourceTypes/resourceTypes.ts +0 -2
- package/generated-src/resources/resourceTypes.ts +0 -2
- package/src/genesis/resources/implementations/foo.ts +0 -183
- package/src/utils/resources.ts +0 -26
- /package/generated-src/{derived → lookups}/mappings.ts +0 -0
package/src/utils/zodCodegen.ts
CHANGED
|
@@ -1,549 +1,549 @@
|
|
|
1
|
-
import { normalizeAllOfSiblingObjectKeywords } from './schemaObjectNormalization.js';
|
|
2
|
-
|
|
3
|
-
export type JsonSchema = any;
|
|
4
|
-
|
|
5
|
-
export type JsonSchemaToZodWarning = {
|
|
6
|
-
path: string;
|
|
7
|
-
message: string;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export type JsonSchemaToZodResult = {
|
|
11
|
-
expressionsByName: Record<string, string>;
|
|
12
|
-
warnings: JsonSchemaToZodWarning[];
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
type Ctx = {
|
|
16
|
-
defNames: Set<string>;
|
|
17
|
-
defsByName: Record<string, any>;
|
|
18
|
-
anchorToDefName: Record<string, string>;
|
|
19
|
-
warnings: JsonSchemaToZodWarning[];
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
function isObject(v: any): v is Record<string, any> {
|
|
23
|
-
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function pathJoin(base: string, key: string | number): string {
|
|
27
|
-
if (!base) return String(key);
|
|
28
|
-
if (typeof key === 'number') return `${base}[${key}]`;
|
|
29
|
-
return `${base}.${key}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function warn(ctx: Ctx, path: string, message: string) {
|
|
33
|
-
ctx.warnings.push({ path, message });
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function asDefSchemaRef(defName: string): string {
|
|
37
|
-
return `${defName}Schema`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function extractPointerDefName(ref: string): string | null {
|
|
41
|
-
if (!ref || typeof ref !== 'string') return null;
|
|
42
|
-
if (!ref.startsWith('#/')) return null;
|
|
43
|
-
const parts = ref.slice(2).split('/');
|
|
44
|
-
if (parts.length !== 2) return null;
|
|
45
|
-
if (parts[0] !== '$defs') return null;
|
|
46
|
-
const defName = parts[1];
|
|
47
|
-
if (defName === undefined) return null;
|
|
48
|
-
const name = defName.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
49
|
-
return name;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function resolveLocalRef(ref: string, ctx: Ctx): string | null {
|
|
53
|
-
const byPointer = extractPointerDefName(ref);
|
|
54
|
-
if (byPointer && ctx.defNames.has(byPointer)) return byPointer;
|
|
55
|
-
|
|
56
|
-
if (ref.startsWith('#') && !ref.startsWith('#/')) {
|
|
57
|
-
const anchor = ref.slice(1);
|
|
58
|
-
const defName = ctx.anchorToDefName[anchor];
|
|
59
|
-
if (defName) return defName;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function jsonString(value: unknown): string {
|
|
66
|
-
return JSON.stringify(value);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function emitRegex(pattern: string): string {
|
|
70
|
-
return `new RegExp(${jsonString(pattern)})`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function hasOwn(obj: any, key: string): boolean {
|
|
74
|
-
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isBooleanSchema(node: any): node is boolean {
|
|
78
|
-
return typeof node === 'boolean';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function isRecordLikeObjectSchema(node: any): boolean {
|
|
82
|
-
if (!isObject(node)) return false;
|
|
83
|
-
if (node.type !== 'object') return false;
|
|
84
|
-
const hasFixedProps = hasOwn(node, 'properties') && isObject(node.properties) && Object.keys(node.properties).length > 0;
|
|
85
|
-
const hasPropertyNames = isObject(node.propertyNames);
|
|
86
|
-
const hasAdditionalSchema = isObject(node.additionalProperties) || isBooleanSchema(node.additionalProperties);
|
|
87
|
-
return !hasFixedProps && hasPropertyNames && hasAdditionalSchema;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function emitRecordLikeObjectSchema(node: any, ctx: Ctx, path: string): string {
|
|
91
|
-
const
|
|
92
|
-
const valueExpr = emitSchema(
|
|
93
|
-
|
|
94
|
-
const pn = node.propertyNames;
|
|
95
|
-
let keyPattern: string | undefined;
|
|
96
|
-
if (isObject(pn)) {
|
|
97
|
-
if (typeof pn.pattern === 'string') {
|
|
98
|
-
keyPattern = pn.pattern;
|
|
99
|
-
} else if (typeof pn.$ref === 'string') {
|
|
100
|
-
const defName = resolveLocalRef(pn.$ref, ctx);
|
|
101
|
-
const resolved = defName ? ctx.defsByName[defName] : undefined;
|
|
102
|
-
if (isObject(resolved) && typeof resolved.pattern === 'string') {
|
|
103
|
-
keyPattern = resolved.pattern;
|
|
104
|
-
} else {
|
|
105
|
-
warn(ctx, pathJoin(path, 'propertyNames'), 'Unsupported propertyNames $ref target; keys will not be validated.');
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
warn(ctx, pathJoin(path, 'propertyNames'), 'Unsupported propertyNames shape; keys will not be validated.');
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
111
|
-
warn(ctx, pathJoin(path, 'propertyNames'), 'Unsupported propertyNames shape; keys will not be validated.');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const minProps = typeof node.minProperties === 'number' ? node.minProperties : undefined;
|
|
115
|
-
const maxProps = typeof node.maxProperties === 'number' ? node.maxProperties : undefined;
|
|
116
|
-
|
|
117
|
-
const checks: string[] = [];
|
|
118
|
-
if (keyPattern) {
|
|
119
|
-
checks.push(
|
|
120
|
-
`for (const k of Object.keys(obj)) { if (!${emitRegex(keyPattern)}.test(k)) ctx.addIssue({ code: 'custom', message: 'Invalid key: ' + k }); }`,
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
if (minProps !== undefined) {
|
|
124
|
-
checks.push(
|
|
125
|
-
`if (Object.keys(obj).length < ${minProps}) ctx.addIssue({ code: 'custom', message: 'Expected at least ${minProps} properties' });`,
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
if (maxProps !== undefined) {
|
|
129
|
-
checks.push(
|
|
130
|
-
`if (Object.keys(obj).length > ${maxProps}) ctx.addIssue({ code: 'custom', message: 'Expected at most ${maxProps} properties' });`,
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (checks.length === 0) {
|
|
135
|
-
return `z.record(z.string(), ${valueExpr})`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return `z.record(z.string(), ${valueExpr}).superRefine((obj, ctx) => { ${checks.join(' ')} })`;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function resolveSchemaIfRef(node: any, ctx: Ctx): any {
|
|
142
|
-
if (isObject(node) && typeof node.$ref === 'string') {
|
|
143
|
-
const defName = resolveLocalRef(node.$ref, ctx);
|
|
144
|
-
if (defName) return ctx.defsByName[defName];
|
|
145
|
-
}
|
|
146
|
-
return node;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function isDirectObjectishSchema(node: any): boolean {
|
|
150
|
-
if (!isObject(node)) return false;
|
|
151
|
-
if (node.type === 'object') return true;
|
|
152
|
-
return (
|
|
153
|
-
hasOwn(node, 'properties') ||
|
|
154
|
-
hasOwn(node, 'required') ||
|
|
155
|
-
hasOwn(node, 'additionalProperties') ||
|
|
156
|
-
hasOwn(node, 'unevaluatedProperties') ||
|
|
157
|
-
hasOwn(node, 'propertyNames') ||
|
|
158
|
-
hasOwn(node, 'patternProperties') ||
|
|
159
|
-
hasOwn(node, 'dependentRequired') ||
|
|
160
|
-
hasOwn(node, 'dependentSchemas')
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function expandAllOfObjectishSchemas(node: any, ctx: Ctx, path: string, seen: Set<any>): any[] | null {
|
|
165
|
-
const resolved = resolveSchemaIfRef(node, ctx);
|
|
166
|
-
if (!isObject(resolved)) return null;
|
|
167
|
-
if (seen.has(resolved)) return null;
|
|
168
|
-
seen.add(resolved);
|
|
169
|
-
|
|
170
|
-
if (isDirectObjectishSchema(resolved)) return [resolved];
|
|
171
|
-
|
|
172
|
-
if (Array.isArray((resolved as any).allOf) && (resolved as any).allOf.length > 0) {
|
|
173
|
-
const out: any[] = [];
|
|
174
|
-
for (let i = 0; i < (resolved as any).allOf.length; i++) {
|
|
175
|
-
const child = (resolved as any).allOf[i];
|
|
176
|
-
const expanded = expandAllOfObjectishSchemas(child, ctx, pathJoin(pathJoin(path, 'allOf'), i), seen);
|
|
177
|
-
if (!expanded) return null;
|
|
178
|
-
out.push(...expanded);
|
|
179
|
-
}
|
|
180
|
-
return out;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function mergePropertySchemas(a: any, b: any): any {
|
|
187
|
-
if (a === undefined) return b;
|
|
188
|
-
if (b === undefined) return a;
|
|
189
|
-
return { allOf: [a, b] };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function mergeAllOfObjectSchemas(allOfSchemas: any[], ctx: Ctx, path: string): any | null {
|
|
193
|
-
const flattened: any[] = [];
|
|
194
|
-
const seen = new Set<any>();
|
|
195
|
-
for (let i = 0; i < allOfSchemas.length; i++) {
|
|
196
|
-
const expanded = expandAllOfObjectishSchemas(allOfSchemas[i], ctx, pathJoin(pathJoin(path, 'allOf'), i), seen);
|
|
197
|
-
if (!expanded) return null;
|
|
198
|
-
flattened.push(...expanded);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const merged: any = { type: 'object', properties: {} as Record<string, any> };
|
|
202
|
-
const required = new Set<string>();
|
|
203
|
-
let additionalProperties: any = undefined;
|
|
204
|
-
let unevaluatedProperties: any = undefined;
|
|
205
|
-
|
|
206
|
-
for (let i = 0; i < flattened.length; i++) {
|
|
207
|
-
const schema = flattened[i];
|
|
208
|
-
const schemaPath = pathJoin(pathJoin(path, 'allOfFlattened'), i);
|
|
209
|
-
if (!isObject(schema)) continue;
|
|
210
|
-
|
|
211
|
-
if (isObject(schema.properties)) {
|
|
212
|
-
for (const [key, value] of Object.entries(schema.properties)) {
|
|
213
|
-
merged.properties[key] = mergePropertySchemas(merged.properties[key], value);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (Array.isArray(schema.required)) {
|
|
218
|
-
for (const requiredKey of schema.required) if (typeof requiredKey === 'string') required.add(requiredKey);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (hasOwn(schema, 'additionalProperties')) {
|
|
222
|
-
if (additionalProperties === undefined) {
|
|
223
|
-
additionalProperties = (schema as any).additionalProperties;
|
|
224
|
-
} else if (additionalProperties === false || (schema as any).additionalProperties === false) {
|
|
225
|
-
additionalProperties = false;
|
|
226
|
-
} else if (isObject(additionalProperties) && isObject((schema as any).additionalProperties)) {
|
|
227
|
-
additionalProperties = { allOf: [additionalProperties, (schema as any).additionalProperties] };
|
|
228
|
-
} else {
|
|
229
|
-
additionalProperties = isObject(additionalProperties)
|
|
230
|
-
? additionalProperties
|
|
231
|
-
: (isObject((schema as any).additionalProperties) ? (schema as any).additionalProperties : true);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if ((schema as any).unevaluatedProperties === false) {
|
|
236
|
-
unevaluatedProperties = false;
|
|
237
|
-
} else if (hasOwn(schema, 'unevaluatedProperties')) {
|
|
238
|
-
warn(ctx, pathJoin(schemaPath, 'unevaluatedProperties'), 'Unsupported unevaluatedProperties value; ignoring.');
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (hasOwn(schema, 'propertyNames') && !hasOwn(merged, 'propertyNames')) merged.propertyNames = (schema as any).propertyNames;
|
|
242
|
-
if (hasOwn(schema, 'patternProperties') && !hasOwn(merged, 'patternProperties')) merged.patternProperties = (schema as any).patternProperties;
|
|
243
|
-
if (hasOwn(schema, 'dependentRequired') && !hasOwn(merged, 'dependentRequired')) merged.dependentRequired = (schema as any).dependentRequired;
|
|
244
|
-
if (hasOwn(schema, 'dependentSchemas') && !hasOwn(merged, 'dependentSchemas')) merged.dependentSchemas = (schema as any).dependentSchemas;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (Object.keys(merged.properties).length === 0) delete merged.properties;
|
|
248
|
-
if (required.size > 0) merged.required = Array.from(required);
|
|
249
|
-
if (additionalProperties !== undefined) merged.additionalProperties = additionalProperties;
|
|
250
|
-
if (unevaluatedProperties === false) merged.unevaluatedProperties = false;
|
|
251
|
-
|
|
252
|
-
return merged;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function emitAllOf(node: any, ctx: Ctx, path: string): string {
|
|
256
|
-
const arr = node.allOf;
|
|
257
|
-
if (!Array.isArray(arr) || arr.length === 0) {
|
|
258
|
-
warn(ctx, pathJoin(path, 'allOf'), 'Expected non-empty allOf array; treating as z.any().');
|
|
259
|
-
return 'z.any()';
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const mergedObject = mergeAllOfObjectSchemas(arr, ctx, path);
|
|
263
|
-
if (mergedObject) {
|
|
264
|
-
return emitSchema(mergedObject, ctx, pathJoin(path, 'allOfMerged'));
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const parts = arr.map((schema: any, index: number) => emitSchema(schema, ctx, pathJoin(pathJoin(path, 'allOf'), index)));
|
|
268
|
-
const firstPart = parts[0];
|
|
269
|
-
if (firstPart === undefined) {
|
|
270
|
-
warn(ctx, pathJoin(path, 'allOf'), 'Expected non-empty allOf array; treating as z.any().');
|
|
271
|
-
return 'z.any()';
|
|
272
|
-
}
|
|
273
|
-
let out = firstPart;
|
|
274
|
-
for (let i = 1; i < parts.length; i++) {
|
|
275
|
-
const part = parts[i];
|
|
276
|
-
if (part === undefined) continue;
|
|
277
|
-
out = `z.intersection(${out}, ${part})`;
|
|
278
|
-
}
|
|
279
|
-
return out;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function emitAnyOfUnion(kind: 'anyOf' | 'oneOf', node: any, ctx: Ctx, path: string): string {
|
|
283
|
-
const arr = node[kind];
|
|
284
|
-
if (!Array.isArray(arr) || arr.length === 0) {
|
|
285
|
-
warn(ctx, pathJoin(path, kind), `Expected non-empty ${kind} array; treating as z.any().`);
|
|
286
|
-
return 'z.any()';
|
|
287
|
-
}
|
|
288
|
-
const parts = arr.map((schema: any, index: number) => emitSchema(schema, ctx, pathJoin(pathJoin(path, kind), index)));
|
|
289
|
-
return `z.union([${parts.join(', ')}])`;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function emitEnum(node: any, ctx: Ctx, path: string): string {
|
|
293
|
-
const values = node.enum;
|
|
294
|
-
if (!Array.isArray(values) || values.length === 0) {
|
|
295
|
-
warn(ctx, pathJoin(path, 'enum'), 'Expected non-empty enum array; treating as z.any().');
|
|
296
|
-
return 'z.any()';
|
|
297
|
-
}
|
|
298
|
-
const literals = values.map((value: any) => `z.literal(${jsonString(value)})`);
|
|
299
|
-
return `z.union([${literals.join(', ')}])`;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function emitType(typeValue: any, node: any, ctx: Ctx, path: string): string {
|
|
303
|
-
const types = Array.isArray(typeValue) ? typeValue : [typeValue];
|
|
304
|
-
const expressions: string[] = [];
|
|
305
|
-
|
|
306
|
-
for (const typeName of types) {
|
|
307
|
-
if (typeName === 'string') {
|
|
308
|
-
let s = 'z.string()';
|
|
309
|
-
if (typeof node.minLength === 'number') s += `.min(${node.minLength})`;
|
|
310
|
-
if (typeof node.maxLength === 'number') s += `.max(${node.maxLength})`;
|
|
311
|
-
if (typeof node.pattern === 'string') s += `.regex(${emitRegex(node.pattern)})`;
|
|
312
|
-
if (typeof node.format === 'string') {
|
|
313
|
-
if (node.format === 'uri') {
|
|
314
|
-
s += '.url()';
|
|
315
|
-
} else if (node.format === 'date-time') {
|
|
316
|
-
s += '.datetime()';
|
|
317
|
-
} else if (node.format === 'uuid') {
|
|
318
|
-
s += '.uuid()';
|
|
319
|
-
} else {
|
|
320
|
-
warn(ctx, pathJoin(path, 'format'), `Ignoring format '${node.format}' (not enforced by generated Zod).`);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
expressions.push(s);
|
|
324
|
-
} else if (typeName === 'number') {
|
|
325
|
-
let n = 'z.number()';
|
|
326
|
-
if (typeof node.minimum === 'number') n += `.min(${node.minimum})`;
|
|
327
|
-
if (typeof node.maximum === 'number') n += `.max(${node.maximum})`;
|
|
328
|
-
if (typeof node.exclusiveMinimum === 'number') n += `.gt(${node.exclusiveMinimum})`;
|
|
329
|
-
if (typeof node.exclusiveMaximum === 'number') n += `.lt(${node.exclusiveMaximum})`;
|
|
330
|
-
if (typeof node.multipleOf === 'number') n += `.multipleOf(${node.multipleOf})`;
|
|
331
|
-
expressions.push(n);
|
|
332
|
-
} else if (typeName === 'integer') {
|
|
333
|
-
let n = 'z.number().int()';
|
|
334
|
-
if (typeof node.minimum === 'number') n += `.min(${node.minimum})`;
|
|
335
|
-
if (typeof node.maximum === 'number') n += `.max(${node.maximum})`;
|
|
336
|
-
if (typeof node.exclusiveMinimum === 'number') n += `.gt(${node.exclusiveMinimum})`;
|
|
337
|
-
if (typeof node.exclusiveMaximum === 'number') n += `.lt(${node.exclusiveMaximum})`;
|
|
338
|
-
if (typeof node.multipleOf === 'number') n += `.multipleOf(${node.multipleOf})`;
|
|
339
|
-
expressions.push(n);
|
|
340
|
-
} else if (typeName === 'boolean') {
|
|
341
|
-
expressions.push('z.boolean()');
|
|
342
|
-
} else if (typeName === 'null') {
|
|
343
|
-
expressions.push('z.null()');
|
|
344
|
-
} else if (typeName === 'array') {
|
|
345
|
-
const itemsExpr = hasOwn(node, 'items') ? emitSchema(node.items, ctx, pathJoin(path, 'items')) : 'z.any()';
|
|
346
|
-
let a = `z.array(${itemsExpr})`;
|
|
347
|
-
if (typeof node.minItems === 'number') a += `.min(${node.minItems})`;
|
|
348
|
-
if (typeof node.maxItems === 'number') a += `.max(${node.maxItems})`;
|
|
349
|
-
if (node.uniqueItems === true) {
|
|
350
|
-
warn(ctx, pathJoin(path, 'uniqueItems'), 'uniqueItems not enforced by generated Zod.');
|
|
351
|
-
}
|
|
352
|
-
expressions.push(a);
|
|
353
|
-
} else if (typeName === 'object') {
|
|
354
|
-
expressions.push(emitObject(node, ctx, path));
|
|
355
|
-
} else {
|
|
356
|
-
warn(ctx, pathJoin(path, 'type'), `Unsupported type '${String(typeName)}'; treating as z.any().`);
|
|
357
|
-
expressions.push('z.any()');
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (expressions.length === 1) {
|
|
362
|
-
const firstExpression = expressions[0];
|
|
363
|
-
if (firstExpression !== undefined) return firstExpression;
|
|
364
|
-
}
|
|
365
|
-
return `z.union([${expressions.join(', ')}])`;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function emitObject(node: any, ctx: Ctx, path: string): string {
|
|
369
|
-
if (isRecordLikeObjectSchema(node)) {
|
|
370
|
-
return emitRecordLikeObjectSchema(node, ctx, path);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const props: Record<string, any> = isObject(node.properties) ? node.properties : {};
|
|
374
|
-
const required = Array.isArray(node.required) ? new Set(node.required.filter((x: any) => typeof x === 'string')) : new Set<string>();
|
|
375
|
-
|
|
376
|
-
const propEntries: string[] = [];
|
|
377
|
-
for (const [key, schemaNode] of Object.entries(props)) {
|
|
378
|
-
const expr = emitSchema(schemaNode, ctx, pathJoin(pathJoin(path, 'properties'), key));
|
|
379
|
-
const withOptional = required.has(key) ? expr : `${expr}.optional()`;
|
|
380
|
-
propEntries.push(`${jsonString(key)}: ${withOptional}`);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
let out = `z.object({ ${propEntries.join(', ')} })`;
|
|
384
|
-
|
|
385
|
-
const hasAllOf = Array.isArray(node.allOf) && node.allOf.length > 0;
|
|
386
|
-
const additional = node.additionalProperties;
|
|
387
|
-
const unevaluated = node.unevaluatedProperties;
|
|
388
|
-
const hasPatternProperties = isObject(node.patternProperties);
|
|
389
|
-
|
|
390
|
-
if (additional === false) {
|
|
391
|
-
if (hasAllOf) {
|
|
392
|
-
warn(ctx, pathJoin(path, 'additionalProperties'), 'additionalProperties:false with allOf is approximated; not emitting .strict().');
|
|
393
|
-
} else {
|
|
394
|
-
out += '.strict()';
|
|
395
|
-
}
|
|
396
|
-
} else if (isObject(additional) || isBooleanSchema(additional)) {
|
|
397
|
-
if (additional === true) {
|
|
398
|
-
out += '.passthrough()';
|
|
399
|
-
} else if (additional !== false) {
|
|
400
|
-
const catchallExpr = emitSchema(additional, ctx, pathJoin(path, 'additionalProperties'));
|
|
401
|
-
out += `.catchall(${catchallExpr})`;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (unevaluated === false) {
|
|
406
|
-
if (hasAllOf) {
|
|
407
|
-
warn(ctx, pathJoin(path, 'unevaluatedProperties'), 'unevaluatedProperties:false with allOf is approximated; not emitting .strict().');
|
|
408
|
-
} else if (hasPatternProperties) {
|
|
409
|
-
warn(ctx, pathJoin(path, 'unevaluatedProperties'), 'unevaluatedProperties:false with patternProperties is approximated; not emitting .strict().');
|
|
410
|
-
} else if (!(hasOwn(node, 'additionalProperties') && additional !== false)) {
|
|
411
|
-
out += '.strict()';
|
|
412
|
-
}
|
|
413
|
-
} else if (unevaluated !== undefined) {
|
|
414
|
-
warn(ctx, pathJoin(path, 'unevaluatedProperties'), 'Unsupported unevaluatedProperties value; ignoring.');
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (typeof node.minProperties === 'number') {
|
|
418
|
-
out += `.refine((o) => Object.keys(o).length >= ${node.minProperties}, { message: 'Expected at least ${node.minProperties} properties' })`;
|
|
419
|
-
}
|
|
420
|
-
if (typeof node.maxProperties === 'number') {
|
|
421
|
-
out += `.refine((o) => Object.keys(o).length <= ${node.maxProperties}, { message: 'Expected at most ${node.maxProperties} properties' })`;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (isObject(node.propertyNames)) {
|
|
425
|
-
warn(ctx, pathJoin(path, 'propertyNames'), 'propertyNames on fixed-shape object not enforced by generated Zod.');
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (hasPatternProperties) {
|
|
429
|
-
warn(ctx, pathJoin(path, 'patternProperties'), 'patternProperties not enforced by generated Zod.');
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if (isObject(node.dependentRequired) || isObject(node.dependentSchemas)) {
|
|
433
|
-
warn(ctx, pathJoin(path, 'dependentRequired'), 'dependentRequired/dependentSchemas not enforced by generated Zod.');
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return out;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function emitSchema(node: any, ctx: Ctx, path: string): string {
|
|
440
|
-
if (isBooleanSchema(node)) {
|
|
441
|
-
return node ? 'z.any()' : 'z.never()';
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (!isObject(node)) {
|
|
445
|
-
warn(ctx, path, 'Expected schema object/boolean; treating as z.any().');
|
|
446
|
-
return 'z.any()';
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (typeof node.$ref === 'string') {
|
|
450
|
-
const defName = resolveLocalRef(node.$ref, ctx);
|
|
451
|
-
if (defName) return asDefSchemaRef(defName);
|
|
452
|
-
warn(ctx, pathJoin(path, '$ref'), `Unsupported $ref '${node.$ref}'; treating as z.any().`);
|
|
453
|
-
return 'z.any()';
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (hasOwn(node, 'const')) {
|
|
457
|
-
return `z.literal(${jsonString(node.const)})`;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (hasOwn(node, 'enum')) {
|
|
461
|
-
return emitEnum(node, ctx, path);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (Array.isArray(node.allOf) && node.allOf.length > 0) {
|
|
465
|
-
return emitAllOf(node, ctx, path);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (Array.isArray(node.oneOf) && node.oneOf.length > 0) {
|
|
469
|
-
return emitAnyOfUnion('oneOf', node, ctx, path);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
if (Array.isArray(node.anyOf) && node.anyOf.length > 0) {
|
|
473
|
-
return emitAnyOfUnion('anyOf', node, ctx, path);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
if (hasOwn(node, 'type')) {
|
|
477
|
-
return emitType(node.type, node, ctx, path);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (hasOwn(node, 'properties') || hasOwn(node, 'required') || hasOwn(node, 'additionalProperties') || hasOwn(node, 'unevaluatedProperties')) {
|
|
481
|
-
return emitObject({ ...node, type: 'object' }, ctx, path);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (hasOwn(node, 'items')) {
|
|
485
|
-
return emitType('array', node, ctx, path);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (hasOwn(node, 'not')) {
|
|
489
|
-
warn(ctx, pathJoin(path, 'not'), 'Keyword `not` is not supported; treating as z.any().');
|
|
490
|
-
return 'z.any()';
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
if (hasOwn(node, 'semanticValidation')) {
|
|
494
|
-
warn(ctx, pathJoin(path, 'semanticValidation'), 'Ajv custom keyword semanticValidation is not enforced by generated Zod.');
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
return 'z.any()';
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
function buildAnchorMap(defs: Record<string, any>): Record<string, string> {
|
|
501
|
-
const map: Record<string, string> = {};
|
|
502
|
-
for (const [defName, defSchema] of Object.entries(defs)) {
|
|
503
|
-
if (!isObject(defSchema)) continue;
|
|
504
|
-
const top = defSchema.$anchor;
|
|
505
|
-
if (typeof top === 'string' && top) map[top] = defName;
|
|
506
|
-
|
|
507
|
-
const nested = (defSchema as any).
|
|
508
|
-
if (isObject(nested) && typeof nested.$anchor === 'string' && nested.$anchor) {
|
|
509
|
-
map[nested.$anchor] = defName;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
return map;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function deepClone<T>(value: T): T {
|
|
516
|
-
if (Array.isArray(value)) return value.map((item) => deepClone(item)) as any;
|
|
517
|
-
if (isObject(value)) {
|
|
518
|
-
const out: Record<string, any> = {};
|
|
519
|
-
for (const key of Object.keys(value)) out[key] = deepClone((value as any)[key]);
|
|
520
|
-
return out as any;
|
|
521
|
-
}
|
|
522
|
-
return value;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function normalizeSchemaForEmitter(schema: any): any {
|
|
526
|
-
return normalizeAllOfSiblingObjectKeywords(schema);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
export function jsonSchemaToZodExpressions(standaloneSchema: JsonSchema, rootName: string): JsonSchemaToZodResult {
|
|
530
|
-
const normalized = normalizeSchemaForEmitter(deepClone(standaloneSchema));
|
|
531
|
-
const defs: Record<string, any> = isObject(normalized?.$defs) ? normalized.$defs : {};
|
|
532
|
-
const defNames = new Set(Object.keys(defs));
|
|
533
|
-
const ctx: Ctx = {
|
|
534
|
-
defNames,
|
|
535
|
-
defsByName: defs,
|
|
536
|
-
anchorToDefName: buildAnchorMap(defs),
|
|
537
|
-
warnings: []
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
const expressionsByName: Record<string, string> = {};
|
|
541
|
-
|
|
542
|
-
for (const defName of Object.keys(defs)) {
|
|
543
|
-
expressionsByName[defName] = emitSchema(defs[defName], ctx, `$defs.${defName}`);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
expressionsByName[rootName] = emitSchema(normalized, ctx, rootName);
|
|
547
|
-
|
|
548
|
-
return { expressionsByName, warnings: ctx.warnings };
|
|
1
|
+
import { normalizeAllOfSiblingObjectKeywords } from './schemaObjectNormalization.js';
|
|
2
|
+
|
|
3
|
+
export type JsonSchema = any;
|
|
4
|
+
|
|
5
|
+
export type JsonSchemaToZodWarning = {
|
|
6
|
+
path: string;
|
|
7
|
+
message: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type JsonSchemaToZodResult = {
|
|
11
|
+
expressionsByName: Record<string, string>;
|
|
12
|
+
warnings: JsonSchemaToZodWarning[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type Ctx = {
|
|
16
|
+
defNames: Set<string>;
|
|
17
|
+
defsByName: Record<string, any>;
|
|
18
|
+
anchorToDefName: Record<string, string>;
|
|
19
|
+
warnings: JsonSchemaToZodWarning[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function isObject(v: any): v is Record<string, any> {
|
|
23
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function pathJoin(base: string, key: string | number): string {
|
|
27
|
+
if (!base) return String(key);
|
|
28
|
+
if (typeof key === 'number') return `${base}[${key}]`;
|
|
29
|
+
return `${base}.${key}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function warn(ctx: Ctx, path: string, message: string) {
|
|
33
|
+
ctx.warnings.push({ path, message });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function asDefSchemaRef(defName: string): string {
|
|
37
|
+
return `${defName}Schema`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function extractPointerDefName(ref: string): string | null {
|
|
41
|
+
if (!ref || typeof ref !== 'string') return null;
|
|
42
|
+
if (!ref.startsWith('#/')) return null;
|
|
43
|
+
const parts = ref.slice(2).split('/');
|
|
44
|
+
if (parts.length !== 2) return null;
|
|
45
|
+
if (parts[0] !== '$defs') return null;
|
|
46
|
+
const defName = parts[1];
|
|
47
|
+
if (defName === undefined) return null;
|
|
48
|
+
const name = defName.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
49
|
+
return name;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function resolveLocalRef(ref: string, ctx: Ctx): string | null {
|
|
53
|
+
const byPointer = extractPointerDefName(ref);
|
|
54
|
+
if (byPointer && ctx.defNames.has(byPointer)) return byPointer;
|
|
55
|
+
|
|
56
|
+
if (ref.startsWith('#') && !ref.startsWith('#/')) {
|
|
57
|
+
const anchor = ref.slice(1);
|
|
58
|
+
const defName = ctx.anchorToDefName[anchor];
|
|
59
|
+
if (defName) return defName;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function jsonString(value: unknown): string {
|
|
66
|
+
return JSON.stringify(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function emitRegex(pattern: string): string {
|
|
70
|
+
return `new RegExp(${jsonString(pattern)})`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function hasOwn(obj: any, key: string): boolean {
|
|
74
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isBooleanSchema(node: any): node is boolean {
|
|
78
|
+
return typeof node === 'boolean';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isRecordLikeObjectSchema(node: any): boolean {
|
|
82
|
+
if (!isObject(node)) return false;
|
|
83
|
+
if (node.type !== 'object') return false;
|
|
84
|
+
const hasFixedProps = hasOwn(node, 'properties') && isObject(node.properties) && Object.keys(node.properties).length > 0;
|
|
85
|
+
const hasPropertyNames = isObject(node.propertyNames);
|
|
86
|
+
const hasAdditionalSchema = isObject(node.additionalProperties) || isBooleanSchema(node.additionalProperties);
|
|
87
|
+
return !hasFixedProps && hasPropertyNames && hasAdditionalSchema;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function emitRecordLikeObjectSchema(node: any, ctx: Ctx, path: string): string {
|
|
91
|
+
const projectionSchemaNode = node.additionalProperties;
|
|
92
|
+
const valueExpr = emitSchema(projectionSchemaNode, ctx, pathJoin(path, 'additionalProperties'));
|
|
93
|
+
|
|
94
|
+
const pn = node.propertyNames;
|
|
95
|
+
let keyPattern: string | undefined;
|
|
96
|
+
if (isObject(pn)) {
|
|
97
|
+
if (typeof pn.pattern === 'string') {
|
|
98
|
+
keyPattern = pn.pattern;
|
|
99
|
+
} else if (typeof pn.$ref === 'string') {
|
|
100
|
+
const defName = resolveLocalRef(pn.$ref, ctx);
|
|
101
|
+
const resolved = defName ? ctx.defsByName[defName] : undefined;
|
|
102
|
+
if (isObject(resolved) && typeof resolved.pattern === 'string') {
|
|
103
|
+
keyPattern = resolved.pattern;
|
|
104
|
+
} else {
|
|
105
|
+
warn(ctx, pathJoin(path, 'propertyNames'), 'Unsupported propertyNames $ref target; keys will not be validated.');
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
warn(ctx, pathJoin(path, 'propertyNames'), 'Unsupported propertyNames shape; keys will not be validated.');
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
warn(ctx, pathJoin(path, 'propertyNames'), 'Unsupported propertyNames shape; keys will not be validated.');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const minProps = typeof node.minProperties === 'number' ? node.minProperties : undefined;
|
|
115
|
+
const maxProps = typeof node.maxProperties === 'number' ? node.maxProperties : undefined;
|
|
116
|
+
|
|
117
|
+
const checks: string[] = [];
|
|
118
|
+
if (keyPattern) {
|
|
119
|
+
checks.push(
|
|
120
|
+
`for (const k of Object.keys(obj)) { if (!${emitRegex(keyPattern)}.test(k)) ctx.addIssue({ code: 'custom', message: 'Invalid key: ' + k }); }`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (minProps !== undefined) {
|
|
124
|
+
checks.push(
|
|
125
|
+
`if (Object.keys(obj).length < ${minProps}) ctx.addIssue({ code: 'custom', message: 'Expected at least ${minProps} properties' });`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (maxProps !== undefined) {
|
|
129
|
+
checks.push(
|
|
130
|
+
`if (Object.keys(obj).length > ${maxProps}) ctx.addIssue({ code: 'custom', message: 'Expected at most ${maxProps} properties' });`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (checks.length === 0) {
|
|
135
|
+
return `z.record(z.string(), ${valueExpr})`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return `z.record(z.string(), ${valueExpr}).superRefine((obj, ctx) => { ${checks.join(' ')} })`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function resolveSchemaIfRef(node: any, ctx: Ctx): any {
|
|
142
|
+
if (isObject(node) && typeof node.$ref === 'string') {
|
|
143
|
+
const defName = resolveLocalRef(node.$ref, ctx);
|
|
144
|
+
if (defName) return ctx.defsByName[defName];
|
|
145
|
+
}
|
|
146
|
+
return node;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isDirectObjectishSchema(node: any): boolean {
|
|
150
|
+
if (!isObject(node)) return false;
|
|
151
|
+
if (node.type === 'object') return true;
|
|
152
|
+
return (
|
|
153
|
+
hasOwn(node, 'properties') ||
|
|
154
|
+
hasOwn(node, 'required') ||
|
|
155
|
+
hasOwn(node, 'additionalProperties') ||
|
|
156
|
+
hasOwn(node, 'unevaluatedProperties') ||
|
|
157
|
+
hasOwn(node, 'propertyNames') ||
|
|
158
|
+
hasOwn(node, 'patternProperties') ||
|
|
159
|
+
hasOwn(node, 'dependentRequired') ||
|
|
160
|
+
hasOwn(node, 'dependentSchemas')
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function expandAllOfObjectishSchemas(node: any, ctx: Ctx, path: string, seen: Set<any>): any[] | null {
|
|
165
|
+
const resolved = resolveSchemaIfRef(node, ctx);
|
|
166
|
+
if (!isObject(resolved)) return null;
|
|
167
|
+
if (seen.has(resolved)) return null;
|
|
168
|
+
seen.add(resolved);
|
|
169
|
+
|
|
170
|
+
if (isDirectObjectishSchema(resolved)) return [resolved];
|
|
171
|
+
|
|
172
|
+
if (Array.isArray((resolved as any).allOf) && (resolved as any).allOf.length > 0) {
|
|
173
|
+
const out: any[] = [];
|
|
174
|
+
for (let i = 0; i < (resolved as any).allOf.length; i++) {
|
|
175
|
+
const child = (resolved as any).allOf[i];
|
|
176
|
+
const expanded = expandAllOfObjectishSchemas(child, ctx, pathJoin(pathJoin(path, 'allOf'), i), seen);
|
|
177
|
+
if (!expanded) return null;
|
|
178
|
+
out.push(...expanded);
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function mergePropertySchemas(a: any, b: any): any {
|
|
187
|
+
if (a === undefined) return b;
|
|
188
|
+
if (b === undefined) return a;
|
|
189
|
+
return { allOf: [a, b] };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function mergeAllOfObjectSchemas(allOfSchemas: any[], ctx: Ctx, path: string): any | null {
|
|
193
|
+
const flattened: any[] = [];
|
|
194
|
+
const seen = new Set<any>();
|
|
195
|
+
for (let i = 0; i < allOfSchemas.length; i++) {
|
|
196
|
+
const expanded = expandAllOfObjectishSchemas(allOfSchemas[i], ctx, pathJoin(pathJoin(path, 'allOf'), i), seen);
|
|
197
|
+
if (!expanded) return null;
|
|
198
|
+
flattened.push(...expanded);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const merged: any = { type: 'object', properties: {} as Record<string, any> };
|
|
202
|
+
const required = new Set<string>();
|
|
203
|
+
let additionalProperties: any = undefined;
|
|
204
|
+
let unevaluatedProperties: any = undefined;
|
|
205
|
+
|
|
206
|
+
for (let i = 0; i < flattened.length; i++) {
|
|
207
|
+
const schema = flattened[i];
|
|
208
|
+
const schemaPath = pathJoin(pathJoin(path, 'allOfFlattened'), i);
|
|
209
|
+
if (!isObject(schema)) continue;
|
|
210
|
+
|
|
211
|
+
if (isObject(schema.properties)) {
|
|
212
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
213
|
+
merged.properties[key] = mergePropertySchemas(merged.properties[key], value);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (Array.isArray(schema.required)) {
|
|
218
|
+
for (const requiredKey of schema.required) if (typeof requiredKey === 'string') required.add(requiredKey);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (hasOwn(schema, 'additionalProperties')) {
|
|
222
|
+
if (additionalProperties === undefined) {
|
|
223
|
+
additionalProperties = (schema as any).additionalProperties;
|
|
224
|
+
} else if (additionalProperties === false || (schema as any).additionalProperties === false) {
|
|
225
|
+
additionalProperties = false;
|
|
226
|
+
} else if (isObject(additionalProperties) && isObject((schema as any).additionalProperties)) {
|
|
227
|
+
additionalProperties = { allOf: [additionalProperties, (schema as any).additionalProperties] };
|
|
228
|
+
} else {
|
|
229
|
+
additionalProperties = isObject(additionalProperties)
|
|
230
|
+
? additionalProperties
|
|
231
|
+
: (isObject((schema as any).additionalProperties) ? (schema as any).additionalProperties : true);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if ((schema as any).unevaluatedProperties === false) {
|
|
236
|
+
unevaluatedProperties = false;
|
|
237
|
+
} else if (hasOwn(schema, 'unevaluatedProperties')) {
|
|
238
|
+
warn(ctx, pathJoin(schemaPath, 'unevaluatedProperties'), 'Unsupported unevaluatedProperties value; ignoring.');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (hasOwn(schema, 'propertyNames') && !hasOwn(merged, 'propertyNames')) merged.propertyNames = (schema as any).propertyNames;
|
|
242
|
+
if (hasOwn(schema, 'patternProperties') && !hasOwn(merged, 'patternProperties')) merged.patternProperties = (schema as any).patternProperties;
|
|
243
|
+
if (hasOwn(schema, 'dependentRequired') && !hasOwn(merged, 'dependentRequired')) merged.dependentRequired = (schema as any).dependentRequired;
|
|
244
|
+
if (hasOwn(schema, 'dependentSchemas') && !hasOwn(merged, 'dependentSchemas')) merged.dependentSchemas = (schema as any).dependentSchemas;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (Object.keys(merged.properties).length === 0) delete merged.properties;
|
|
248
|
+
if (required.size > 0) merged.required = Array.from(required);
|
|
249
|
+
if (additionalProperties !== undefined) merged.additionalProperties = additionalProperties;
|
|
250
|
+
if (unevaluatedProperties === false) merged.unevaluatedProperties = false;
|
|
251
|
+
|
|
252
|
+
return merged;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function emitAllOf(node: any, ctx: Ctx, path: string): string {
|
|
256
|
+
const arr = node.allOf;
|
|
257
|
+
if (!Array.isArray(arr) || arr.length === 0) {
|
|
258
|
+
warn(ctx, pathJoin(path, 'allOf'), 'Expected non-empty allOf array; treating as z.any().');
|
|
259
|
+
return 'z.any()';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const mergedObject = mergeAllOfObjectSchemas(arr, ctx, path);
|
|
263
|
+
if (mergedObject) {
|
|
264
|
+
return emitSchema(mergedObject, ctx, pathJoin(path, 'allOfMerged'));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const parts = arr.map((schema: any, index: number) => emitSchema(schema, ctx, pathJoin(pathJoin(path, 'allOf'), index)));
|
|
268
|
+
const firstPart = parts[0];
|
|
269
|
+
if (firstPart === undefined) {
|
|
270
|
+
warn(ctx, pathJoin(path, 'allOf'), 'Expected non-empty allOf array; treating as z.any().');
|
|
271
|
+
return 'z.any()';
|
|
272
|
+
}
|
|
273
|
+
let out = firstPart;
|
|
274
|
+
for (let i = 1; i < parts.length; i++) {
|
|
275
|
+
const part = parts[i];
|
|
276
|
+
if (part === undefined) continue;
|
|
277
|
+
out = `z.intersection(${out}, ${part})`;
|
|
278
|
+
}
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function emitAnyOfUnion(kind: 'anyOf' | 'oneOf', node: any, ctx: Ctx, path: string): string {
|
|
283
|
+
const arr = node[kind];
|
|
284
|
+
if (!Array.isArray(arr) || arr.length === 0) {
|
|
285
|
+
warn(ctx, pathJoin(path, kind), `Expected non-empty ${kind} array; treating as z.any().`);
|
|
286
|
+
return 'z.any()';
|
|
287
|
+
}
|
|
288
|
+
const parts = arr.map((schema: any, index: number) => emitSchema(schema, ctx, pathJoin(pathJoin(path, kind), index)));
|
|
289
|
+
return `z.union([${parts.join(', ')}])`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function emitEnum(node: any, ctx: Ctx, path: string): string {
|
|
293
|
+
const values = node.enum;
|
|
294
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
295
|
+
warn(ctx, pathJoin(path, 'enum'), 'Expected non-empty enum array; treating as z.any().');
|
|
296
|
+
return 'z.any()';
|
|
297
|
+
}
|
|
298
|
+
const literals = values.map((value: any) => `z.literal(${jsonString(value)})`);
|
|
299
|
+
return `z.union([${literals.join(', ')}])`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function emitType(typeValue: any, node: any, ctx: Ctx, path: string): string {
|
|
303
|
+
const types = Array.isArray(typeValue) ? typeValue : [typeValue];
|
|
304
|
+
const expressions: string[] = [];
|
|
305
|
+
|
|
306
|
+
for (const typeName of types) {
|
|
307
|
+
if (typeName === 'string') {
|
|
308
|
+
let s = 'z.string()';
|
|
309
|
+
if (typeof node.minLength === 'number') s += `.min(${node.minLength})`;
|
|
310
|
+
if (typeof node.maxLength === 'number') s += `.max(${node.maxLength})`;
|
|
311
|
+
if (typeof node.pattern === 'string') s += `.regex(${emitRegex(node.pattern)})`;
|
|
312
|
+
if (typeof node.format === 'string') {
|
|
313
|
+
if (node.format === 'uri') {
|
|
314
|
+
s += '.url()';
|
|
315
|
+
} else if (node.format === 'date-time') {
|
|
316
|
+
s += '.datetime()';
|
|
317
|
+
} else if (node.format === 'uuid') {
|
|
318
|
+
s += '.uuid()';
|
|
319
|
+
} else {
|
|
320
|
+
warn(ctx, pathJoin(path, 'format'), `Ignoring format '${node.format}' (not enforced by generated Zod).`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
expressions.push(s);
|
|
324
|
+
} else if (typeName === 'number') {
|
|
325
|
+
let n = 'z.number()';
|
|
326
|
+
if (typeof node.minimum === 'number') n += `.min(${node.minimum})`;
|
|
327
|
+
if (typeof node.maximum === 'number') n += `.max(${node.maximum})`;
|
|
328
|
+
if (typeof node.exclusiveMinimum === 'number') n += `.gt(${node.exclusiveMinimum})`;
|
|
329
|
+
if (typeof node.exclusiveMaximum === 'number') n += `.lt(${node.exclusiveMaximum})`;
|
|
330
|
+
if (typeof node.multipleOf === 'number') n += `.multipleOf(${node.multipleOf})`;
|
|
331
|
+
expressions.push(n);
|
|
332
|
+
} else if (typeName === 'integer') {
|
|
333
|
+
let n = 'z.number().int()';
|
|
334
|
+
if (typeof node.minimum === 'number') n += `.min(${node.minimum})`;
|
|
335
|
+
if (typeof node.maximum === 'number') n += `.max(${node.maximum})`;
|
|
336
|
+
if (typeof node.exclusiveMinimum === 'number') n += `.gt(${node.exclusiveMinimum})`;
|
|
337
|
+
if (typeof node.exclusiveMaximum === 'number') n += `.lt(${node.exclusiveMaximum})`;
|
|
338
|
+
if (typeof node.multipleOf === 'number') n += `.multipleOf(${node.multipleOf})`;
|
|
339
|
+
expressions.push(n);
|
|
340
|
+
} else if (typeName === 'boolean') {
|
|
341
|
+
expressions.push('z.boolean()');
|
|
342
|
+
} else if (typeName === 'null') {
|
|
343
|
+
expressions.push('z.null()');
|
|
344
|
+
} else if (typeName === 'array') {
|
|
345
|
+
const itemsExpr = hasOwn(node, 'items') ? emitSchema(node.items, ctx, pathJoin(path, 'items')) : 'z.any()';
|
|
346
|
+
let a = `z.array(${itemsExpr})`;
|
|
347
|
+
if (typeof node.minItems === 'number') a += `.min(${node.minItems})`;
|
|
348
|
+
if (typeof node.maxItems === 'number') a += `.max(${node.maxItems})`;
|
|
349
|
+
if (node.uniqueItems === true) {
|
|
350
|
+
warn(ctx, pathJoin(path, 'uniqueItems'), 'uniqueItems not enforced by generated Zod.');
|
|
351
|
+
}
|
|
352
|
+
expressions.push(a);
|
|
353
|
+
} else if (typeName === 'object') {
|
|
354
|
+
expressions.push(emitObject(node, ctx, path));
|
|
355
|
+
} else {
|
|
356
|
+
warn(ctx, pathJoin(path, 'type'), `Unsupported type '${String(typeName)}'; treating as z.any().`);
|
|
357
|
+
expressions.push('z.any()');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (expressions.length === 1) {
|
|
362
|
+
const firstExpression = expressions[0];
|
|
363
|
+
if (firstExpression !== undefined) return firstExpression;
|
|
364
|
+
}
|
|
365
|
+
return `z.union([${expressions.join(', ')}])`;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function emitObject(node: any, ctx: Ctx, path: string): string {
|
|
369
|
+
if (isRecordLikeObjectSchema(node)) {
|
|
370
|
+
return emitRecordLikeObjectSchema(node, ctx, path);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const props: Record<string, any> = isObject(node.properties) ? node.properties : {};
|
|
374
|
+
const required = Array.isArray(node.required) ? new Set(node.required.filter((x: any) => typeof x === 'string')) : new Set<string>();
|
|
375
|
+
|
|
376
|
+
const propEntries: string[] = [];
|
|
377
|
+
for (const [key, schemaNode] of Object.entries(props)) {
|
|
378
|
+
const expr = emitSchema(schemaNode, ctx, pathJoin(pathJoin(path, 'properties'), key));
|
|
379
|
+
const withOptional = required.has(key) ? expr : `${expr}.optional()`;
|
|
380
|
+
propEntries.push(`${jsonString(key)}: ${withOptional}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
let out = `z.object({ ${propEntries.join(', ')} })`;
|
|
384
|
+
|
|
385
|
+
const hasAllOf = Array.isArray(node.allOf) && node.allOf.length > 0;
|
|
386
|
+
const additional = node.additionalProperties;
|
|
387
|
+
const unevaluated = node.unevaluatedProperties;
|
|
388
|
+
const hasPatternProperties = isObject(node.patternProperties);
|
|
389
|
+
|
|
390
|
+
if (additional === false) {
|
|
391
|
+
if (hasAllOf) {
|
|
392
|
+
warn(ctx, pathJoin(path, 'additionalProperties'), 'additionalProperties:false with allOf is approximated; not emitting .strict().');
|
|
393
|
+
} else {
|
|
394
|
+
out += '.strict()';
|
|
395
|
+
}
|
|
396
|
+
} else if (isObject(additional) || isBooleanSchema(additional)) {
|
|
397
|
+
if (additional === true) {
|
|
398
|
+
out += '.passthrough()';
|
|
399
|
+
} else if (additional !== false) {
|
|
400
|
+
const catchallExpr = emitSchema(additional, ctx, pathJoin(path, 'additionalProperties'));
|
|
401
|
+
out += `.catchall(${catchallExpr})`;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (unevaluated === false) {
|
|
406
|
+
if (hasAllOf) {
|
|
407
|
+
warn(ctx, pathJoin(path, 'unevaluatedProperties'), 'unevaluatedProperties:false with allOf is approximated; not emitting .strict().');
|
|
408
|
+
} else if (hasPatternProperties) {
|
|
409
|
+
warn(ctx, pathJoin(path, 'unevaluatedProperties'), 'unevaluatedProperties:false with patternProperties is approximated; not emitting .strict().');
|
|
410
|
+
} else if (!(hasOwn(node, 'additionalProperties') && additional !== false)) {
|
|
411
|
+
out += '.strict()';
|
|
412
|
+
}
|
|
413
|
+
} else if (unevaluated !== undefined) {
|
|
414
|
+
warn(ctx, pathJoin(path, 'unevaluatedProperties'), 'Unsupported unevaluatedProperties value; ignoring.');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (typeof node.minProperties === 'number') {
|
|
418
|
+
out += `.refine((o) => Object.keys(o).length >= ${node.minProperties}, { message: 'Expected at least ${node.minProperties} properties' })`;
|
|
419
|
+
}
|
|
420
|
+
if (typeof node.maxProperties === 'number') {
|
|
421
|
+
out += `.refine((o) => Object.keys(o).length <= ${node.maxProperties}, { message: 'Expected at most ${node.maxProperties} properties' })`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (isObject(node.propertyNames)) {
|
|
425
|
+
warn(ctx, pathJoin(path, 'propertyNames'), 'propertyNames on fixed-shape object not enforced by generated Zod.');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (hasPatternProperties) {
|
|
429
|
+
warn(ctx, pathJoin(path, 'patternProperties'), 'patternProperties not enforced by generated Zod.');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (isObject(node.dependentRequired) || isObject(node.dependentSchemas)) {
|
|
433
|
+
warn(ctx, pathJoin(path, 'dependentRequired'), 'dependentRequired/dependentSchemas not enforced by generated Zod.');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return out;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function emitSchema(node: any, ctx: Ctx, path: string): string {
|
|
440
|
+
if (isBooleanSchema(node)) {
|
|
441
|
+
return node ? 'z.any()' : 'z.never()';
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (!isObject(node)) {
|
|
445
|
+
warn(ctx, path, 'Expected schema object/boolean; treating as z.any().');
|
|
446
|
+
return 'z.any()';
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (typeof node.$ref === 'string') {
|
|
450
|
+
const defName = resolveLocalRef(node.$ref, ctx);
|
|
451
|
+
if (defName) return asDefSchemaRef(defName);
|
|
452
|
+
warn(ctx, pathJoin(path, '$ref'), `Unsupported $ref '${node.$ref}'; treating as z.any().`);
|
|
453
|
+
return 'z.any()';
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (hasOwn(node, 'const')) {
|
|
457
|
+
return `z.literal(${jsonString(node.const)})`;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (hasOwn(node, 'enum')) {
|
|
461
|
+
return emitEnum(node, ctx, path);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (Array.isArray(node.allOf) && node.allOf.length > 0) {
|
|
465
|
+
return emitAllOf(node, ctx, path);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (Array.isArray(node.oneOf) && node.oneOf.length > 0) {
|
|
469
|
+
return emitAnyOfUnion('oneOf', node, ctx, path);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (Array.isArray(node.anyOf) && node.anyOf.length > 0) {
|
|
473
|
+
return emitAnyOfUnion('anyOf', node, ctx, path);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (hasOwn(node, 'type')) {
|
|
477
|
+
return emitType(node.type, node, ctx, path);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (hasOwn(node, 'properties') || hasOwn(node, 'required') || hasOwn(node, 'additionalProperties') || hasOwn(node, 'unevaluatedProperties')) {
|
|
481
|
+
return emitObject({ ...node, type: 'object' }, ctx, path);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (hasOwn(node, 'items')) {
|
|
485
|
+
return emitType('array', node, ctx, path);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (hasOwn(node, 'not')) {
|
|
489
|
+
warn(ctx, pathJoin(path, 'not'), 'Keyword `not` is not supported; treating as z.any().');
|
|
490
|
+
return 'z.any()';
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (hasOwn(node, 'semanticValidation')) {
|
|
494
|
+
warn(ctx, pathJoin(path, 'semanticValidation'), 'Ajv custom keyword semanticValidation is not enforced by generated Zod.');
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return 'z.any()';
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function buildAnchorMap(defs: Record<string, any>): Record<string, string> {
|
|
501
|
+
const map: Record<string, string> = {};
|
|
502
|
+
for (const [defName, defSchema] of Object.entries(defs)) {
|
|
503
|
+
if (!isObject(defSchema)) continue;
|
|
504
|
+
const top = defSchema.$anchor;
|
|
505
|
+
if (typeof top === 'string' && top) map[top] = defName;
|
|
506
|
+
|
|
507
|
+
const nested = (defSchema as any).projectionSchema;
|
|
508
|
+
if (isObject(nested) && typeof nested.$anchor === 'string' && nested.$anchor) {
|
|
509
|
+
map[nested.$anchor] = defName;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return map;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function deepClone<T>(value: T): T {
|
|
516
|
+
if (Array.isArray(value)) return value.map((item) => deepClone(item)) as any;
|
|
517
|
+
if (isObject(value)) {
|
|
518
|
+
const out: Record<string, any> = {};
|
|
519
|
+
for (const key of Object.keys(value)) out[key] = deepClone((value as any)[key]);
|
|
520
|
+
return out as any;
|
|
521
|
+
}
|
|
522
|
+
return value;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function normalizeSchemaForEmitter(schema: any): any {
|
|
526
|
+
return normalizeAllOfSiblingObjectKeywords(schema);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export function jsonSchemaToZodExpressions(standaloneSchema: JsonSchema, rootName: string): JsonSchemaToZodResult {
|
|
530
|
+
const normalized = normalizeSchemaForEmitter(deepClone(standaloneSchema));
|
|
531
|
+
const defs: Record<string, any> = isObject(normalized?.$defs) ? normalized.$defs : {};
|
|
532
|
+
const defNames = new Set(Object.keys(defs));
|
|
533
|
+
const ctx: Ctx = {
|
|
534
|
+
defNames,
|
|
535
|
+
defsByName: defs,
|
|
536
|
+
anchorToDefName: buildAnchorMap(defs),
|
|
537
|
+
warnings: []
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
const expressionsByName: Record<string, string> = {};
|
|
541
|
+
|
|
542
|
+
for (const defName of Object.keys(defs)) {
|
|
543
|
+
expressionsByName[defName] = emitSchema(defs[defName], ctx, `$defs.${defName}`);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
expressionsByName[rootName] = emitSchema(normalized, ctx, rootName);
|
|
547
|
+
|
|
548
|
+
return { expressionsByName, warnings: ctx.warnings };
|
|
549
549
|
}
|