@gabrielbryk/json-schema-to-zod 2.7.4 → 2.9.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 +23 -1
- package/dist/cjs/core/analyzeSchema.js +62 -0
- package/dist/cjs/core/emitZod.js +141 -0
- package/dist/cjs/generators/generateBundle.js +355 -0
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/jsonSchemaToZod.js +5 -73
- package/dist/cjs/parsers/parseArray.js +34 -15
- package/dist/cjs/parsers/parseIfThenElse.js +2 -1
- package/dist/cjs/parsers/parseNumber.js +81 -39
- package/dist/cjs/parsers/parseObject.js +24 -0
- package/dist/cjs/parsers/parseSchema.js +147 -25
- package/dist/cjs/parsers/parseString.js +294 -54
- package/dist/cjs/utils/buildRefRegistry.js +56 -0
- package/dist/cjs/utils/cycles.js +113 -0
- package/dist/cjs/utils/resolveUri.js +16 -0
- package/dist/cjs/utils/withMessage.js +4 -5
- package/dist/esm/core/analyzeSchema.js +58 -0
- package/dist/esm/core/emitZod.js +137 -0
- package/dist/esm/generators/generateBundle.js +351 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/jsonSchemaToZod.js +5 -73
- package/dist/esm/parsers/parseArray.js +34 -15
- package/dist/esm/parsers/parseIfThenElse.js +2 -1
- package/dist/esm/parsers/parseNumber.js +81 -39
- package/dist/esm/parsers/parseObject.js +24 -0
- package/dist/esm/parsers/parseSchema.js +147 -25
- package/dist/esm/parsers/parseString.js +294 -54
- package/dist/esm/utils/buildRefRegistry.js +52 -0
- package/dist/esm/utils/cycles.js +107 -0
- package/dist/esm/utils/resolveUri.js +12 -0
- package/dist/esm/utils/withMessage.js +4 -5
- package/dist/types/Types.d.ts +36 -0
- 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 +62 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/jsonSchemaToZod.d.ts +1 -1
- package/dist/types/parsers/parseSchema.d.ts +2 -1
- package/dist/types/parsers/parseString.d.ts +2 -2
- package/dist/types/utils/buildRefRegistry.d.ts +12 -0
- package/dist/types/utils/cycles.d.ts +7 -0
- package/dist/types/utils/jsdocs.d.ts +1 -1
- package/dist/types/utils/resolveUri.d.ts +1 -0
- package/dist/types/utils/withMessage.d.ts +6 -1
- 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,70 +1,112 @@
|
|
|
1
1
|
import { withMessage } from "../utils/withMessage.js";
|
|
2
2
|
export const parseNumber = (schema) => {
|
|
3
|
-
|
|
3
|
+
const formatError = schema.errorMessage?.format;
|
|
4
|
+
const numericFormatMap = {
|
|
5
|
+
int32: "z.int32",
|
|
6
|
+
uint32: "z.uint32",
|
|
7
|
+
float32: "z.float32",
|
|
8
|
+
float64: "z.float64",
|
|
9
|
+
safeint: "z.safeint",
|
|
10
|
+
int64: "z.int64",
|
|
11
|
+
uint64: "z.uint64",
|
|
12
|
+
};
|
|
13
|
+
const mappedFormat = schema.format && numericFormatMap[schema.format] ? numericFormatMap[schema.format] : undefined;
|
|
14
|
+
const formatParams = formatError !== undefined ? `{ error: ${JSON.stringify(formatError)} }` : "";
|
|
15
|
+
let r = mappedFormat ? `${mappedFormat}(${formatParams})` : "z.number()";
|
|
4
16
|
if (schema.type === "integer") {
|
|
5
|
-
|
|
17
|
+
if (!mappedFormat) {
|
|
18
|
+
r += withMessage(schema, "type", () => ({
|
|
19
|
+
opener: ".int(",
|
|
20
|
+
closer: ")",
|
|
21
|
+
messagePrefix: "{ error: ",
|
|
22
|
+
messageCloser: " })",
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
6
25
|
}
|
|
7
26
|
else {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
27
|
+
if (!mappedFormat) {
|
|
28
|
+
r += withMessage(schema, "format", ({ value }) => {
|
|
29
|
+
if (value === "int64") {
|
|
30
|
+
return {
|
|
31
|
+
opener: ".int(",
|
|
32
|
+
closer: ")",
|
|
33
|
+
messagePrefix: "{ error: ",
|
|
34
|
+
messageCloser: " })",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
13
39
|
}
|
|
14
40
|
r += withMessage(schema, "multipleOf", ({ value, json }) => {
|
|
15
41
|
if (value === 1) {
|
|
16
42
|
if (r.startsWith("z.number().int(")) {
|
|
17
43
|
return;
|
|
18
44
|
}
|
|
19
|
-
return
|
|
45
|
+
return {
|
|
46
|
+
opener: ".int(",
|
|
47
|
+
closer: ")",
|
|
48
|
+
messagePrefix: "{ error: ",
|
|
49
|
+
messageCloser: " })",
|
|
50
|
+
};
|
|
20
51
|
}
|
|
21
|
-
return
|
|
52
|
+
return {
|
|
53
|
+
opener: `.multipleOf(${json}`,
|
|
54
|
+
closer: ")",
|
|
55
|
+
messagePrefix: ", { error: ",
|
|
56
|
+
messageCloser: " })",
|
|
57
|
+
};
|
|
22
58
|
});
|
|
23
59
|
if (typeof schema.minimum === "number") {
|
|
24
60
|
if (schema.exclusiveMinimum === true) {
|
|
25
|
-
r += withMessage(schema, "minimum", ({ json }) =>
|
|
26
|
-
`.gt(${json}`,
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
61
|
+
r += withMessage(schema, "minimum", ({ json }) => ({
|
|
62
|
+
opener: `.gt(${json}`,
|
|
63
|
+
closer: ")",
|
|
64
|
+
messagePrefix: ", { error: ",
|
|
65
|
+
messageCloser: " })",
|
|
66
|
+
}));
|
|
30
67
|
}
|
|
31
68
|
else {
|
|
32
|
-
r += withMessage(schema, "minimum", ({ json }) =>
|
|
33
|
-
`.gte(${json}`,
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
69
|
+
r += withMessage(schema, "minimum", ({ json }) => ({
|
|
70
|
+
opener: `.gte(${json}`,
|
|
71
|
+
closer: ")",
|
|
72
|
+
messagePrefix: ", { error: ",
|
|
73
|
+
messageCloser: " })",
|
|
74
|
+
}));
|
|
37
75
|
}
|
|
38
76
|
}
|
|
39
77
|
else if (typeof schema.exclusiveMinimum === "number") {
|
|
40
|
-
r += withMessage(schema, "exclusiveMinimum", ({ json }) =>
|
|
41
|
-
`.gt(${json}`,
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
78
|
+
r += withMessage(schema, "exclusiveMinimum", ({ json }) => ({
|
|
79
|
+
opener: `.gt(${json}`,
|
|
80
|
+
closer: ")",
|
|
81
|
+
messagePrefix: ", { error: ",
|
|
82
|
+
messageCloser: " })",
|
|
83
|
+
}));
|
|
45
84
|
}
|
|
46
85
|
if (typeof schema.maximum === "number") {
|
|
47
86
|
if (schema.exclusiveMaximum === true) {
|
|
48
|
-
r += withMessage(schema, "maximum", ({ json }) =>
|
|
49
|
-
`.lt(${json}`,
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
|
|
87
|
+
r += withMessage(schema, "maximum", ({ json }) => ({
|
|
88
|
+
opener: `.lt(${json}`,
|
|
89
|
+
closer: ")",
|
|
90
|
+
messagePrefix: ", { error: ",
|
|
91
|
+
messageCloser: " })",
|
|
92
|
+
}));
|
|
53
93
|
}
|
|
54
94
|
else {
|
|
55
|
-
r += withMessage(schema, "maximum", ({ json }) =>
|
|
56
|
-
`.lte(${json}`,
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
|
|
95
|
+
r += withMessage(schema, "maximum", ({ json }) => ({
|
|
96
|
+
opener: `.lte(${json}`,
|
|
97
|
+
closer: ")",
|
|
98
|
+
messagePrefix: ", { error: ",
|
|
99
|
+
messageCloser: " })",
|
|
100
|
+
}));
|
|
60
101
|
}
|
|
61
102
|
}
|
|
62
103
|
else if (typeof schema.exclusiveMaximum === "number") {
|
|
63
|
-
r += withMessage(schema, "exclusiveMaximum", ({ json }) =>
|
|
64
|
-
`.lt(${json}`,
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
|
|
104
|
+
r += withMessage(schema, "exclusiveMaximum", ({ json }) => ({
|
|
105
|
+
opener: `.lt(${json}`,
|
|
106
|
+
closer: ")",
|
|
107
|
+
messagePrefix: ", { error: ",
|
|
108
|
+
messageCloser: " })",
|
|
109
|
+
}));
|
|
68
110
|
}
|
|
69
111
|
return r;
|
|
70
112
|
};
|
|
@@ -268,6 +268,30 @@ export function parseObject(objectSchema, refs) {
|
|
|
268
268
|
}`;
|
|
269
269
|
})
|
|
270
270
|
.join("\n ")}
|
|
271
|
+
})`;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// dependentRequired
|
|
275
|
+
if (objectSchema.dependentRequired && typeof objectSchema.dependentRequired === "object") {
|
|
276
|
+
const entries = Object.entries(objectSchema.dependentRequired);
|
|
277
|
+
if (entries.length) {
|
|
278
|
+
const depRequiredMessage = objectSchema.errorMessage?.dependentRequired ?? "Dependent required properties missing";
|
|
279
|
+
output += `.superRefine((obj, ctx) => {
|
|
280
|
+
${entries
|
|
281
|
+
.map(([prop, deps]) => {
|
|
282
|
+
const arr = Array.isArray(deps) ? deps : [];
|
|
283
|
+
if (!arr.length)
|
|
284
|
+
return "";
|
|
285
|
+
const jsonDeps = JSON.stringify(arr);
|
|
286
|
+
return `if (Object.prototype.hasOwnProperty.call(obj, ${JSON.stringify(prop)})) {
|
|
287
|
+
const missing = ${jsonDeps}.filter((d) => !Object.prototype.hasOwnProperty.call(obj, d));
|
|
288
|
+
if (missing.length) {
|
|
289
|
+
ctx.addIssue({ code: "custom", message: ${JSON.stringify(depRequiredMessage)}, path: [], params: { missing } });
|
|
290
|
+
}
|
|
291
|
+
}`;
|
|
292
|
+
})
|
|
293
|
+
.filter(Boolean)
|
|
294
|
+
.join("\n ")}
|
|
271
295
|
})`;
|
|
272
296
|
}
|
|
273
297
|
}
|
|
@@ -16,17 +16,33 @@ import { parseOneOf } from "./parseOneOf.js";
|
|
|
16
16
|
import { parseSimpleDiscriminatedOneOf } from "./parseSimpleDiscriminatedOneOf.js";
|
|
17
17
|
import { parseNullable } from "./parseNullable.js";
|
|
18
18
|
import { anyOrUnknown } from "../utils/anyOrUnknown.js";
|
|
19
|
+
import { resolveUri } from "../utils/resolveUri.js";
|
|
20
|
+
import { buildRefRegistry } from "../utils/buildRefRegistry.js";
|
|
19
21
|
export const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) => {
|
|
20
22
|
// Ensure ref bookkeeping exists so $ref declarations and getter-based recursion work
|
|
21
23
|
refs.root = refs.root ?? schema;
|
|
24
|
+
refs.rootBaseUri = refs.rootBaseUri ?? "root:///";
|
|
22
25
|
refs.declarations = refs.declarations ?? new Map();
|
|
26
|
+
refs.dependencies = refs.dependencies ?? new Map();
|
|
23
27
|
refs.inProgress = refs.inProgress ?? new Set();
|
|
24
28
|
refs.refNameByPointer = refs.refNameByPointer ?? new Map();
|
|
25
29
|
refs.usedNames = refs.usedNames ?? new Set();
|
|
26
30
|
if (typeof schema !== "object")
|
|
27
31
|
return schema ? anyOrUnknown(refs) : "z.never()";
|
|
32
|
+
const parentBase = refs.currentBaseUri ?? refs.rootBaseUri ?? "root:///";
|
|
33
|
+
const baseUri = typeof schema.$id === "string"
|
|
34
|
+
? resolveUri(parentBase, schema.$id)
|
|
35
|
+
: parentBase;
|
|
36
|
+
const dynamicAnchors = Array.isArray(refs.dynamicAnchors) ? [...refs.dynamicAnchors] : [];
|
|
37
|
+
if (typeof schema.$dynamicAnchor === "string") {
|
|
38
|
+
dynamicAnchors.push({
|
|
39
|
+
name: schema.$dynamicAnchor,
|
|
40
|
+
uri: baseUri,
|
|
41
|
+
path: refs.path,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
28
44
|
if (refs.parserOverride) {
|
|
29
|
-
const custom = refs.parserOverride(schema, refs);
|
|
45
|
+
const custom = refs.parserOverride(schema, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
|
|
30
46
|
if (typeof custom === "string") {
|
|
31
47
|
return custom;
|
|
32
48
|
}
|
|
@@ -46,14 +62,14 @@ export const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockM
|
|
|
46
62
|
refs.seen.set(schema, seen);
|
|
47
63
|
}
|
|
48
64
|
if (its.a.ref(schema)) {
|
|
49
|
-
const parsedRef = parseRef(schema, refs);
|
|
65
|
+
const parsedRef = parseRef(schema, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
|
|
50
66
|
seen.r = parsedRef;
|
|
51
67
|
return parsedRef;
|
|
52
68
|
}
|
|
53
|
-
let parsed = selectParser(schema, refs);
|
|
69
|
+
let parsed = selectParser(schema, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
|
|
54
70
|
if (!blockMeta) {
|
|
55
71
|
if (!refs.withoutDescribes) {
|
|
56
|
-
parsed = addDescribes(schema, parsed, refs);
|
|
72
|
+
parsed = addDescribes(schema, parsed, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
|
|
57
73
|
}
|
|
58
74
|
if (!refs.withoutDefaults) {
|
|
59
75
|
parsed = addDefaults(schema, parsed);
|
|
@@ -64,23 +80,47 @@ export const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockM
|
|
|
64
80
|
return parsed;
|
|
65
81
|
};
|
|
66
82
|
const parseRef = (schema, refs) => {
|
|
67
|
-
const
|
|
83
|
+
const refValue = schema.$dynamicRef ?? schema.$ref;
|
|
84
|
+
const resolved = resolveRef(schema, refValue, refs);
|
|
68
85
|
if (!resolved) {
|
|
86
|
+
refs.onUnresolvedRef?.(refValue, refs.path);
|
|
69
87
|
return anyOrUnknown(refs);
|
|
70
88
|
}
|
|
71
|
-
const { schema: target, path } = resolved;
|
|
72
|
-
const refName = getOrCreateRefName(
|
|
89
|
+
const { schema: target, path, pointerKey } = resolved;
|
|
90
|
+
const refName = getOrCreateRefName(pointerKey, path, refs);
|
|
73
91
|
if (!refs.declarations.has(refName) && !refs.inProgress.has(refName)) {
|
|
74
92
|
refs.inProgress.add(refName);
|
|
75
93
|
const declaration = parseSchema(target, {
|
|
76
94
|
...refs,
|
|
77
95
|
path,
|
|
96
|
+
currentBaseUri: resolved.baseUri,
|
|
78
97
|
currentSchemaName: refName,
|
|
79
98
|
root: refs.root,
|
|
80
99
|
});
|
|
81
100
|
refs.inProgress.delete(refName);
|
|
82
101
|
refs.declarations.set(refName, declaration);
|
|
83
102
|
}
|
|
103
|
+
const current = refs.currentSchemaName;
|
|
104
|
+
if (current) {
|
|
105
|
+
const deps = refs.dependencies;
|
|
106
|
+
const set = deps.get(current) ?? new Set();
|
|
107
|
+
set.add(refName);
|
|
108
|
+
deps.set(current, set);
|
|
109
|
+
}
|
|
110
|
+
const currentComponent = refs.currentSchemaName
|
|
111
|
+
? refs.cycleComponentByName?.get(refs.currentSchemaName)
|
|
112
|
+
: undefined;
|
|
113
|
+
const targetComponent = refs.cycleComponentByName?.get(refName);
|
|
114
|
+
const isSameCycle = currentComponent !== undefined &&
|
|
115
|
+
targetComponent !== undefined &&
|
|
116
|
+
currentComponent === targetComponent &&
|
|
117
|
+
refs.cycleRefNames?.has(refName);
|
|
118
|
+
// Only lazy if the ref stays inside the current strongly-connected component
|
|
119
|
+
// (or is currently being resolved). This avoids TDZ on true cycles while
|
|
120
|
+
// letting ordered, acyclic refs stay direct.
|
|
121
|
+
if (isSameCycle || refs.inProgress.has(refName)) {
|
|
122
|
+
return `z.lazy(() => ${refName})`;
|
|
123
|
+
}
|
|
84
124
|
return refName;
|
|
85
125
|
};
|
|
86
126
|
const addDescribes = (schema, parsed, refs) => {
|
|
@@ -106,23 +146,93 @@ const addDescribes = (schema, parsed, refs) => {
|
|
|
106
146
|
}
|
|
107
147
|
return parsed;
|
|
108
148
|
};
|
|
109
|
-
const resolveRef = (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
149
|
+
const resolveRef = (schemaNode, ref, refs) => {
|
|
150
|
+
const base = refs.currentBaseUri ?? refs.rootBaseUri ?? "root:///";
|
|
151
|
+
// Handle dynamicRef lookup via dynamicAnchors stack
|
|
152
|
+
const isDynamic = typeof schemaNode.$dynamicRef === "string";
|
|
153
|
+
if (isDynamic && refs.dynamicAnchors && ref.startsWith("#")) {
|
|
154
|
+
const name = ref.slice(1);
|
|
155
|
+
for (let i = refs.dynamicAnchors.length - 1; i >= 0; i -= 1) {
|
|
156
|
+
const entry = refs.dynamicAnchors[i];
|
|
157
|
+
if (entry.name === name) {
|
|
158
|
+
const key = `${entry.uri}#${name}`;
|
|
159
|
+
const target = refs.refRegistry?.get(key);
|
|
160
|
+
if (target) {
|
|
161
|
+
return { schema: target.schema, path: target.path, baseUri: target.baseUri, pointerKey: key };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Resolve URI against base
|
|
167
|
+
const resolvedUri = resolveUri(base, ref);
|
|
168
|
+
const [uriBase, fragment] = resolvedUri.split("#");
|
|
169
|
+
const key = fragment ? `${uriBase}#${fragment}` : uriBase;
|
|
170
|
+
let regEntry = refs.refRegistry?.get(key);
|
|
171
|
+
if (regEntry) {
|
|
172
|
+
return { schema: regEntry.schema, path: regEntry.path, baseUri: regEntry.baseUri, pointerKey: key };
|
|
173
|
+
}
|
|
174
|
+
// Legacy recursive ref: treat as dynamic to __recursive__
|
|
175
|
+
if (schemaNode.$recursiveRef) {
|
|
176
|
+
const recursiveKey = `${base}#__recursive__`;
|
|
177
|
+
regEntry = refs.refRegistry?.get(recursiveKey);
|
|
178
|
+
if (regEntry) {
|
|
179
|
+
return {
|
|
180
|
+
schema: regEntry.schema,
|
|
181
|
+
path: regEntry.path,
|
|
182
|
+
baseUri: regEntry.baseUri,
|
|
183
|
+
pointerKey: recursiveKey,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// External resolver hook
|
|
188
|
+
const extBase = uriBaseFromRef(resolvedUri);
|
|
189
|
+
if (refs.resolveExternalRef && extBase && !isLocalBase(extBase, refs.rootBaseUri ?? "")) {
|
|
190
|
+
const loaded = refs.resolveExternalRef(extBase);
|
|
191
|
+
if (loaded) {
|
|
192
|
+
// If async resolver is used synchronously here, it will be ignored; keep simple sync for now
|
|
193
|
+
const schema = loaded.then ? undefined : loaded;
|
|
194
|
+
if (schema) {
|
|
195
|
+
const { registry } = buildRefRegistry(schema, extBase);
|
|
196
|
+
registry.forEach((entry, k) => refs.refRegistry?.set(k, entry));
|
|
197
|
+
regEntry = refs.refRegistry?.get(key);
|
|
198
|
+
if (regEntry) {
|
|
199
|
+
return {
|
|
200
|
+
schema: regEntry.schema,
|
|
201
|
+
path: regEntry.path,
|
|
202
|
+
baseUri: regEntry.baseUri,
|
|
203
|
+
pointerKey: key,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Backward compatibility: JSON Pointer into root
|
|
210
|
+
if (refs.root && ref.startsWith("#/")) {
|
|
211
|
+
const rawSegments = ref
|
|
212
|
+
.slice(2)
|
|
213
|
+
.split("/")
|
|
214
|
+
.filter((segment) => segment.length > 0)
|
|
215
|
+
.map(decodePointerSegment);
|
|
216
|
+
let current = refs.root;
|
|
217
|
+
for (const segment of rawSegments) {
|
|
218
|
+
if (typeof current !== "object" || current === null)
|
|
219
|
+
return undefined;
|
|
220
|
+
current = current[segment];
|
|
221
|
+
}
|
|
222
|
+
return { schema: current, path: rawSegments, baseUri: base, pointerKey: ref };
|
|
223
|
+
}
|
|
224
|
+
return undefined;
|
|
124
225
|
};
|
|
125
226
|
const decodePointerSegment = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
227
|
+
const uriBaseFromRef = (resolvedUri) => {
|
|
228
|
+
const hashIdx = resolvedUri.indexOf("#");
|
|
229
|
+
return hashIdx === -1 ? resolvedUri : resolvedUri.slice(0, hashIdx);
|
|
230
|
+
};
|
|
231
|
+
const isLocalBase = (base, rootBase) => {
|
|
232
|
+
if (!rootBase)
|
|
233
|
+
return false;
|
|
234
|
+
return base === rootBase;
|
|
235
|
+
};
|
|
126
236
|
const getOrCreateRefName = (pointer, path, refs) => {
|
|
127
237
|
if (refs.refNameByPointer?.has(pointer)) {
|
|
128
238
|
return refs.refNameByPointer.get(pointer);
|
|
@@ -133,12 +243,24 @@ const getOrCreateRefName = (pointer, path, refs) => {
|
|
|
133
243
|
return preferred;
|
|
134
244
|
};
|
|
135
245
|
const buildNameFromPath = (path, used) => {
|
|
136
|
-
const filtered = path
|
|
246
|
+
const filtered = path
|
|
247
|
+
.map((segment, idx) => {
|
|
248
|
+
if (idx === 0 && (segment === "$defs" || segment === "definitions")) {
|
|
249
|
+
return undefined; // root-level defs prefix is redundant for naming
|
|
250
|
+
}
|
|
251
|
+
if (segment === "properties")
|
|
252
|
+
return undefined; // skip noisy properties segment
|
|
253
|
+
if (segment === "$defs" || segment === "definitions")
|
|
254
|
+
return "Defs";
|
|
255
|
+
return segment;
|
|
256
|
+
})
|
|
257
|
+
.filter((segment) => segment !== undefined);
|
|
137
258
|
const base = filtered.length
|
|
138
259
|
? filtered
|
|
139
260
|
.map((segment) => typeof segment === "number"
|
|
140
261
|
? `Ref${segment}`
|
|
141
262
|
: segment
|
|
263
|
+
.toString()
|
|
142
264
|
.replace(/[^a-zA-Z0-9_$]/g, " ")
|
|
143
265
|
.split(" ")
|
|
144
266
|
.filter(Boolean)
|
|
@@ -209,7 +331,7 @@ const selectParser = (schema, refs) => {
|
|
|
209
331
|
return parseMultipleType(schema, refs);
|
|
210
332
|
}
|
|
211
333
|
else if (its.a.primitive(schema, "string")) {
|
|
212
|
-
return parseString(schema);
|
|
334
|
+
return parseString(schema, refs);
|
|
213
335
|
}
|
|
214
336
|
else if (its.a.primitive(schema, "number") ||
|
|
215
337
|
its.a.primitive(schema, "integer")) {
|
|
@@ -240,7 +362,7 @@ export const its = {
|
|
|
240
362
|
nullable: (x) => x.nullable === true,
|
|
241
363
|
multipleType: (x) => Array.isArray(x.type),
|
|
242
364
|
not: (x) => x.not !== undefined,
|
|
243
|
-
ref: (x) => typeof x.$ref === "string",
|
|
365
|
+
ref: (x) => typeof x.$ref === "string" || typeof x.$dynamicRef === "string",
|
|
244
366
|
const: (x) => x.const !== undefined,
|
|
245
367
|
primitive: (x, p) => x.type === p,
|
|
246
368
|
conditional: (x) => Boolean("if" in x && x.if && "then" in x && "else" in x && x.then && x.else),
|