@gabrielbryk/json-schema-to-zod 2.8.0 → 2.10.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/CHANGELOG.md +18 -0
- package/dist/cjs/core/analyzeSchema.js +62 -0
- package/dist/cjs/core/emitZod.js +141 -0
- package/dist/cjs/generators/generateBundle.js +117 -63
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/jsonSchemaToZod.js +5 -167
- package/dist/cjs/parsers/parseAllOf.js +12 -6
- package/dist/cjs/parsers/parseBoolean.js +1 -3
- package/dist/cjs/parsers/parseIfThenElse.js +1 -1
- package/dist/cjs/parsers/parseNull.js +1 -3
- package/dist/cjs/parsers/parseObject.js +8 -3
- package/dist/cjs/parsers/parseSchema.js +130 -26
- package/dist/cjs/parsers/parseString.js +1 -1
- package/dist/cjs/utils/buildRefRegistry.js +56 -0
- package/dist/cjs/utils/omit.js +3 -2
- package/dist/cjs/utils/resolveUri.js +16 -0
- package/dist/esm/Types.js +1 -2
- package/dist/esm/cli.js +10 -12
- package/dist/esm/core/analyzeSchema.js +58 -0
- package/dist/esm/core/emitZod.js +137 -0
- package/dist/esm/generators/generateBundle.js +118 -68
- package/dist/esm/index.js +34 -46
- package/dist/esm/jsonSchemaToZod.js +5 -171
- package/dist/esm/parsers/parseAllOf.js +17 -14
- package/dist/esm/parsers/parseAnyOf.js +6 -10
- package/dist/esm/parsers/parseArray.js +11 -15
- package/dist/esm/parsers/parseBoolean.js +1 -7
- package/dist/esm/parsers/parseConst.js +1 -5
- package/dist/esm/parsers/parseDefault.js +3 -7
- package/dist/esm/parsers/parseEnum.js +1 -5
- package/dist/esm/parsers/parseIfThenElse.js +6 -10
- package/dist/esm/parsers/parseMultipleType.js +3 -7
- package/dist/esm/parsers/parseNot.js +4 -8
- package/dist/esm/parsers/parseNull.js +1 -7
- package/dist/esm/parsers/parseNullable.js +4 -8
- package/dist/esm/parsers/parseNumber.js +11 -15
- package/dist/esm/parsers/parseObject.js +33 -31
- package/dist/esm/parsers/parseOneOf.js +6 -10
- package/dist/esm/parsers/parseSchema.js +187 -87
- package/dist/esm/parsers/parseSimpleDiscriminatedOneOf.js +6 -10
- package/dist/esm/parsers/parseString.js +12 -16
- package/dist/esm/utils/anyOrUnknown.js +1 -5
- package/dist/esm/utils/buildRefRegistry.js +52 -0
- package/dist/esm/utils/cliTools.js +7 -13
- package/dist/esm/utils/cycles.js +3 -9
- package/dist/esm/utils/half.js +1 -5
- package/dist/esm/utils/jsdocs.js +3 -8
- package/dist/esm/utils/omit.js +4 -7
- package/dist/esm/utils/resolveUri.js +12 -0
- package/dist/esm/utils/withMessage.js +1 -4
- package/dist/esm/zodToJsonSchema.js +1 -4
- package/dist/types/Types.d.ts +34 -3
- package/dist/types/core/analyzeSchema.d.ts +24 -0
- package/dist/types/core/emitZod.d.ts +2 -0
- package/dist/types/generators/generateBundle.d.ts +5 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/jsonSchemaToZod.d.ts +1 -1
- package/dist/types/parsers/parseBoolean.d.ts +1 -4
- package/dist/types/parsers/parseNull.d.ts +1 -4
- package/dist/types/parsers/parseSchema.d.ts +2 -1
- package/dist/types/utils/buildRefRegistry.d.ts +12 -0
- package/dist/types/utils/resolveUri.d.ts +1 -0
- package/docs/proposals/bundle-refactor.md +43 -0
- package/docs/proposals/ref-anchor-support.md +65 -0
- package/eslint.config.js +26 -0
- package/package.json +10 -4
- /package/{jest.config.js → jest.config.cjs} +0 -0
- /package/{postcjs.js → postcjs.cjs} +0 -0
- /package/{postesm.js → postesm.cjs} +0 -0
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const jsdocs_js_1 = require("../utils/jsdocs.js");
|
|
10
|
-
const anyOrUnknown_js_1 = require("../utils/anyOrUnknown.js");
|
|
11
|
-
function parseObject(objectSchema, refs) {
|
|
1
|
+
import { parseAnyOf } from "./parseAnyOf.js";
|
|
2
|
+
import { parseOneOf } from "./parseOneOf.js";
|
|
3
|
+
import { its, parseSchema } from "./parseSchema.js";
|
|
4
|
+
import { parseAllOf } from "./parseAllOf.js";
|
|
5
|
+
import { parseIfThenElse } from "./parseIfThenElse.js";
|
|
6
|
+
import { addJsdocs } from "../utils/jsdocs.js";
|
|
7
|
+
import { anyOrUnknown } from "../utils/anyOrUnknown.js";
|
|
8
|
+
export function parseObject(objectSchema, refs) {
|
|
12
9
|
let properties = undefined;
|
|
13
10
|
if (objectSchema.properties) {
|
|
14
11
|
if (!Object.keys(objectSchema.properties).length) {
|
|
@@ -19,7 +16,7 @@ function parseObject(objectSchema, refs) {
|
|
|
19
16
|
properties += Object.keys(objectSchema.properties)
|
|
20
17
|
.map((key) => {
|
|
21
18
|
const propSchema = objectSchema.properties[key];
|
|
22
|
-
const parsedProp =
|
|
19
|
+
const parsedProp = parseSchema(propSchema, {
|
|
23
20
|
...refs,
|
|
24
21
|
path: [...refs.path, "properties", key],
|
|
25
22
|
});
|
|
@@ -35,7 +32,7 @@ function parseObject(objectSchema, refs) {
|
|
|
35
32
|
? `get ${JSON.stringify(key)}(){ return ${valueWithOptional} }`
|
|
36
33
|
: `${JSON.stringify(key)}: ${valueWithOptional}`;
|
|
37
34
|
if (refs.withJsdocs && typeof propSchema === "object") {
|
|
38
|
-
result =
|
|
35
|
+
result = addJsdocs(propSchema, result);
|
|
39
36
|
}
|
|
40
37
|
return result;
|
|
41
38
|
})
|
|
@@ -44,7 +41,7 @@ function parseObject(objectSchema, refs) {
|
|
|
44
41
|
}
|
|
45
42
|
}
|
|
46
43
|
const additionalProperties = objectSchema.additionalProperties !== undefined
|
|
47
|
-
?
|
|
44
|
+
? parseSchema(objectSchema.additionalProperties, {
|
|
48
45
|
...refs,
|
|
49
46
|
path: [...refs.path, "additionalProperties"],
|
|
50
47
|
})
|
|
@@ -55,7 +52,7 @@ function parseObject(objectSchema, refs) {
|
|
|
55
52
|
const parsedPatternProperties = Object.fromEntries(Object.entries(objectSchema.patternProperties).map(([key, value]) => {
|
|
56
53
|
return [
|
|
57
54
|
key,
|
|
58
|
-
|
|
55
|
+
parseSchema(value, {
|
|
59
56
|
...refs,
|
|
60
57
|
path: [...refs.path, "patternProperties", key],
|
|
61
58
|
}),
|
|
@@ -154,7 +151,7 @@ function parseObject(objectSchema, refs) {
|
|
|
154
151
|
// Check if there will be an .and() call that adds properties from oneOf/anyOf/allOf/if-then-else
|
|
155
152
|
// In that case, we should NOT use .strict() because it will reject the additional keys
|
|
156
153
|
// before the union gets a chance to validate them.
|
|
157
|
-
const hasCompositionKeywords =
|
|
154
|
+
const hasCompositionKeywords = its.an.anyOf(objectSchema) || its.a.oneOf(objectSchema) || its.an.allOf(objectSchema) || its.a.conditional(objectSchema);
|
|
158
155
|
let output = properties
|
|
159
156
|
? patternProperties
|
|
160
157
|
? properties + patternProperties
|
|
@@ -170,12 +167,12 @@ function parseObject(objectSchema, refs) {
|
|
|
170
167
|
? patternProperties
|
|
171
168
|
: additionalProperties
|
|
172
169
|
? `z.record(z.string(), ${additionalProperties})`
|
|
173
|
-
: `z.record(z.string(), ${
|
|
170
|
+
: `z.record(z.string(), ${anyOrUnknown(refs)})`;
|
|
174
171
|
if (unevaluated === false && properties && !hasCompositionKeywords) {
|
|
175
172
|
output += ".strict()";
|
|
176
173
|
}
|
|
177
174
|
else if (unevaluated && typeof unevaluated !== 'boolean') {
|
|
178
|
-
const unevaluatedSchema =
|
|
175
|
+
const unevaluatedSchema = parseSchema(unevaluated, {
|
|
179
176
|
...refs,
|
|
180
177
|
path: [...refs.path, "unevaluatedProperties"],
|
|
181
178
|
});
|
|
@@ -196,30 +193,33 @@ function parseObject(objectSchema, refs) {
|
|
|
196
193
|
}
|
|
197
194
|
})`;
|
|
198
195
|
}
|
|
199
|
-
if (
|
|
200
|
-
output += `.and(${
|
|
196
|
+
if (its.an.anyOf(objectSchema)) {
|
|
197
|
+
output += `.and(${parseAnyOf({
|
|
201
198
|
...objectSchema,
|
|
202
199
|
anyOf: objectSchema.anyOf.map((x) => typeof x === "object" &&
|
|
200
|
+
x !== null &&
|
|
203
201
|
!x.type &&
|
|
204
202
|
(x.properties || x.additionalProperties || x.patternProperties)
|
|
205
203
|
? { ...x, type: "object" }
|
|
206
204
|
: x),
|
|
207
205
|
}, refs)})`;
|
|
208
206
|
}
|
|
209
|
-
if (
|
|
210
|
-
output += `.and(${
|
|
207
|
+
if (its.a.oneOf(objectSchema)) {
|
|
208
|
+
output += `.and(${parseOneOf({
|
|
211
209
|
...objectSchema,
|
|
212
210
|
oneOf: objectSchema.oneOf.map((x) => typeof x === "object" &&
|
|
211
|
+
x !== null &&
|
|
213
212
|
!x.type &&
|
|
214
213
|
(x.properties || x.additionalProperties || x.patternProperties)
|
|
215
214
|
? { ...x, type: "object" }
|
|
216
215
|
: x),
|
|
217
216
|
}, refs)})`;
|
|
218
217
|
}
|
|
219
|
-
if (
|
|
220
|
-
output += `.and(${
|
|
218
|
+
if (its.an.allOf(objectSchema)) {
|
|
219
|
+
output += `.and(${parseAllOf({
|
|
221
220
|
...objectSchema,
|
|
222
221
|
allOf: objectSchema.allOf.map((x) => typeof x === "object" &&
|
|
222
|
+
x !== null &&
|
|
223
223
|
!x.type &&
|
|
224
224
|
(x.properties || x.additionalProperties || x.patternProperties)
|
|
225
225
|
? { ...x, type: "object" }
|
|
@@ -227,17 +227,18 @@ function parseObject(objectSchema, refs) {
|
|
|
227
227
|
}, refs)})`;
|
|
228
228
|
}
|
|
229
229
|
// Handle if/then/else conditionals on object schemas
|
|
230
|
-
if (
|
|
231
|
-
output += `.and(${
|
|
230
|
+
if (its.a.conditional(objectSchema)) {
|
|
231
|
+
output += `.and(${parseIfThenElse(objectSchema, refs)})`;
|
|
232
232
|
}
|
|
233
233
|
// propertyNames
|
|
234
234
|
if (objectSchema.propertyNames) {
|
|
235
235
|
const normalizedPropNames = typeof objectSchema.propertyNames === "object" &&
|
|
236
|
+
objectSchema.propertyNames !== null &&
|
|
236
237
|
!objectSchema.propertyNames.type &&
|
|
237
238
|
objectSchema.propertyNames.pattern
|
|
238
239
|
? { ...objectSchema.propertyNames, type: "string" }
|
|
239
240
|
: objectSchema.propertyNames;
|
|
240
|
-
const propNameSchema =
|
|
241
|
+
const propNameSchema = parseSchema(normalizedPropNames, {
|
|
241
242
|
...refs,
|
|
242
243
|
path: [...refs.path, "propertyNames"],
|
|
243
244
|
});
|
|
@@ -261,12 +262,12 @@ function parseObject(objectSchema, refs) {
|
|
|
261
262
|
if (entries.length) {
|
|
262
263
|
output += `.superRefine((obj, ctx) => {
|
|
263
264
|
${entries
|
|
264
|
-
.map(([key, schema]
|
|
265
|
-
const parsed =
|
|
265
|
+
.map(([key, schema]) => {
|
|
266
|
+
const parsed = parseSchema(schema, { ...refs, path: [...refs.path, "dependentSchemas", key] });
|
|
266
267
|
return `if (Object.prototype.hasOwnProperty.call(obj, ${JSON.stringify(key)})) {
|
|
267
268
|
const result = ${parsed}.safeParse(obj);
|
|
268
269
|
if (!result.success) {
|
|
269
|
-
ctx.addIssue({ code: "custom", message: "Dependent schema failed", path: [], params: { issues: result.error.issues } });
|
|
270
|
+
ctx.addIssue({ code: "custom", message: ${objectSchema.errorMessage?.dependentSchemas ?? JSON.stringify("Dependent schema failed")}, path: [], params: { issues: result.error.issues } });
|
|
270
271
|
}
|
|
271
272
|
}`;
|
|
272
273
|
})
|
|
@@ -278,7 +279,8 @@ function parseObject(objectSchema, refs) {
|
|
|
278
279
|
if (objectSchema.dependentRequired && typeof objectSchema.dependentRequired === "object") {
|
|
279
280
|
const entries = Object.entries(objectSchema.dependentRequired);
|
|
280
281
|
if (entries.length) {
|
|
281
|
-
const depRequiredMessage = objectSchema.errorMessage?.dependentRequired ??
|
|
282
|
+
const depRequiredMessage = objectSchema.errorMessage?.dependentRequired ??
|
|
283
|
+
"Dependent required properties missing";
|
|
282
284
|
output += `.superRefine((obj, ctx) => {
|
|
283
285
|
${entries
|
|
284
286
|
.map(([prop, deps]) => {
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const parseSchema_js_1 = require("./parseSchema.js");
|
|
5
|
-
const anyOrUnknown_js_1 = require("../utils/anyOrUnknown.js");
|
|
6
|
-
const parseOneOf = (schema, refs) => {
|
|
1
|
+
import { parseSchema } from "./parseSchema.js";
|
|
2
|
+
import { anyOrUnknown } from "../utils/anyOrUnknown.js";
|
|
3
|
+
export const parseOneOf = (schema, refs) => {
|
|
7
4
|
if (!schema.oneOf.length) {
|
|
8
|
-
return
|
|
5
|
+
return anyOrUnknown(refs);
|
|
9
6
|
}
|
|
10
7
|
if (schema.oneOf.length === 1) {
|
|
11
|
-
return
|
|
8
|
+
return parseSchema(schema.oneOf[0], {
|
|
12
9
|
...refs,
|
|
13
10
|
path: [...refs.path, "oneOf", 0],
|
|
14
11
|
});
|
|
15
12
|
}
|
|
16
13
|
// Generate parsed schemas for each oneOf option
|
|
17
|
-
const parsedSchemas = schema.oneOf.map((s, i) =>
|
|
14
|
+
const parsedSchemas = schema.oneOf.map((s, i) => parseSchema(s, {
|
|
18
15
|
...refs,
|
|
19
16
|
path: [...refs.path, "oneOf", i],
|
|
20
17
|
}));
|
|
@@ -50,4 +47,3 @@ const parseOneOf = (schema, refs) => {
|
|
|
50
47
|
// Default: use simple z.union() (at least one must match)
|
|
51
48
|
return `z.union([${parsedSchemas.join(", ")}])`;
|
|
52
49
|
};
|
|
53
|
-
exports.parseOneOf = parseOneOf;
|
|
@@ -1,36 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) => {
|
|
1
|
+
import { parseAnyOf } from "./parseAnyOf.js";
|
|
2
|
+
import { parseBoolean } from "./parseBoolean.js";
|
|
3
|
+
import { parseDefault } from "./parseDefault.js";
|
|
4
|
+
import { parseMultipleType } from "./parseMultipleType.js";
|
|
5
|
+
import { parseNot } from "./parseNot.js";
|
|
6
|
+
import { parseNull } from "./parseNull.js";
|
|
7
|
+
import { parseAllOf } from "./parseAllOf.js";
|
|
8
|
+
import { parseArray } from "./parseArray.js";
|
|
9
|
+
import { parseConst } from "./parseConst.js";
|
|
10
|
+
import { parseEnum } from "./parseEnum.js";
|
|
11
|
+
import { parseIfThenElse } from "./parseIfThenElse.js";
|
|
12
|
+
import { parseNumber } from "./parseNumber.js";
|
|
13
|
+
import { parseObject } from "./parseObject.js";
|
|
14
|
+
import { parseString } from "./parseString.js";
|
|
15
|
+
import { parseOneOf } from "./parseOneOf.js";
|
|
16
|
+
import { parseSimpleDiscriminatedOneOf } from "./parseSimpleDiscriminatedOneOf.js";
|
|
17
|
+
import { parseNullable } from "./parseNullable.js";
|
|
18
|
+
import { anyOrUnknown } from "../utils/anyOrUnknown.js";
|
|
19
|
+
import { resolveUri } from "../utils/resolveUri.js";
|
|
20
|
+
import { buildRefRegistry } from "../utils/buildRefRegistry.js";
|
|
21
|
+
export const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) => {
|
|
23
22
|
// Ensure ref bookkeeping exists so $ref declarations and getter-based recursion work
|
|
24
23
|
refs.root = refs.root ?? schema;
|
|
24
|
+
refs.rootBaseUri = refs.rootBaseUri ?? "root:///";
|
|
25
25
|
refs.declarations = refs.declarations ?? new Map();
|
|
26
26
|
refs.dependencies = refs.dependencies ?? new Map();
|
|
27
27
|
refs.inProgress = refs.inProgress ?? new Set();
|
|
28
28
|
refs.refNameByPointer = refs.refNameByPointer ?? new Map();
|
|
29
29
|
refs.usedNames = refs.usedNames ?? new Set();
|
|
30
30
|
if (typeof schema !== "object")
|
|
31
|
-
return schema ?
|
|
31
|
+
return schema ? anyOrUnknown(refs) : "z.never()";
|
|
32
|
+
const parentBase = refs.currentBaseUri ?? refs.rootBaseUri ?? "root:///";
|
|
33
|
+
const baseUri = typeof schema.$id === "string" ? resolveUri(parentBase, schema.$id) : parentBase;
|
|
34
|
+
const dynamicAnchors = Array.isArray(refs.dynamicAnchors) ? [...refs.dynamicAnchors] : [];
|
|
35
|
+
if (typeof schema.$dynamicAnchor === "string") {
|
|
36
|
+
dynamicAnchors.push({
|
|
37
|
+
name: schema.$dynamicAnchor,
|
|
38
|
+
uri: baseUri,
|
|
39
|
+
path: refs.path,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
32
42
|
if (refs.parserOverride) {
|
|
33
|
-
const custom = refs.parserOverride(schema, refs);
|
|
43
|
+
const custom = refs.parserOverride(schema, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
|
|
34
44
|
if (typeof custom === "string") {
|
|
35
45
|
return custom;
|
|
36
46
|
}
|
|
@@ -41,7 +51,7 @@ const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) =>
|
|
|
41
51
|
return seen.r;
|
|
42
52
|
}
|
|
43
53
|
if (refs.depth === undefined || seen.n >= refs.depth) {
|
|
44
|
-
return
|
|
54
|
+
return anyOrUnknown(refs);
|
|
45
55
|
}
|
|
46
56
|
seen.n += 1;
|
|
47
57
|
}
|
|
@@ -49,15 +59,15 @@ const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) =>
|
|
|
49
59
|
seen = { r: undefined, n: 0 };
|
|
50
60
|
refs.seen.set(schema, seen);
|
|
51
61
|
}
|
|
52
|
-
if (
|
|
53
|
-
const parsedRef = parseRef(schema, refs);
|
|
62
|
+
if (its.a.ref(schema)) {
|
|
63
|
+
const parsedRef = parseRef(schema, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
|
|
54
64
|
seen.r = parsedRef;
|
|
55
65
|
return parsedRef;
|
|
56
66
|
}
|
|
57
|
-
let parsed = selectParser(schema, refs);
|
|
67
|
+
let parsed = selectParser(schema, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
|
|
58
68
|
if (!blockMeta) {
|
|
59
69
|
if (!refs.withoutDescribes) {
|
|
60
|
-
parsed = addDescribes(schema, parsed, refs);
|
|
70
|
+
parsed = addDescribes(schema, parsed, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
|
|
61
71
|
}
|
|
62
72
|
if (!refs.withoutDefaults) {
|
|
63
73
|
parsed = addDefaults(schema, parsed);
|
|
@@ -67,19 +77,24 @@ const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) =>
|
|
|
67
77
|
seen.r = parsed;
|
|
68
78
|
return parsed;
|
|
69
79
|
};
|
|
70
|
-
exports.parseSchema = parseSchema;
|
|
71
80
|
const parseRef = (schema, refs) => {
|
|
72
|
-
const
|
|
81
|
+
const refValue = schema.$dynamicRef ?? schema.$ref;
|
|
82
|
+
if (typeof refValue !== "string") {
|
|
83
|
+
return anyOrUnknown(refs);
|
|
84
|
+
}
|
|
85
|
+
const resolved = resolveRef(schema, refValue, refs);
|
|
73
86
|
if (!resolved) {
|
|
74
|
-
|
|
87
|
+
refs.onUnresolvedRef?.(refValue, refs.path);
|
|
88
|
+
return anyOrUnknown(refs);
|
|
75
89
|
}
|
|
76
|
-
const { schema: target, path } = resolved;
|
|
77
|
-
const refName = getOrCreateRefName(
|
|
90
|
+
const { schema: target, path, pointerKey } = resolved;
|
|
91
|
+
const refName = getOrCreateRefName(pointerKey, path, refs);
|
|
78
92
|
if (!refs.declarations.has(refName) && !refs.inProgress.has(refName)) {
|
|
79
93
|
refs.inProgress.add(refName);
|
|
80
|
-
const declaration =
|
|
94
|
+
const declaration = parseSchema(target, {
|
|
81
95
|
...refs,
|
|
82
96
|
path,
|
|
97
|
+
currentBaseUri: resolved.baseUri,
|
|
83
98
|
currentSchemaName: refName,
|
|
84
99
|
root: refs.root,
|
|
85
100
|
});
|
|
@@ -132,23 +147,96 @@ const addDescribes = (schema, parsed, refs) => {
|
|
|
132
147
|
}
|
|
133
148
|
return parsed;
|
|
134
149
|
};
|
|
135
|
-
const resolveRef = (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
const resolveRef = (schemaNode, ref, refs) => {
|
|
151
|
+
const base = refs.currentBaseUri ?? refs.rootBaseUri ?? "root:///";
|
|
152
|
+
// Handle dynamicRef lookup via dynamicAnchors stack
|
|
153
|
+
const isDynamic = typeof schemaNode.$dynamicRef === "string";
|
|
154
|
+
if (isDynamic && refs.dynamicAnchors && ref.startsWith("#")) {
|
|
155
|
+
const name = ref.slice(1);
|
|
156
|
+
for (let i = refs.dynamicAnchors.length - 1; i >= 0; i -= 1) {
|
|
157
|
+
const entry = refs.dynamicAnchors[i];
|
|
158
|
+
if (entry.name === name) {
|
|
159
|
+
const key = `${entry.uri}#${name}`;
|
|
160
|
+
const target = refs.refRegistry?.get(key);
|
|
161
|
+
if (target) {
|
|
162
|
+
return { schema: target.schema, path: target.path, baseUri: target.baseUri, pointerKey: key };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Resolve URI against base
|
|
168
|
+
const resolvedUri = resolveUri(base, ref);
|
|
169
|
+
const [uriBase, fragment] = resolvedUri.split("#");
|
|
170
|
+
const key = fragment ? `${uriBase}#${fragment}` : uriBase;
|
|
171
|
+
let regEntry = refs.refRegistry?.get(key);
|
|
172
|
+
if (regEntry) {
|
|
173
|
+
return { schema: regEntry.schema, path: regEntry.path, baseUri: regEntry.baseUri, pointerKey: key };
|
|
174
|
+
}
|
|
175
|
+
// Legacy recursive ref: treat as dynamic to __recursive__
|
|
176
|
+
if (schemaNode.$recursiveRef) {
|
|
177
|
+
const recursiveKey = `${base}#__recursive__`;
|
|
178
|
+
regEntry = refs.refRegistry?.get(recursiveKey);
|
|
179
|
+
if (regEntry) {
|
|
180
|
+
return {
|
|
181
|
+
schema: regEntry.schema,
|
|
182
|
+
path: regEntry.path,
|
|
183
|
+
baseUri: regEntry.baseUri,
|
|
184
|
+
pointerKey: recursiveKey,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// External resolver hook
|
|
189
|
+
const extBase = uriBaseFromRef(resolvedUri);
|
|
190
|
+
if (refs.resolveExternalRef && extBase && !isLocalBase(extBase, refs.rootBaseUri ?? "")) {
|
|
191
|
+
const loaded = refs.resolveExternalRef(extBase);
|
|
192
|
+
if (loaded) {
|
|
193
|
+
// If async resolver is used synchronously here, it will be ignored; keep simple sync for now
|
|
194
|
+
const maybePromise = loaded;
|
|
195
|
+
const schema = typeof maybePromise.then === "function"
|
|
196
|
+
? undefined
|
|
197
|
+
: loaded;
|
|
198
|
+
if (schema) {
|
|
199
|
+
const { registry } = buildRefRegistry(schema, extBase);
|
|
200
|
+
registry.forEach((entry, k) => refs.refRegistry?.set(k, entry));
|
|
201
|
+
regEntry = refs.refRegistry?.get(key);
|
|
202
|
+
if (regEntry) {
|
|
203
|
+
return {
|
|
204
|
+
schema: regEntry.schema,
|
|
205
|
+
path: regEntry.path,
|
|
206
|
+
baseUri: regEntry.baseUri,
|
|
207
|
+
pointerKey: key,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Backward compatibility: JSON Pointer into root
|
|
214
|
+
if (refs.root && ref.startsWith("#/")) {
|
|
215
|
+
const rawSegments = ref
|
|
216
|
+
.slice(2)
|
|
217
|
+
.split("/")
|
|
218
|
+
.filter((segment) => segment.length > 0)
|
|
219
|
+
.map(decodePointerSegment);
|
|
220
|
+
let current = refs.root;
|
|
221
|
+
for (const segment of rawSegments) {
|
|
222
|
+
if (typeof current !== "object" || current === null)
|
|
223
|
+
return undefined;
|
|
224
|
+
current = current[segment];
|
|
225
|
+
}
|
|
226
|
+
return { schema: current, path: rawSegments, baseUri: base, pointerKey: ref };
|
|
227
|
+
}
|
|
228
|
+
return undefined;
|
|
150
229
|
};
|
|
151
230
|
const decodePointerSegment = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
231
|
+
const uriBaseFromRef = (resolvedUri) => {
|
|
232
|
+
const hashIdx = resolvedUri.indexOf("#");
|
|
233
|
+
return hashIdx === -1 ? resolvedUri : resolvedUri.slice(0, hashIdx);
|
|
234
|
+
};
|
|
235
|
+
const isLocalBase = (base, rootBase) => {
|
|
236
|
+
if (!rootBase)
|
|
237
|
+
return false;
|
|
238
|
+
return base === rootBase;
|
|
239
|
+
};
|
|
152
240
|
const getOrCreateRefName = (pointer, path, refs) => {
|
|
153
241
|
if (refs.refNameByPointer?.has(pointer)) {
|
|
154
242
|
return refs.refNameByPointer.get(pointer);
|
|
@@ -159,12 +247,24 @@ const getOrCreateRefName = (pointer, path, refs) => {
|
|
|
159
247
|
return preferred;
|
|
160
248
|
};
|
|
161
249
|
const buildNameFromPath = (path, used) => {
|
|
162
|
-
const filtered = path
|
|
250
|
+
const filtered = path
|
|
251
|
+
.map((segment, idx) => {
|
|
252
|
+
if (idx === 0 && (segment === "$defs" || segment === "definitions")) {
|
|
253
|
+
return undefined; // root-level defs prefix is redundant for naming
|
|
254
|
+
}
|
|
255
|
+
if (segment === "properties")
|
|
256
|
+
return undefined; // skip noisy properties segment
|
|
257
|
+
if (segment === "$defs" || segment === "definitions")
|
|
258
|
+
return "Defs";
|
|
259
|
+
return segment;
|
|
260
|
+
})
|
|
261
|
+
.filter((segment) => segment !== undefined);
|
|
163
262
|
const base = filtered.length
|
|
164
263
|
? filtered
|
|
165
264
|
.map((segment) => typeof segment === "number"
|
|
166
265
|
? `Ref${segment}`
|
|
167
266
|
: segment
|
|
267
|
+
.toString()
|
|
168
268
|
.replace(/[^a-zA-Z0-9_$]/g, " ")
|
|
169
269
|
.split(" ")
|
|
170
270
|
.filter(Boolean)
|
|
@@ -201,60 +301,60 @@ const addAnnotations = (schema, parsed) => {
|
|
|
201
301
|
return parsed;
|
|
202
302
|
};
|
|
203
303
|
const selectParser = (schema, refs) => {
|
|
204
|
-
if (
|
|
205
|
-
return
|
|
304
|
+
if (its.a.nullable(schema)) {
|
|
305
|
+
return parseNullable(schema, refs);
|
|
206
306
|
}
|
|
207
|
-
else if (
|
|
208
|
-
return
|
|
307
|
+
else if (its.an.object(schema)) {
|
|
308
|
+
return parseObject(schema, refs);
|
|
209
309
|
}
|
|
210
|
-
else if (
|
|
211
|
-
return
|
|
310
|
+
else if (its.an.array(schema)) {
|
|
311
|
+
return parseArray(schema, refs);
|
|
212
312
|
}
|
|
213
|
-
else if (
|
|
214
|
-
return
|
|
313
|
+
else if (its.an.anyOf(schema)) {
|
|
314
|
+
return parseAnyOf(schema, refs);
|
|
215
315
|
}
|
|
216
|
-
else if (
|
|
217
|
-
return
|
|
316
|
+
else if (its.an.allOf(schema)) {
|
|
317
|
+
return parseAllOf(schema, refs);
|
|
218
318
|
}
|
|
219
|
-
else if (
|
|
220
|
-
return
|
|
319
|
+
else if (its.a.simpleDiscriminatedOneOf(schema)) {
|
|
320
|
+
return parseSimpleDiscriminatedOneOf(schema, refs);
|
|
221
321
|
}
|
|
222
|
-
else if (
|
|
223
|
-
return
|
|
322
|
+
else if (its.a.oneOf(schema)) {
|
|
323
|
+
return parseOneOf(schema, refs);
|
|
224
324
|
}
|
|
225
|
-
else if (
|
|
226
|
-
return
|
|
325
|
+
else if (its.a.not(schema)) {
|
|
326
|
+
return parseNot(schema, refs);
|
|
227
327
|
}
|
|
228
|
-
else if (
|
|
229
|
-
return
|
|
328
|
+
else if (its.an.enum(schema)) {
|
|
329
|
+
return parseEnum(schema); //<-- needs to come before primitives
|
|
230
330
|
}
|
|
231
|
-
else if (
|
|
232
|
-
return
|
|
331
|
+
else if (its.a.const(schema)) {
|
|
332
|
+
return parseConst(schema);
|
|
233
333
|
}
|
|
234
|
-
else if (
|
|
235
|
-
return
|
|
334
|
+
else if (its.a.multipleType(schema)) {
|
|
335
|
+
return parseMultipleType(schema, refs);
|
|
236
336
|
}
|
|
237
|
-
else if (
|
|
238
|
-
return
|
|
337
|
+
else if (its.a.primitive(schema, "string")) {
|
|
338
|
+
return parseString(schema, refs);
|
|
239
339
|
}
|
|
240
|
-
else if (
|
|
241
|
-
|
|
242
|
-
return
|
|
340
|
+
else if (its.a.primitive(schema, "number") ||
|
|
341
|
+
its.a.primitive(schema, "integer")) {
|
|
342
|
+
return parseNumber(schema);
|
|
243
343
|
}
|
|
244
|
-
else if (
|
|
245
|
-
return
|
|
344
|
+
else if (its.a.primitive(schema, "boolean")) {
|
|
345
|
+
return parseBoolean();
|
|
246
346
|
}
|
|
247
|
-
else if (
|
|
248
|
-
return
|
|
347
|
+
else if (its.a.primitive(schema, "null")) {
|
|
348
|
+
return parseNull();
|
|
249
349
|
}
|
|
250
|
-
else if (
|
|
251
|
-
return
|
|
350
|
+
else if (its.a.conditional(schema)) {
|
|
351
|
+
return parseIfThenElse(schema, refs);
|
|
252
352
|
}
|
|
253
353
|
else {
|
|
254
|
-
return
|
|
354
|
+
return parseDefault(schema, refs);
|
|
255
355
|
}
|
|
256
356
|
};
|
|
257
|
-
|
|
357
|
+
export const its = {
|
|
258
358
|
an: {
|
|
259
359
|
object: (x) => x.type === "object",
|
|
260
360
|
array: (x) => x.type === "array",
|
|
@@ -266,7 +366,7 @@ exports.its = {
|
|
|
266
366
|
nullable: (x) => x.nullable === true,
|
|
267
367
|
multipleType: (x) => Array.isArray(x.type),
|
|
268
368
|
not: (x) => x.not !== undefined,
|
|
269
|
-
ref: (x) => typeof x.$ref === "string",
|
|
369
|
+
ref: (x) => typeof x.$ref === "string" || typeof x.$dynamicRef === "string",
|
|
270
370
|
const: (x) => x.const !== undefined,
|
|
271
371
|
primitive: (x, p) => x.type === p,
|
|
272
372
|
conditional: (x) => Boolean("if" in x && x.if && "then" in x && "else" in x && x.then && x.else),
|
|
@@ -1,21 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const parseSchema_js_1 = require("./parseSchema.js");
|
|
5
|
-
const anyOrUnknown_js_1 = require("../utils/anyOrUnknown.js");
|
|
6
|
-
const parseSimpleDiscriminatedOneOf = (schema, refs) => {
|
|
1
|
+
import { parseSchema } from "./parseSchema.js";
|
|
2
|
+
import { anyOrUnknown } from "../utils/anyOrUnknown.js";
|
|
3
|
+
export const parseSimpleDiscriminatedOneOf = (schema, refs) => {
|
|
7
4
|
const discriminator = schema.discriminator.propertyName;
|
|
8
|
-
const options = schema.oneOf.map((option, i) =>
|
|
5
|
+
const options = schema.oneOf.map((option, i) => parseSchema(option, {
|
|
9
6
|
...refs,
|
|
10
7
|
path: [...refs.path, "oneOf", i],
|
|
11
8
|
}));
|
|
12
9
|
return schema.oneOf.length
|
|
13
10
|
? schema.oneOf.length === 1
|
|
14
|
-
?
|
|
11
|
+
? parseSchema(schema.oneOf[0], {
|
|
15
12
|
...refs,
|
|
16
13
|
path: [...refs.path, "oneOf", 0],
|
|
17
14
|
})
|
|
18
15
|
: `z.discriminatedUnion("${discriminator}", [${options.join(", ")}])`
|
|
19
|
-
:
|
|
16
|
+
: anyOrUnknown(refs);
|
|
20
17
|
};
|
|
21
|
-
exports.parseSimpleDiscriminatedOneOf = parseSimpleDiscriminatedOneOf;
|