schema-components 1.12.11 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dist/core/adapter.d.mts +8 -3
- package/dist/core/adapter.mjs +58 -14
- package/dist/core/constraints.d.mts +17 -0
- package/dist/core/constraints.mjs +150 -0
- package/dist/core/diagnostics.d.mts +2 -0
- package/dist/core/diagnostics.mjs +33 -0
- package/dist/core/errors.d.mts +1 -1
- package/dist/core/formats.d.mts +33 -0
- package/dist/core/formats.mjs +67 -0
- package/dist/core/merge.d.mts +48 -0
- package/dist/core/merge.mjs +125 -0
- package/dist/core/normalise.d.mts +41 -0
- package/dist/core/normalise.mjs +2 -0
- package/dist/core/openapi30.d.mts +41 -0
- package/dist/core/openapi30.mjs +2 -0
- package/dist/core/ref.d.mts +2 -0
- package/dist/core/ref.mjs +165 -0
- package/dist/core/renderer.d.mts +2 -2
- package/dist/core/renderer.mjs +8 -0
- package/dist/core/swagger2.d.mts +11 -0
- package/dist/core/swagger2.mjs +2 -0
- package/dist/core/typeInference.d.mts +2 -0
- package/dist/core/typeInference.mjs +1 -0
- package/dist/core/types.d.mts +2 -3
- package/dist/core/types.mjs +58 -2
- package/dist/core/version.d.mts +2 -0
- package/dist/core/version.mjs +151 -0
- package/dist/core/walkBuilders.d.mts +66 -0
- package/dist/core/walkBuilders.mjs +152 -0
- package/dist/core/walker.d.mts +3 -10
- package/dist/core/walker.mjs +245 -233
- package/dist/diagnostics-DzbZmcLI.d.mts +64 -0
- package/dist/html/a11y.d.mts +5 -4
- package/dist/html/renderToHtml.d.mts +3 -3
- package/dist/html/renderToHtml.mjs +23 -379
- package/dist/html/renderToHtmlStream.d.mts +29 -47
- package/dist/html/renderToHtmlStream.mjs +33 -305
- package/dist/html/renderers.d.mts +14 -0
- package/dist/html/renderers.mjs +406 -0
- package/dist/html/streamRenderers.d.mts +13 -0
- package/dist/html/streamRenderers.mjs +243 -0
- package/dist/normalise-tL9FckAk.mjs +748 -0
- package/dist/openapi/ApiCallbacks.d.mts +16 -0
- package/dist/openapi/ApiCallbacks.mjs +34 -0
- package/dist/openapi/ApiLinks.d.mts +16 -0
- package/dist/openapi/ApiLinks.mjs +42 -0
- package/dist/openapi/ApiResponseHeaders.d.mts +16 -0
- package/dist/openapi/ApiResponseHeaders.mjs +35 -0
- package/dist/openapi/ApiSecurity.d.mts +19 -0
- package/dist/openapi/ApiSecurity.mjs +33 -0
- package/dist/openapi/bundle.d.mts +47 -0
- package/dist/openapi/bundle.mjs +95 -0
- package/dist/openapi/components.d.mts +7 -1
- package/dist/openapi/components.mjs +30 -6
- package/dist/openapi/parser.d.mts +59 -2
- package/dist/openapi/parser.mjs +189 -8
- package/dist/react/SchemaComponent.d.mts +13 -4
- package/dist/react/SchemaComponent.mjs +51 -91
- package/dist/react/SchemaView.d.mts +10 -2
- package/dist/react/SchemaView.mjs +33 -15
- package/dist/react/fieldPath.d.mts +20 -0
- package/dist/react/fieldPath.mjs +81 -0
- package/dist/react/headless.d.mts +2 -4
- package/dist/react/headless.mjs +3 -492
- package/dist/react/headlessRenderers.d.mts +23 -0
- package/dist/react/headlessRenderers.mjs +507 -0
- package/dist/ref-DvWoULcy.d.mts +44 -0
- package/dist/renderer-BdSqllx5.d.mts +160 -0
- package/dist/themes/mantine.d.mts +1 -1
- package/dist/themes/mantine.mjs +2 -1
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/mui.mjs +3 -2
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/radix.mjs +2 -1
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/themes/shadcn.mjs +10 -6
- package/dist/typeInference-k7FXfTVO.d.mts +335 -0
- package/dist/types-D_5ST7SS.d.mts +269 -0
- package/dist/version-B5NV-35j.d.mts +69 -0
- package/package.json +1 -1
- package/dist/types-BJzEgJdX.d.mts +0 -335
- /package/dist/{errors-DIKI2C78.d.mts → errors-C5zRC2PU.d.mts} +0 -0
package/dist/core/walker.mjs
CHANGED
|
@@ -1,196 +1,63 @@
|
|
|
1
1
|
import { isObject } from "./guards.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { appendPointer, emitDiagnostic } from "./diagnostics.mjs";
|
|
3
|
+
import { countDistinctRefs, resolveRef } from "./ref.mjs";
|
|
4
|
+
import { extractArrayConstraints, extractObjectConstraints, stripInapplicableConstraints } from "./constraints.mjs";
|
|
5
|
+
import { ANNOTATION_SIBLINGS, detectDiscriminated, mergeAllOf, mergeRefSiblings, normaliseAnyOf } from "./merge.mjs";
|
|
6
|
+
import { buildBase, buildBooleanField, buildFileField, buildNullField, buildNumberField, buildStringField, buildUnknownField, extractChildOverride, extractSchemaMetaFields, getArray, getObject, getString, isPrimitive, walkDependentRequiredMap, walkSubSchemaMap, withoutKeys } from "./walkBuilders.mjs";
|
|
3
7
|
//#region src/core/walker.ts
|
|
4
|
-
function getString(obj, key) {
|
|
5
|
-
const value = obj[key];
|
|
6
|
-
return typeof value === "string" ? value : void 0;
|
|
7
|
-
}
|
|
8
|
-
function getNumber(obj, key) {
|
|
9
|
-
const value = obj[key];
|
|
10
|
-
return typeof value === "number" ? value : void 0;
|
|
11
|
-
}
|
|
12
|
-
function getArray(obj, key) {
|
|
13
|
-
const value = obj[key];
|
|
14
|
-
return Array.isArray(value) ? value : void 0;
|
|
15
|
-
}
|
|
16
|
-
function getObject(obj, key) {
|
|
17
|
-
const value = obj[key];
|
|
18
|
-
return isObject(value) ? value : void 0;
|
|
19
|
-
}
|
|
20
|
-
const MAX_REF_DEPTH = 10;
|
|
21
|
-
function resolveRef(schema, rootDocument, visited) {
|
|
22
|
-
const ref = getString(schema, "$ref");
|
|
23
|
-
if (ref === void 0) return schema;
|
|
24
|
-
if (visited.has(ref)) return { type: "unknown" };
|
|
25
|
-
if (visited.size >= MAX_REF_DEPTH) return { type: "unknown" };
|
|
26
|
-
const resolved = dereference(ref, rootDocument);
|
|
27
|
-
if (resolved === void 0) return { type: "unknown" };
|
|
28
|
-
const nextVisited = new Set(visited);
|
|
29
|
-
nextVisited.add(ref);
|
|
30
|
-
return resolveRef(resolved, rootDocument, nextVisited);
|
|
31
|
-
}
|
|
32
|
-
function dereference(ref, root) {
|
|
33
|
-
if (ref === "#") return root;
|
|
34
|
-
if (!ref.startsWith("#/")) return void 0;
|
|
35
|
-
const parts = ref.slice(2).split("/");
|
|
36
|
-
if (parts.length === 1 && parts[0] === "") return root;
|
|
37
|
-
let current = root;
|
|
38
|
-
for (const part of parts) {
|
|
39
|
-
if (!isObject(current)) return void 0;
|
|
40
|
-
const decoded = part.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
41
|
-
current = current[decoded];
|
|
42
|
-
}
|
|
43
|
-
return isObject(current) ? current : void 0;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Merge multiple JSON Schema objects from allOf into one.
|
|
47
|
-
* Merges: properties, required, meta fields, and constraints.
|
|
48
|
-
*/
|
|
49
|
-
function mergeAllOf(schemas) {
|
|
50
|
-
const merged = {};
|
|
51
|
-
const properties = {};
|
|
52
|
-
const required = [];
|
|
53
|
-
for (const entry of schemas) {
|
|
54
|
-
if (!isObject(entry)) continue;
|
|
55
|
-
const props = getObject(entry, "properties");
|
|
56
|
-
if (props !== void 0) for (const [key, value] of Object.entries(props)) properties[key] = value;
|
|
57
|
-
const req = getArray(entry, "required");
|
|
58
|
-
if (req !== void 0) {
|
|
59
|
-
for (const r of req) if (typeof r === "string" && !required.includes(r)) required.push(r);
|
|
60
|
-
}
|
|
61
|
-
for (const [key, value] of Object.entries(entry)) {
|
|
62
|
-
if (key === "properties" || key === "required" || key === "allOf" || key === "type") continue;
|
|
63
|
-
if (!(key in merged)) merged[key] = value;
|
|
64
|
-
}
|
|
65
|
-
if (!("type" in merged)) {
|
|
66
|
-
const type = getString(entry, "type");
|
|
67
|
-
if (type !== void 0) merged.type = type;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (Object.keys(properties).length > 0) merged.properties = properties;
|
|
71
|
-
if (required.length > 0) merged.required = required;
|
|
72
|
-
return merged;
|
|
73
|
-
}
|
|
74
8
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
9
|
+
* Handle JSON Schema boolean values (Draft 06+).
|
|
10
|
+
* - `true` → permissive (unknown, editable)
|
|
11
|
+
* - `false` → never (cannot hold any value)
|
|
77
12
|
*/
|
|
78
|
-
function
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
else inner = opt;
|
|
86
|
-
}
|
|
87
|
-
if (!hasNull || inner === void 0) return void 0;
|
|
13
|
+
function walkBooleanSchema(value) {
|
|
14
|
+
if (value) return {
|
|
15
|
+
type: "unknown",
|
|
16
|
+
editability: "editable",
|
|
17
|
+
meta: {},
|
|
18
|
+
constraints: {}
|
|
19
|
+
};
|
|
88
20
|
return {
|
|
89
|
-
|
|
90
|
-
|
|
21
|
+
type: "never",
|
|
22
|
+
editability: "presentation",
|
|
23
|
+
meta: { rejected: true },
|
|
24
|
+
constraints: {}
|
|
91
25
|
};
|
|
92
26
|
}
|
|
93
27
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
28
|
+
* Walk a sub-schema that may be an object, a boolean, or neither.
|
|
29
|
+
* Dispatches to walkNode (object), walkBooleanSchema (boolean),
|
|
30
|
+
* or returns unknown with a diagnostic.
|
|
96
31
|
*/
|
|
97
|
-
function
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
for (const opt of options) {
|
|
101
|
-
if (!isObject(opt)) return void 0;
|
|
102
|
-
const props = getObject(opt, "properties");
|
|
103
|
-
if (props === void 0) return void 0;
|
|
104
|
-
let foundKey;
|
|
105
|
-
for (const [key, value] of Object.entries(props)) if (isObject(value) && "const" in value) {
|
|
106
|
-
foundKey = key;
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
if (foundKey === void 0) return void 0;
|
|
110
|
-
if (discriminator === void 0) discriminator = foundKey;
|
|
111
|
-
else if (discriminator !== foundKey) return;
|
|
112
|
-
}
|
|
113
|
-
if (discriminator === void 0) return void 0;
|
|
32
|
+
function walkSubSchema(value, ctx) {
|
|
33
|
+
if (isObject(value)) return walkNode(value, ctx);
|
|
34
|
+
if (typeof value === "boolean") return walkBooleanSchema(value);
|
|
114
35
|
return {
|
|
115
|
-
|
|
116
|
-
|
|
36
|
+
type: "unknown",
|
|
37
|
+
editability: "editable",
|
|
38
|
+
meta: {},
|
|
39
|
+
constraints: {}
|
|
117
40
|
};
|
|
118
41
|
}
|
|
119
|
-
const META_KEYWORDS = new Set([
|
|
120
|
-
"readOnly",
|
|
121
|
-
"writeOnly",
|
|
122
|
-
"description",
|
|
123
|
-
"title",
|
|
124
|
-
"deprecated",
|
|
125
|
-
"default",
|
|
126
|
-
"component",
|
|
127
|
-
"example",
|
|
128
|
-
"examples"
|
|
129
|
-
]);
|
|
130
|
-
function extractMetaFromJson(schema) {
|
|
131
|
-
const meta = {};
|
|
132
|
-
for (const [key, value] of Object.entries(schema)) if (META_KEYWORDS.has(key)) meta[key] = value;
|
|
133
|
-
return meta;
|
|
134
|
-
}
|
|
135
|
-
function extractConstraintsFromJson(schema) {
|
|
136
|
-
const constraints = {};
|
|
137
|
-
const minLength = getNumber(schema, "minLength");
|
|
138
|
-
if (minLength !== void 0) constraints.minLength = minLength;
|
|
139
|
-
const maxLength = getNumber(schema, "maxLength");
|
|
140
|
-
if (maxLength !== void 0) constraints.maxLength = maxLength;
|
|
141
|
-
const minimum = getNumber(schema, "minimum");
|
|
142
|
-
if (minimum !== void 0) constraints.minimum = minimum;
|
|
143
|
-
const maximum = getNumber(schema, "maximum");
|
|
144
|
-
if (maximum !== void 0) constraints.maximum = maximum;
|
|
145
|
-
const pattern = getString(schema, "pattern");
|
|
146
|
-
if (pattern !== void 0) constraints.pattern = pattern;
|
|
147
|
-
const format = getString(schema, "format");
|
|
148
|
-
if (format !== void 0) constraints.format = format;
|
|
149
|
-
const minItems = getNumber(schema, "minItems");
|
|
150
|
-
if (minItems !== void 0) constraints.minItems = minItems;
|
|
151
|
-
const maxItems = getNumber(schema, "maxItems");
|
|
152
|
-
if (maxItems !== void 0) constraints.maxItems = maxItems;
|
|
153
|
-
if (format === "binary") {
|
|
154
|
-
const contentMediaType = getString(schema, "contentMediaType");
|
|
155
|
-
if (contentMediaType !== void 0) constraints.mimeTypes = [contentMediaType];
|
|
156
|
-
}
|
|
157
|
-
return constraints;
|
|
158
|
-
}
|
|
159
|
-
const OVERRIDE_META_KEYS = new Set([
|
|
160
|
-
"readOnly",
|
|
161
|
-
"writeOnly",
|
|
162
|
-
"description",
|
|
163
|
-
"title",
|
|
164
|
-
"deprecated",
|
|
165
|
-
"component",
|
|
166
|
-
"visible",
|
|
167
|
-
"order"
|
|
168
|
-
]);
|
|
169
|
-
function extractSchemaMetaFields(overrides) {
|
|
170
|
-
if (overrides === void 0) return void 0;
|
|
171
|
-
const meta = {};
|
|
172
|
-
for (const key of Object.keys(overrides)) if (OVERRIDE_META_KEYS.has(key)) meta[key] = overrides[key];
|
|
173
|
-
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
174
|
-
}
|
|
175
|
-
function extractChildOverride(overrides, key) {
|
|
176
|
-
if (overrides === void 0) return void 0;
|
|
177
|
-
const child = overrides[key];
|
|
178
|
-
if (child === void 0 || child === null) return void 0;
|
|
179
|
-
if (typeof child !== "object" || Array.isArray(child)) return void 0;
|
|
180
|
-
const result = {};
|
|
181
|
-
for (const [k, v] of Object.entries(child)) result[k] = v;
|
|
182
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
183
|
-
}
|
|
184
42
|
function walk(schema, options = {}) {
|
|
185
|
-
const { componentMeta, rootMeta, fieldOverrides, rootDocument } = options;
|
|
43
|
+
const { componentMeta, rootMeta, fieldOverrides, rootDocument, diagnostics, externalResolver } = options;
|
|
44
|
+
if (typeof schema === "boolean") return walkBooleanSchema(schema);
|
|
186
45
|
if (!isObject(schema)) return {
|
|
187
46
|
type: "unknown",
|
|
188
47
|
editability: "editable",
|
|
189
48
|
meta: {},
|
|
190
49
|
constraints: {}
|
|
191
50
|
};
|
|
51
|
+
const topRef = typeof schema.$ref === "string" ? schema.$ref : void 0;
|
|
52
|
+
if (topRef !== void 0 && !topRef.startsWith("#")) emitDiagnostic(diagnostics, {
|
|
53
|
+
code: "external-ref",
|
|
54
|
+
message: `External $ref not supported: ${topRef}`,
|
|
55
|
+
pointer: "",
|
|
56
|
+
detail: { ref: topRef }
|
|
57
|
+
});
|
|
192
58
|
const doc = rootDocument ?? schema;
|
|
193
|
-
|
|
59
|
+
const maxRefDepth = countDistinctRefs(doc);
|
|
60
|
+
return walkNode(resolveRef(schema, doc, /* @__PURE__ */ new Set(), diagnostics, maxRefDepth, externalResolver), {
|
|
194
61
|
componentMeta,
|
|
195
62
|
rootMeta,
|
|
196
63
|
fieldOverrides,
|
|
@@ -198,7 +65,11 @@ function walk(schema, options = {}) {
|
|
|
198
65
|
isNullable: false,
|
|
199
66
|
isOptional: false,
|
|
200
67
|
defaultValue: void 0,
|
|
201
|
-
refResults: /* @__PURE__ */ new Map()
|
|
68
|
+
refResults: /* @__PURE__ */ new Map(),
|
|
69
|
+
pointer: "",
|
|
70
|
+
diagnostics,
|
|
71
|
+
maxRefDepth,
|
|
72
|
+
externalResolver
|
|
202
73
|
});
|
|
203
74
|
}
|
|
204
75
|
function walkNode(schema, ctx) {
|
|
@@ -223,7 +94,8 @@ function walkNode(schema, ctx) {
|
|
|
223
94
|
if (ref !== void 0) {
|
|
224
95
|
const cached = ctx.refResults.get(ref);
|
|
225
96
|
if (cached !== void 0) return cached;
|
|
226
|
-
const
|
|
97
|
+
const hasSiblings = [...ANNOTATION_SIBLINGS].some((k) => k in schema);
|
|
98
|
+
const resolved = resolveRef(schema, ctx.rootDocument, /* @__PURE__ */ new Set(), ctx.diagnostics, ctx.maxRefDepth, ctx.externalResolver);
|
|
227
99
|
const placeholder = {
|
|
228
100
|
type: "unknown",
|
|
229
101
|
editability: "editable",
|
|
@@ -231,68 +103,172 @@ function walkNode(schema, ctx) {
|
|
|
231
103
|
constraints: {}
|
|
232
104
|
};
|
|
233
105
|
ctx.refResults.set(ref, placeholder);
|
|
234
|
-
|
|
106
|
+
let result = walkNode(resolved, ctx);
|
|
107
|
+
if (hasSiblings) result = {
|
|
108
|
+
...result,
|
|
109
|
+
meta: mergeRefSiblings(schema, result.meta)
|
|
110
|
+
};
|
|
235
111
|
Object.assign(placeholder, result);
|
|
236
112
|
return placeholder;
|
|
237
113
|
}
|
|
114
|
+
const ifSchema = getObject(schema, "if");
|
|
115
|
+
if (ifSchema !== void 0) {
|
|
116
|
+
emitDiagnostic(ctx.diagnostics, {
|
|
117
|
+
code: "conditional-fallback",
|
|
118
|
+
message: "if/then/else rendered as base schema; conditionals require runtime evaluation",
|
|
119
|
+
pointer: ctx.pointer
|
|
120
|
+
});
|
|
121
|
+
const base = buildBase(withoutKeys(schema, [
|
|
122
|
+
"if",
|
|
123
|
+
"then",
|
|
124
|
+
"else"
|
|
125
|
+
]), ctx);
|
|
126
|
+
const thenSchema = getObject(schema, "then");
|
|
127
|
+
const elseSchema = getObject(schema, "else");
|
|
128
|
+
const conditional = {
|
|
129
|
+
...base,
|
|
130
|
+
type: "conditional",
|
|
131
|
+
constraints: {},
|
|
132
|
+
ifClause: walkNode(ifSchema, ctx)
|
|
133
|
+
};
|
|
134
|
+
if (thenSchema !== void 0) conditional.thenClause = walkNode(thenSchema, ctx);
|
|
135
|
+
if (elseSchema !== void 0) conditional.elseClause = walkNode(elseSchema, ctx);
|
|
136
|
+
return conditional;
|
|
137
|
+
}
|
|
138
|
+
const notSchema = getObject(schema, "not");
|
|
139
|
+
if (notSchema !== void 0) {
|
|
140
|
+
emitDiagnostic(ctx.diagnostics, {
|
|
141
|
+
code: "type-negation-fallback",
|
|
142
|
+
message: "not schema rendered as negation; TypeScript cannot negate types",
|
|
143
|
+
pointer: ctx.pointer
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
...buildBase(withoutKeys(schema, ["not"]), ctx),
|
|
147
|
+
type: "negation",
|
|
148
|
+
constraints: {},
|
|
149
|
+
negated: walkNode(notSchema, ctx)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
238
152
|
const enumValues = getArray(schema, "enum");
|
|
239
153
|
if (enumValues !== void 0) return walkEnum(schema, enumValues, ctx);
|
|
240
|
-
if ("const" in schema)
|
|
154
|
+
if ("const" in schema) {
|
|
155
|
+
if (!isPrimitive(schema.const)) emitDiagnostic(ctx.diagnostics, {
|
|
156
|
+
code: "invalid-const",
|
|
157
|
+
message: `const value is not a primitive: ${typeof schema.const}`,
|
|
158
|
+
pointer: ctx.pointer,
|
|
159
|
+
detail: { constValue: schema.const }
|
|
160
|
+
});
|
|
161
|
+
return walkLiteral(schema, ctx);
|
|
162
|
+
}
|
|
241
163
|
const type = getString(schema, "type");
|
|
242
|
-
|
|
164
|
+
const typeArray = getArray(schema, "type");
|
|
165
|
+
if (type === void 0 && typeArray !== void 0) {
|
|
166
|
+
const nonNullTypes = typeArray.filter((t) => typeof t === "string" && t !== "null");
|
|
167
|
+
const hasNull = typeArray.includes("null");
|
|
168
|
+
if (nonNullTypes.length === 0) return buildNullField(schema, ctx);
|
|
169
|
+
if (nonNullTypes.length === 1) {
|
|
170
|
+
const walkCtx = hasNull ? {
|
|
171
|
+
...ctx,
|
|
172
|
+
isNullable: true
|
|
173
|
+
} : ctx;
|
|
174
|
+
const singleType = nonNullTypes[0];
|
|
175
|
+
if (singleType === void 0) return buildUnknownField(schema, ctx);
|
|
176
|
+
return walkNode({
|
|
177
|
+
...stripInapplicableConstraints(schema, singleType),
|
|
178
|
+
type: singleType
|
|
179
|
+
}, walkCtx);
|
|
180
|
+
}
|
|
181
|
+
const options = nonNullTypes.map((t) => ({
|
|
182
|
+
...stripInapplicableConstraints(schema, t),
|
|
183
|
+
type: t
|
|
184
|
+
}));
|
|
185
|
+
if (hasNull) return walkUnion([...options, { type: "null" }], {
|
|
186
|
+
...ctx,
|
|
187
|
+
isNullable: true
|
|
188
|
+
});
|
|
189
|
+
return walkUnion(options, ctx);
|
|
190
|
+
}
|
|
191
|
+
if (type === void 0) {
|
|
192
|
+
emitDiagnostic(ctx.diagnostics, {
|
|
193
|
+
code: "unsupported-type",
|
|
194
|
+
message: "Schema has no type, composition, enum, or const; rendering as unknown",
|
|
195
|
+
pointer: ctx.pointer
|
|
196
|
+
});
|
|
197
|
+
return buildUnknownField(schema, ctx);
|
|
198
|
+
}
|
|
243
199
|
if (type === "string") return walkString(schema, ctx);
|
|
244
200
|
if (type === "number" || type === "integer") return walkNumber(schema, ctx);
|
|
245
201
|
if (type === "boolean") return walkBoolean(schema, ctx);
|
|
246
|
-
if (type === "null") return
|
|
202
|
+
if (type === "null") return buildNullField(schema, ctx);
|
|
247
203
|
if (type === "object") {
|
|
248
204
|
const properties = getObject(schema, "properties");
|
|
249
205
|
if (properties !== void 0) return walkObject(schema, properties, ctx);
|
|
250
206
|
const additionalProps = getObject(schema, "additionalProperties");
|
|
251
207
|
if (additionalProps !== void 0) return walkRecord(schema, additionalProps, ctx);
|
|
252
|
-
return
|
|
208
|
+
return {
|
|
209
|
+
...buildBase(schema, ctx),
|
|
210
|
+
type: "object",
|
|
211
|
+
constraints: extractObjectConstraints(schema),
|
|
212
|
+
fields: {},
|
|
213
|
+
requiredFields: []
|
|
214
|
+
};
|
|
253
215
|
}
|
|
254
216
|
if (type === "array") return walkArray(schema, ctx);
|
|
255
|
-
|
|
217
|
+
emitDiagnostic(ctx.diagnostics, {
|
|
218
|
+
code: "unsupported-type",
|
|
219
|
+
message: `Unknown schema type: ${type}`,
|
|
220
|
+
pointer: ctx.pointer,
|
|
221
|
+
detail: { type }
|
|
222
|
+
});
|
|
223
|
+
return buildUnknownField(schema, ctx);
|
|
256
224
|
}
|
|
257
225
|
function walkString(schema, ctx) {
|
|
258
|
-
if (getString(schema, "format") === "binary") return
|
|
259
|
-
|
|
226
|
+
if (getString(schema, "format") === "binary") return buildFileField(schema, ctx);
|
|
227
|
+
const field = buildStringField(schema, ctx);
|
|
228
|
+
const contentSchema = getObject(schema, "contentSchema");
|
|
229
|
+
if (contentSchema !== void 0) field.meta.decodedSchema = walkNode(contentSchema, ctx);
|
|
230
|
+
return field;
|
|
260
231
|
}
|
|
261
232
|
function walkNumber(schema, ctx) {
|
|
262
|
-
return
|
|
233
|
+
return buildNumberField(schema, ctx);
|
|
263
234
|
}
|
|
264
235
|
function walkBoolean(schema, ctx) {
|
|
265
|
-
return
|
|
236
|
+
return buildBooleanField(schema, ctx);
|
|
266
237
|
}
|
|
267
238
|
function walkEnum(schema, enumValues, ctx) {
|
|
268
239
|
return {
|
|
269
|
-
...
|
|
270
|
-
|
|
240
|
+
...buildBase(schema, ctx),
|
|
241
|
+
type: "enum",
|
|
242
|
+
constraints: {},
|
|
243
|
+
enumValues: enumValues.filter((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v === null)
|
|
271
244
|
};
|
|
272
245
|
}
|
|
273
246
|
function walkLiteral(schema, ctx) {
|
|
274
247
|
const constValue = schema.const;
|
|
275
248
|
const values = isPrimitive(constValue) ? [constValue] : [];
|
|
276
249
|
return {
|
|
277
|
-
...
|
|
250
|
+
...buildBase(schema, ctx),
|
|
251
|
+
type: "literal",
|
|
252
|
+
constraints: {},
|
|
278
253
|
literalValues: values
|
|
279
254
|
};
|
|
280
255
|
}
|
|
281
256
|
function walkObject(schema, properties, ctx) {
|
|
282
|
-
const
|
|
283
|
-
const required = getArray(schema, "required");
|
|
257
|
+
const requiredFields = getArray(schema, "required")?.filter((r) => typeof r === "string") ?? [];
|
|
284
258
|
const fields = {};
|
|
285
259
|
for (const [key, propSchema] of Object.entries(properties)) {
|
|
286
260
|
const childOverride = extractChildOverride(ctx.fieldOverrides, key);
|
|
287
|
-
const isRequired =
|
|
261
|
+
const isRequired = requiredFields.includes(key);
|
|
288
262
|
const childCtx = {
|
|
289
263
|
...ctx,
|
|
290
264
|
fieldOverrides: childOverride,
|
|
291
|
-
isOptional: !isRequired
|
|
265
|
+
isOptional: !isRequired,
|
|
266
|
+
pointer: appendPointer(ctx.pointer, key)
|
|
292
267
|
};
|
|
293
268
|
const overrideMeta = extractSchemaMetaFields(childOverride);
|
|
294
269
|
if (overrideMeta !== void 0 && ("readOnly" in overrideMeta || "writeOnly" in overrideMeta)) childCtx.componentMeta = void 0;
|
|
295
270
|
if (isObject(propSchema)) fields[key] = walkNode(propSchema, childCtx);
|
|
271
|
+
else if (typeof propSchema === "boolean") fields[key] = walkBooleanSchema(propSchema);
|
|
296
272
|
else fields[key] = {
|
|
297
273
|
type: "unknown",
|
|
298
274
|
editability: "editable",
|
|
@@ -300,13 +276,53 @@ function walkObject(schema, properties, ctx) {
|
|
|
300
276
|
constraints: {}
|
|
301
277
|
};
|
|
302
278
|
}
|
|
279
|
+
const patternProps = getObject(schema, "patternProperties");
|
|
280
|
+
const walkedPatternProps = patternProps !== void 0 ? walkSubSchemaMap(patternProps, walkNode, ctx) : void 0;
|
|
281
|
+
let additionalPropertiesClosed;
|
|
282
|
+
let additionalPropertiesSchema;
|
|
283
|
+
const additionalProps = schema.additionalProperties;
|
|
284
|
+
if (additionalProps === false) additionalPropertiesClosed = true;
|
|
285
|
+
else if (additionalProps === true) additionalPropertiesSchema = {
|
|
286
|
+
type: "unknown",
|
|
287
|
+
editability: "editable",
|
|
288
|
+
meta: {},
|
|
289
|
+
constraints: {}
|
|
290
|
+
};
|
|
291
|
+
else if (isObject(additionalProps)) additionalPropertiesSchema = walkNode(additionalProps, ctx);
|
|
292
|
+
const depSchemas = getObject(schema, "dependentSchemas");
|
|
293
|
+
const walkedDepSchemas = depSchemas !== void 0 ? walkSubSchemaMap(depSchemas, walkNode, ctx) : void 0;
|
|
294
|
+
const depReq = getObject(schema, "dependentRequired");
|
|
295
|
+
const walkedDepReq = depReq !== void 0 ? walkDependentRequiredMap(depReq) : void 0;
|
|
296
|
+
let unevaluatedProperties;
|
|
297
|
+
let unevaluatedPropertiesClosed;
|
|
298
|
+
const unevalProps = schema.unevaluatedProperties;
|
|
299
|
+
if (unevalProps === false) unevaluatedPropertiesClosed = true;
|
|
300
|
+
else if (unevalProps === true) unevaluatedProperties = {
|
|
301
|
+
type: "unknown",
|
|
302
|
+
editability: "editable",
|
|
303
|
+
meta: {},
|
|
304
|
+
constraints: {}
|
|
305
|
+
};
|
|
306
|
+
else if (isObject(unevalProps)) unevaluatedProperties = walkNode(unevalProps, ctx);
|
|
307
|
+
const propertyNamesSchema = getObject(schema, "propertyNames");
|
|
308
|
+
const walkedPropertyNames = propertyNamesSchema !== void 0 ? walkNode(propertyNamesSchema, ctx) : void 0;
|
|
303
309
|
return {
|
|
304
|
-
...
|
|
305
|
-
|
|
310
|
+
...buildBase(schema, ctx),
|
|
311
|
+
type: "object",
|
|
312
|
+
constraints: extractObjectConstraints(schema),
|
|
313
|
+
fields,
|
|
314
|
+
requiredFields,
|
|
315
|
+
...walkedPatternProps !== void 0 && Object.keys(walkedPatternProps).length > 0 ? { patternProperties: walkedPatternProps } : {},
|
|
316
|
+
...additionalPropertiesClosed ? { additionalPropertiesClosed } : {},
|
|
317
|
+
...additionalPropertiesSchema !== void 0 ? { additionalPropertiesSchema } : {},
|
|
318
|
+
...walkedDepSchemas !== void 0 && Object.keys(walkedDepSchemas).length > 0 ? { dependentSchemas: walkedDepSchemas } : {},
|
|
319
|
+
...walkedDepReq !== void 0 && Object.keys(walkedDepReq).length > 0 ? { dependentRequired: walkedDepReq } : {},
|
|
320
|
+
...unevaluatedProperties !== void 0 ? { unevaluatedProperties } : {},
|
|
321
|
+
...unevaluatedPropertiesClosed ? { unevaluatedPropertiesClosed } : {},
|
|
322
|
+
...walkedPropertyNames !== void 0 ? { propertyNames: walkedPropertyNames } : {}
|
|
306
323
|
};
|
|
307
324
|
}
|
|
308
325
|
function walkRecord(schema, valueSchema, ctx) {
|
|
309
|
-
const base = buildField(schema, "record", ctx);
|
|
310
326
|
const propertyNames = getObject(schema, "propertyNames");
|
|
311
327
|
const keyType = propertyNames !== void 0 ? walkNode(propertyNames, ctx) : {
|
|
312
328
|
type: "string",
|
|
@@ -316,39 +332,61 @@ function walkRecord(schema, valueSchema, ctx) {
|
|
|
316
332
|
};
|
|
317
333
|
const valueType = walkNode(valueSchema, ctx);
|
|
318
334
|
return {
|
|
319
|
-
...
|
|
335
|
+
...buildBase(schema, ctx),
|
|
336
|
+
type: "record",
|
|
337
|
+
constraints: extractObjectConstraints(schema),
|
|
320
338
|
keyType,
|
|
321
339
|
valueType
|
|
322
340
|
};
|
|
323
341
|
}
|
|
324
342
|
function walkArray(schema, ctx) {
|
|
325
|
-
const
|
|
343
|
+
const prefixItems = getArray(schema, "prefixItems");
|
|
344
|
+
if (prefixItems !== void 0) {
|
|
345
|
+
const walkedItems = prefixItems.filter(isObject).map((item) => walkNode(item, ctx));
|
|
346
|
+
return {
|
|
347
|
+
...buildBase(schema, ctx),
|
|
348
|
+
type: "tuple",
|
|
349
|
+
constraints: extractArrayConstraints(schema),
|
|
350
|
+
prefixItems: walkedItems
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const unevaluatedItemsSchema = getObject(schema, "unevaluatedItems");
|
|
354
|
+
const walkedUnevaluatedItems = unevaluatedItemsSchema !== void 0 ? walkNode(unevaluatedItemsSchema, ctx) : void 0;
|
|
326
355
|
const items = getObject(schema, "items");
|
|
327
356
|
if (items !== void 0) {
|
|
328
357
|
const elementOverride = extractChildOverride(ctx.fieldOverrides, "[]");
|
|
329
358
|
return {
|
|
330
|
-
...
|
|
359
|
+
...buildBase(schema, ctx),
|
|
360
|
+
type: "array",
|
|
361
|
+
constraints: extractArrayConstraints(schema),
|
|
331
362
|
element: walkNode(items, {
|
|
332
363
|
...ctx,
|
|
333
364
|
fieldOverrides: elementOverride
|
|
334
|
-
})
|
|
365
|
+
}),
|
|
366
|
+
...walkedUnevaluatedItems !== void 0 ? { unevaluatedItems: walkedUnevaluatedItems } : {}
|
|
335
367
|
};
|
|
336
368
|
}
|
|
337
|
-
return
|
|
369
|
+
return {
|
|
370
|
+
...buildBase(schema, ctx),
|
|
371
|
+
type: "array",
|
|
372
|
+
constraints: extractArrayConstraints(schema),
|
|
373
|
+
...walkedUnevaluatedItems !== void 0 ? { unevaluatedItems: walkedUnevaluatedItems } : {}
|
|
374
|
+
};
|
|
338
375
|
}
|
|
339
376
|
function walkUnion(options, ctx) {
|
|
340
|
-
const
|
|
377
|
+
const walkedOptions = options.map((opt) => walkSubSchema(opt, ctx));
|
|
341
378
|
return {
|
|
342
|
-
...
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}))
|
|
379
|
+
...buildBase({}, ctx),
|
|
380
|
+
type: "union",
|
|
381
|
+
constraints: {},
|
|
382
|
+
options: walkedOptions
|
|
347
383
|
};
|
|
348
384
|
}
|
|
349
385
|
function walkDiscriminatedUnion(discriminated, ctx) {
|
|
350
386
|
return {
|
|
351
|
-
...
|
|
387
|
+
...buildBase({}, ctx),
|
|
388
|
+
type: "discriminatedUnion",
|
|
389
|
+
constraints: {},
|
|
352
390
|
options: discriminated.options.map((opt) => walkNode(opt, {
|
|
353
391
|
...ctx,
|
|
354
392
|
fieldOverrides: void 0
|
|
@@ -356,31 +394,5 @@ function walkDiscriminatedUnion(discriminated, ctx) {
|
|
|
356
394
|
discriminator: discriminated.discriminator
|
|
357
395
|
};
|
|
358
396
|
}
|
|
359
|
-
function buildField(schema, type, ctx) {
|
|
360
|
-
const propertyMeta = extractMetaFromJson(schema);
|
|
361
|
-
const overrideMeta = extractSchemaMetaFields(ctx.fieldOverrides);
|
|
362
|
-
const mergedMeta = {
|
|
363
|
-
...propertyMeta,
|
|
364
|
-
...overrideMeta
|
|
365
|
-
};
|
|
366
|
-
const defaultValue = "default" in schema ? schema.default : void 0;
|
|
367
|
-
const editability = resolveEditability(mergedMeta, ctx.componentMeta, ctx.rootMeta);
|
|
368
|
-
if ((overrideMeta !== void 0 && ("readOnly" in overrideMeta || "writeOnly" in overrideMeta) || Boolean(propertyMeta.readOnly) || Boolean(propertyMeta.writeOnly)) && ctx.componentMeta !== void 0) ctx = {
|
|
369
|
-
...ctx,
|
|
370
|
-
componentMeta: void 0
|
|
371
|
-
};
|
|
372
|
-
return {
|
|
373
|
-
type,
|
|
374
|
-
editability,
|
|
375
|
-
meta: mergedMeta,
|
|
376
|
-
isOptional: ctx.isOptional,
|
|
377
|
-
isNullable: ctx.isNullable,
|
|
378
|
-
defaultValue: defaultValue ?? ctx.defaultValue,
|
|
379
|
-
constraints: extractConstraintsFromJson(schema)
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
function isPrimitive(value) {
|
|
383
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null;
|
|
384
|
-
}
|
|
385
397
|
//#endregion
|
|
386
398
|
export { walk };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/core/diagnostics.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Diagnostics channel for schema-components.
|
|
4
|
+
*
|
|
5
|
+
* Provides a structured way to surface silent fallbacks — unresolved `$ref`,
|
|
6
|
+
* unknown keywords, unknown `format` values, invalid `const` values,
|
|
7
|
+
* unsupported `type` entries, dropped Swagger 2.0 features, external
|
|
8
|
+
* `$ref`, type-negation fallbacks, and conditional fallbacks.
|
|
9
|
+
*
|
|
10
|
+
* Consumers pass a `DiagnosticSink` callback to receive diagnostics
|
|
11
|
+
* as they occur. By default, diagnostics are silently discarded.
|
|
12
|
+
* Setting `strict: true` converts any diagnostic into a thrown
|
|
13
|
+
* `SchemaCompatibilityError`.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Machine-readable codes identifying each class of diagnostic.
|
|
17
|
+
* Stable across releases — consumers can pattern-match on these.
|
|
18
|
+
*/
|
|
19
|
+
type DiagnosticCode = "unresolved-ref" | "unknown-keyword" | "unknown-format" | "invalid-const" | "unsupported-type" | "dropped-swagger-feature" | "external-ref" | "type-negation-fallback" | "conditional-fallback" | "assumed-draft" | "depth-exceeded";
|
|
20
|
+
/**
|
|
21
|
+
* A single diagnostic emitted during schema processing.
|
|
22
|
+
*/
|
|
23
|
+
interface Diagnostic {
|
|
24
|
+
/** Machine-readable code for programmatic handling. */
|
|
25
|
+
code: DiagnosticCode;
|
|
26
|
+
/** Human-readable description of the issue. */
|
|
27
|
+
message: string;
|
|
28
|
+
/** JSON Pointer to the schema node that triggered the diagnostic. */
|
|
29
|
+
pointer: string;
|
|
30
|
+
/** Additional context specific to the diagnostic code. */
|
|
31
|
+
detail?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Callback that receives each diagnostic as it is emitted.
|
|
35
|
+
*/
|
|
36
|
+
type DiagnosticSink = (d: Diagnostic) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Diagnostics configuration threaded through the processing pipeline.
|
|
39
|
+
*/
|
|
40
|
+
interface DiagnosticsOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Callback for receiving diagnostics. When omitted, diagnostics
|
|
43
|
+
* are silently discarded (preserving backward compatibility).
|
|
44
|
+
*/
|
|
45
|
+
diagnostics?: DiagnosticSink;
|
|
46
|
+
/**
|
|
47
|
+
* When `true`, any diagnostic is converted to a thrown
|
|
48
|
+
* `SchemaCompatibilityError`. Useful in CI or strict mode
|
|
49
|
+
* to catch schema drift early.
|
|
50
|
+
*/
|
|
51
|
+
strict?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Emit a diagnostic through the configured sink.
|
|
55
|
+
* When `strict` is enabled, throws a `SchemaCompatibilityError` instead.
|
|
56
|
+
*/
|
|
57
|
+
declare function emitDiagnostic(opts: DiagnosticsOptions | undefined, diagnostic: Diagnostic): void;
|
|
58
|
+
/**
|
|
59
|
+
* Append a segment to a JSON Pointer.
|
|
60
|
+
* Encodes `/` and `~` per RFC 6901.
|
|
61
|
+
*/
|
|
62
|
+
declare function appendPointer(base: string, segment: string): string;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { appendPointer as a, DiagnosticsOptions as i, DiagnosticCode as n, emitDiagnostic as o, DiagnosticSink as r, Diagnostic as t };
|