schema-components 1.19.0 → 1.20.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/dist/core/adapter.d.mts +2 -2
- package/dist/core/adapter.mjs +59 -9
- package/dist/core/constraints.d.mts +2 -2
- package/dist/core/diagnostics.d.mts +1 -1
- package/dist/core/errors.d.mts +1 -1
- package/dist/core/errors.mjs +9 -1
- package/dist/core/fieldOrder.d.mts +1 -1
- package/dist/core/formats.d.mts +21 -14
- package/dist/core/formats.mjs +88 -4
- package/dist/core/merge.d.mts +1 -1
- package/dist/core/normalise.d.mts +2 -2
- package/dist/core/normalise.mjs +1 -1
- package/dist/core/openapi30.mjs +1 -1
- package/dist/core/ref.d.mts +1 -1
- package/dist/core/renderer.d.mts +1 -1
- package/dist/core/swagger2.d.mts +1 -1
- package/dist/core/swagger2.mjs +1 -1
- package/dist/core/typeInference.d.mts +2 -2
- package/dist/core/types.d.mts +1 -1
- package/dist/core/uri.d.mts +41 -0
- package/dist/core/uri.mjs +76 -0
- package/dist/core/version.d.mts +2 -2
- package/dist/core/version.mjs +25 -1
- package/dist/core/walkBuilders.d.mts +3 -3
- package/dist/core/walker.d.mts +1 -1
- package/dist/core/walker.mjs +50 -3
- package/dist/{diagnostics-VgEKI_Ct.d.mts → diagnostics-CbBPsxSt.d.mts} +1 -1
- package/dist/{errors-CnGjT1cg.d.mts → errors-C2iABcn9.d.mts} +8 -1
- package/dist/html/a11y.d.mts +2 -2
- package/dist/html/renderToHtml.d.mts +2 -2
- package/dist/html/renderToHtmlStream.d.mts +2 -2
- package/dist/html/renderers.d.mts +2 -2
- package/dist/html/renderers.mjs +9 -2
- package/dist/html/streamRenderers.d.mts +2 -2
- package/dist/{normalise-C0ofw3W6.mjs → normalise-CMMEl4cd.mjs} +255 -18
- package/dist/openapi/ApiCallbacks.d.mts +1 -1
- package/dist/openapi/ApiLinks.d.mts +1 -1
- package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
- package/dist/openapi/ApiSecurity.d.mts +1 -1
- package/dist/openapi/bundle.mjs +2 -0
- package/dist/openapi/components.d.mts +2 -2
- package/dist/openapi/components.mjs +16 -5
- package/dist/openapi/parser.d.mts +1 -1
- package/dist/openapi/parser.mjs +2 -0
- package/dist/openapi/resolve.d.mts +11 -1
- package/dist/openapi/resolve.mjs +39 -2
- package/dist/react/SchemaComponent.d.mts +21 -9
- package/dist/react/SchemaView.d.mts +3 -3
- package/dist/react/fieldPath.d.mts +1 -1
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headlessRenderers.d.mts +2 -2
- package/dist/react/headlessRenderers.mjs +18 -6
- package/dist/{ref-Bb43ZURY.d.mts → ref-C8JbwfiS.d.mts} +1 -1
- package/dist/{renderer-BQqiXUYP.d.mts → renderer-SOIbJBtk.d.mts} +1 -1
- package/dist/themes/mantine.d.mts +1 -1
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/{typeInference-5JiqIZ8t.d.mts → typeInference-CDoD_LZ_.d.mts} +187 -42
- package/dist/{types-D_5ST7SS.d.mts → types-C9zw9wbX.d.mts} +6 -0
- package/dist/{version-XNH7PRGP.d.mts → version-D-u7aMfy.d.mts} +36 -1
- package/package.json +1 -1
package/dist/core/walker.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { appendPointer, emitDiagnostic } from "./diagnostics.mjs";
|
|
|
3
3
|
import { countDistinctRefs, resolveRef } from "./ref.mjs";
|
|
4
4
|
import { extractArrayConstraints, extractObjectConstraints, stripInapplicableConstraints } from "./constraints.mjs";
|
|
5
5
|
import { ANNOTATION_SIBLINGS, detectDiscriminated, mergeAllOf, mergeRefSiblings, normaliseAnyOf } from "./merge.mjs";
|
|
6
|
+
import { isPrototypePollutingKey } from "./uri.mjs";
|
|
6
7
|
import { buildBase, buildBooleanField, buildFileField, buildNullField, buildNumberField, buildStringField, buildUnknownField, extractChildOverride, extractSchemaMetaFields, getArray, getObject, getString, isPrimitive, walkDependentRequiredMap, walkSubSchemaMap, withoutKeys } from "./walkBuilders.mjs";
|
|
7
8
|
//#region src/core/walker.ts
|
|
8
9
|
/**
|
|
@@ -236,11 +237,28 @@ function walkBoolean(schema, ctx) {
|
|
|
236
237
|
return buildBooleanField(schema, ctx);
|
|
237
238
|
}
|
|
238
239
|
function walkEnum(schema, enumValues, ctx) {
|
|
240
|
+
const accepted = [];
|
|
241
|
+
for (let i = 0; i < enumValues.length; i++) {
|
|
242
|
+
const v = enumValues[i];
|
|
243
|
+
if (isPrimitive(v)) {
|
|
244
|
+
accepted.push(v);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
emitDiagnostic(ctx.diagnostics, {
|
|
248
|
+
code: "enum-value-filtered",
|
|
249
|
+
message: `enum value at index ${String(i)} is not a primitive (${v === void 0 ? "undefined" : typeof v}); dropping the entry`,
|
|
250
|
+
pointer: appendPointer(ctx.pointer, `enum/${String(i)}`),
|
|
251
|
+
detail: {
|
|
252
|
+
index: i,
|
|
253
|
+
value: v
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
239
257
|
return {
|
|
240
258
|
...buildBase(schema, ctx),
|
|
241
259
|
type: "enum",
|
|
242
260
|
constraints: {},
|
|
243
|
-
enumValues:
|
|
261
|
+
enumValues: accepted
|
|
244
262
|
};
|
|
245
263
|
}
|
|
246
264
|
function walkLiteral(schema, ctx) {
|
|
@@ -254,9 +272,35 @@ function walkLiteral(schema, ctx) {
|
|
|
254
272
|
};
|
|
255
273
|
}
|
|
256
274
|
function walkObject(schema, properties, ctx) {
|
|
257
|
-
const
|
|
275
|
+
const required = getArray(schema, "required");
|
|
276
|
+
const requiredFields = [];
|
|
277
|
+
if (required !== void 0) for (let i = 0; i < required.length; i++) {
|
|
278
|
+
const r = required[i];
|
|
279
|
+
if (typeof r === "string") {
|
|
280
|
+
requiredFields.push(r);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
emitDiagnostic(ctx.diagnostics, {
|
|
284
|
+
code: "required-non-string",
|
|
285
|
+
message: `required[${String(i)}] is not a string (${r === null ? "null" : typeof r}); dropping the entry`,
|
|
286
|
+
pointer: appendPointer(ctx.pointer, `required/${String(i)}`),
|
|
287
|
+
detail: {
|
|
288
|
+
index: i,
|
|
289
|
+
value: r
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
258
293
|
const fields = {};
|
|
259
294
|
for (const [key, propSchema] of Object.entries(properties)) {
|
|
295
|
+
if (isPrototypePollutingKey(key)) {
|
|
296
|
+
emitDiagnostic(ctx.diagnostics, {
|
|
297
|
+
code: "prototype-polluting-property",
|
|
298
|
+
message: `Refusing to register prototype-polluting property name: ${key}`,
|
|
299
|
+
pointer: appendPointer(ctx.pointer, key),
|
|
300
|
+
detail: { propertyName: key }
|
|
301
|
+
});
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
260
304
|
const childOverride = extractChildOverride(ctx.fieldOverrides, key);
|
|
261
305
|
const isRequired = requiredFields.includes(key);
|
|
262
306
|
const childCtx = {
|
|
@@ -343,11 +387,14 @@ function walkArray(schema, ctx) {
|
|
|
343
387
|
const prefixItems = getArray(schema, "prefixItems");
|
|
344
388
|
if (prefixItems !== void 0) {
|
|
345
389
|
const walkedItems = prefixItems.filter(isObject).map((item) => walkNode(item, ctx));
|
|
390
|
+
const restSchema = getObject(schema, "items");
|
|
391
|
+
const restItems = restSchema !== void 0 ? walkNode(restSchema, ctx) : void 0;
|
|
346
392
|
return {
|
|
347
393
|
...buildBase(schema, ctx),
|
|
348
394
|
type: "tuple",
|
|
349
395
|
constraints: extractArrayConstraints(schema),
|
|
350
|
-
prefixItems: walkedItems
|
|
396
|
+
prefixItems: walkedItems,
|
|
397
|
+
...restItems !== void 0 ? { restItems } : {}
|
|
351
398
|
};
|
|
352
399
|
}
|
|
353
400
|
const unevaluatedItemsSchema = getObject(schema, "unevaluatedItems");
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* Machine-readable codes identifying each class of diagnostic.
|
|
17
17
|
* Stable across releases — consumers can pattern-match on these.
|
|
18
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" | "allof-conflict" | "discriminator-inconsistent" | "divisible-by-conflict" | "legacy-dependencies-split" | "dependent-required-invalid";
|
|
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" | "allof-conflict" | "discriminator-inconsistent" | "divisible-by-conflict" | "legacy-dependencies-split" | "dependent-required-invalid" | "unknown-json-schema-dialect" | "relative-ref-resolved" | "bare-exclusive-bound" | "enum-value-filtered" | "required-non-string" | "pattern-invalid" | "duplicate-body-parameter" | "prototype-polluting-property";
|
|
20
20
|
/**
|
|
21
21
|
* A single diagnostic emitted during schema processing.
|
|
22
22
|
*/
|
|
@@ -33,7 +33,14 @@ declare class SchemaNormalisationError extends SchemaError {
|
|
|
33
33
|
* (e.g. "bigint", "date", "map", "set"). `undefined` for other kinds.
|
|
34
34
|
*/
|
|
35
35
|
readonly zodType: string | undefined;
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* The original underlying error, when this normalisation error wraps
|
|
38
|
+
* another exception (typically the error thrown by `z.toJSONSchema()`).
|
|
39
|
+
* Preserves the source stack trace so the root cause is not lost when
|
|
40
|
+
* the classifier translates the message.
|
|
41
|
+
*/
|
|
42
|
+
readonly cause: unknown;
|
|
43
|
+
constructor(message: string, schema: unknown, kind: SchemaNormalisationError["kind"], zodType?: string, cause?: unknown);
|
|
37
44
|
}
|
|
38
45
|
/**
|
|
39
46
|
* A theme adapter's render function threw during rendering.
|
package/dist/html/a11y.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as WalkedField } from "../types-
|
|
2
|
-
import { t as AllConstraints } from "../renderer-
|
|
1
|
+
import { M as WalkedField } from "../types-C9zw9wbX.mjs";
|
|
2
|
+
import { t as AllConstraints } from "../renderer-SOIbJBtk.mjs";
|
|
3
3
|
import { HtmlAttributes, HtmlNode } from "./html.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/html/a11y.d.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { T as SchemaMeta } from "../types-
|
|
2
|
-
import { o as HtmlResolver } from "../renderer-
|
|
1
|
+
import { T as SchemaMeta } from "../types-C9zw9wbX.mjs";
|
|
2
|
+
import { o as HtmlResolver } from "../renderer-SOIbJBtk.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/html/renderToHtml.d.ts
|
|
5
5
|
interface RenderToHtmlOptions {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { T as SchemaMeta } from "../types-
|
|
2
|
-
import { o as HtmlResolver } from "../renderer-
|
|
1
|
+
import { T as SchemaMeta } from "../types-C9zw9wbX.mjs";
|
|
2
|
+
import { o as HtmlResolver } from "../renderer-SOIbJBtk.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/html/renderToHtmlStream.d.ts
|
|
5
5
|
interface StreamRenderOptions {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as WalkedField } from "../types-
|
|
2
|
-
import { o as HtmlResolver } from "../renderer-
|
|
1
|
+
import { M as WalkedField } from "../types-C9zw9wbX.mjs";
|
|
2
|
+
import { o as HtmlResolver } from "../renderer-SOIbJBtk.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/html/renderers.d.ts
|
|
5
5
|
declare function dateInputType(format: string | undefined): string | undefined;
|
package/dist/html/renderers.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sortFieldsByOrder } from "../core/fieldOrder.mjs";
|
|
2
|
+
import { isSafeHyperlink, isSafeMailtoAddress } from "../core/uri.mjs";
|
|
2
3
|
import { h, raw, serialize } from "./html.mjs";
|
|
3
4
|
import { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildInputId, requiredIndicator } from "./a11y.mjs";
|
|
4
5
|
//#region src/html/renderers.ts
|
|
@@ -26,12 +27,12 @@ function renderStringReadOnly(props) {
|
|
|
26
27
|
...ariaReadonlyAttrs()
|
|
27
28
|
}, "—");
|
|
28
29
|
const format = props.constraints.format;
|
|
29
|
-
if (format === "email") return h("a", {
|
|
30
|
+
if (format === "email" && isSafeMailtoAddress(strValue)) return h("a", {
|
|
30
31
|
class: "sc-value",
|
|
31
32
|
href: `mailto:${strValue}`,
|
|
32
33
|
...ariaReadonlyAttrs()
|
|
33
34
|
}, strValue);
|
|
34
|
-
if (format === "uri" || format === "url") return h("a", {
|
|
35
|
+
if ((format === "uri" || format === "url") && isSafeHyperlink(strValue)) return h("a", {
|
|
35
36
|
class: "sc-value",
|
|
36
37
|
href: strValue,
|
|
37
38
|
...ariaReadonlyAttrs()
|
|
@@ -356,6 +357,7 @@ function renderTupleHtml(props) {
|
|
|
356
357
|
if (props.tree.type !== "tuple") return renderUnknownHtml(props);
|
|
357
358
|
const arr = Array.isArray(props.value) ? props.value : [];
|
|
358
359
|
const prefixItems = props.tree.prefixItems;
|
|
360
|
+
const restItems = props.tree.restItems;
|
|
359
361
|
const children = [];
|
|
360
362
|
for (let i = 0; i < prefixItems.length; i++) {
|
|
361
363
|
const itemValue = arr[i];
|
|
@@ -364,6 +366,11 @@ function renderTupleHtml(props) {
|
|
|
364
366
|
const childHtml = props.renderChild(element, itemValue, `[${String(i)}]`);
|
|
365
367
|
children.push(h("div", { class: "sc-tuple-item" }, h("span", { class: "sc-tuple-index" }, String(i)), raw(childHtml)));
|
|
366
368
|
}
|
|
369
|
+
if (restItems !== void 0) for (let i = prefixItems.length; i < arr.length; i++) {
|
|
370
|
+
const itemValue = arr[i];
|
|
371
|
+
const childHtml = props.renderChild(restItems, itemValue, `[${String(i)}]`);
|
|
372
|
+
children.push(h("div", { class: "sc-tuple-item sc-tuple-rest" }, h("span", { class: "sc-tuple-index" }, String(i)), raw(childHtml)));
|
|
373
|
+
}
|
|
367
374
|
return serialize(h("div", { class: "sc-tuple" }, ...children));
|
|
368
375
|
}
|
|
369
376
|
function renderConditionalHtml(props) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as WalkedField } from "../types-
|
|
2
|
-
import { o as HtmlResolver } from "../renderer-
|
|
1
|
+
import { M as WalkedField } from "../types-C9zw9wbX.mjs";
|
|
2
|
+
import { o as HtmlResolver } from "../renderer-SOIbJBtk.mjs";
|
|
3
3
|
import { HtmlElement } from "./html.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/html/streamRenderers.d.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isObject } from "./core/guards.mjs";
|
|
2
2
|
import { appendPointer, emitDiagnostic } from "./core/diagnostics.mjs";
|
|
3
|
-
import { isOpenApi30, isSwagger2 } from "./core/version.mjs";
|
|
3
|
+
import { isOpenApi30, isOpenApi31, isSwagger2, readJsonSchemaDialect } from "./core/version.mjs";
|
|
4
4
|
//#region src/core/openapi30.ts
|
|
5
5
|
/**
|
|
6
6
|
* OpenAPI 3.0.x schema normalisation.
|
|
@@ -278,7 +278,7 @@ function normaliseContentMap(content, normaliseSchema) {
|
|
|
278
278
|
const encoding = mediaObj.encoding;
|
|
279
279
|
if (isObject(encoding)) normalised.encoding = mapObjectValues(encoding, (enc) => isObject(enc) ? normaliseEncoding(enc, normaliseSchema) : enc);
|
|
280
280
|
if ("example" in normalised && !("examples" in normalised)) {
|
|
281
|
-
normalised.examples = { value: normalised.example };
|
|
281
|
+
normalised.examples = { default: { value: normalised.example } };
|
|
282
282
|
delete normalised.example;
|
|
283
283
|
} else if ("example" in normalised) delete normalised.example;
|
|
284
284
|
result[mediaType] = normalised;
|
|
@@ -332,7 +332,7 @@ function normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, dia
|
|
|
332
332
|
result.servers = [{ url: `${typeof schemes[0] === "string" ? schemes[0] : "https"}://${host}${basePath}` }];
|
|
333
333
|
}
|
|
334
334
|
const paths = doc.paths;
|
|
335
|
-
if (isObject(paths)) result.paths = normaliseSwaggerPaths(paths, doc);
|
|
335
|
+
if (isObject(paths)) result.paths = normaliseSwaggerPaths(paths, doc, diagnostics);
|
|
336
336
|
const components = {};
|
|
337
337
|
const definitions = doc.definitions;
|
|
338
338
|
if (isObject(definitions)) {
|
|
@@ -353,7 +353,7 @@ function normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, dia
|
|
|
353
353
|
const resolved = resolveSwaggerParameter(param, doc);
|
|
354
354
|
const location = resolved.in;
|
|
355
355
|
if (location === "body") requestBodies[name] = buildRequestBody(resolved, globalConsumes);
|
|
356
|
-
else if (location === "formData") requestBodies[name] = buildRequestBody(buildFormDataBody(resolved, [resolved]),
|
|
356
|
+
else if (location === "formData") requestBodies[name] = buildRequestBody(buildFormDataBody(resolved, [resolved]), formDataContentTypes(globalConsumes));
|
|
357
357
|
else convertedParameters[name] = normaliseSwaggerParameter(resolved, doc);
|
|
358
358
|
}
|
|
359
359
|
if (Object.keys(convertedParameters).length > 0) components.parameters = convertedParameters;
|
|
@@ -381,7 +381,7 @@ function normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, dia
|
|
|
381
381
|
});
|
|
382
382
|
return result;
|
|
383
383
|
}
|
|
384
|
-
function normaliseSwaggerPaths(paths, doc) {
|
|
384
|
+
function normaliseSwaggerPaths(paths, doc, diagnostics) {
|
|
385
385
|
const result = {};
|
|
386
386
|
const METHODS = [
|
|
387
387
|
"get",
|
|
@@ -401,7 +401,7 @@ function normaliseSwaggerPaths(paths, doc) {
|
|
|
401
401
|
for (const method of METHODS) {
|
|
402
402
|
const operation = pathItem[method];
|
|
403
403
|
if (!isObject(operation)) continue;
|
|
404
|
-
normalisedPath[method] = normaliseSwaggerOperation(operation, doc);
|
|
404
|
+
normalisedPath[method] = normaliseSwaggerOperation(operation, doc, path, method, diagnostics);
|
|
405
405
|
}
|
|
406
406
|
const pathParams = pathItem.parameters;
|
|
407
407
|
if (Array.isArray(pathParams)) normalisedPath.parameters = pathParams.map((p) => isObject(p) ? normaliseSwaggerParameter(p, doc) : p);
|
|
@@ -409,7 +409,7 @@ function normaliseSwaggerPaths(paths, doc) {
|
|
|
409
409
|
}
|
|
410
410
|
return result;
|
|
411
411
|
}
|
|
412
|
-
function normaliseSwaggerOperation(operation, doc) {
|
|
412
|
+
function normaliseSwaggerOperation(operation, doc, path, method, diagnostics) {
|
|
413
413
|
const result = {};
|
|
414
414
|
const globalProduces = Array.isArray(doc.produces) ? doc.produces : ["application/json"];
|
|
415
415
|
const globalConsumes = Array.isArray(doc.consumes) ? doc.consumes : ["application/json"];
|
|
@@ -420,28 +420,61 @@ function normaliseSwaggerOperation(operation, doc) {
|
|
|
420
420
|
if (Array.isArray(params)) {
|
|
421
421
|
const nonBodyParams = [];
|
|
422
422
|
let bodyParam;
|
|
423
|
+
let firstBodyName;
|
|
423
424
|
let usesFormData = false;
|
|
424
|
-
for (const param of params) {
|
|
425
|
+
for (const [index, param] of params.entries()) {
|
|
425
426
|
if (!isObject(param)) {
|
|
426
427
|
nonBodyParams.push(param);
|
|
427
428
|
continue;
|
|
428
429
|
}
|
|
429
430
|
const resolvedParam = resolveSwaggerParameter(param, doc);
|
|
430
431
|
const location = resolvedParam.in;
|
|
431
|
-
if (location === "body")
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
432
|
+
if (location === "body") {
|
|
433
|
+
if (bodyParam !== void 0) {
|
|
434
|
+
const duplicateName = typeof resolvedParam.name === "string" ? resolvedParam.name : `parameters[${String(index)}]`;
|
|
435
|
+
emitDiagnostic(diagnostics, {
|
|
436
|
+
code: "duplicate-body-parameter",
|
|
437
|
+
message: `Operation defines more than one "in: body" parameter; keeping the first ("${firstBodyName ?? "(unnamed)"}") and discarding "${duplicateName}"`,
|
|
438
|
+
pointer: appendPointer(appendPointer(appendPointer(appendPointer("", "paths"), path), method), "parameters"),
|
|
439
|
+
detail: {
|
|
440
|
+
kept: firstBodyName,
|
|
441
|
+
discarded: duplicateName,
|
|
442
|
+
location: "operation"
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
bodyParam = resolvedParam;
|
|
448
|
+
firstBodyName = typeof resolvedParam.name === "string" ? resolvedParam.name : void 0;
|
|
449
|
+
} else if (location === "formData") {
|
|
450
|
+
if (!usesFormData) {
|
|
451
|
+
bodyParam = buildFormDataBody(resolvedParam, params);
|
|
452
|
+
usesFormData = true;
|
|
453
|
+
}
|
|
435
454
|
} else nonBodyParams.push(normaliseSwaggerParameter(resolvedParam, doc));
|
|
436
455
|
}
|
|
437
456
|
if (nonBodyParams.length > 0) result.parameters = nonBodyParams;
|
|
438
|
-
if (bodyParam !== void 0) result.requestBody = buildRequestBody(bodyParam, usesFormData ?
|
|
457
|
+
if (bodyParam !== void 0) result.requestBody = buildRequestBody(bodyParam, usesFormData ? formDataContentTypes(consumes) : consumes);
|
|
439
458
|
}
|
|
440
459
|
const responses = operation.responses;
|
|
441
460
|
if (isObject(responses)) result.responses = normaliseSwaggerResponses(responses, doc, produces);
|
|
442
461
|
return result;
|
|
443
462
|
}
|
|
444
463
|
/**
|
|
464
|
+
* Determine the request body media type for a Swagger 2.0 formData operation.
|
|
465
|
+
*
|
|
466
|
+
* Per the OAS 3 conversion rules, `application/x-www-form-urlencoded` is
|
|
467
|
+
* preferred when the operation- or document-level `consumes` includes it;
|
|
468
|
+
* otherwise `multipart/form-data` is the default. File uploads (Swagger 2.0
|
|
469
|
+
* `type: file`) still require `multipart/form-data`, but the formData body
|
|
470
|
+
* schema-builder normalises them to `string` + `format: binary` either way
|
|
471
|
+
* and the choice of media type is left to the source document.
|
|
472
|
+
*/
|
|
473
|
+
function formDataContentTypes(consumes) {
|
|
474
|
+
if (consumes.includes("application/x-www-form-urlencoded")) return ["application/x-www-form-urlencoded"];
|
|
475
|
+
return ["multipart/form-data"];
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
445
478
|
* Resolve a Swagger parameter that may be a `$ref`.
|
|
446
479
|
*/
|
|
447
480
|
function resolveSwaggerParameter(param, doc, visited = /* @__PURE__ */ new Set()) {
|
|
@@ -596,7 +629,7 @@ function normaliseSwaggerResponses(responses, doc, produces) {
|
|
|
596
629
|
function normaliseSwaggerSingleResponse(response, doc, produces) {
|
|
597
630
|
const resolved = resolveSwaggerResponse(response, doc);
|
|
598
631
|
const normalised = {};
|
|
599
|
-
for (const [key, value] of Object.entries(resolved)) if (key !== "schema") normalised[key] = value;
|
|
632
|
+
for (const [key, value] of Object.entries(resolved)) if (key !== "schema" && key !== "headers") normalised[key] = value;
|
|
600
633
|
const schema = resolved.schema;
|
|
601
634
|
if (isObject(schema)) {
|
|
602
635
|
const content = {};
|
|
@@ -604,9 +637,64 @@ function normaliseSwaggerSingleResponse(response, doc, produces) {
|
|
|
604
637
|
for (const ct of contentTypes) if (typeof ct === "string") content[ct] = { schema };
|
|
605
638
|
normalised.content = content;
|
|
606
639
|
}
|
|
640
|
+
const headers = resolved.headers;
|
|
641
|
+
if (isObject(headers)) {
|
|
642
|
+
const convertedHeaders = {};
|
|
643
|
+
for (const [name, header] of Object.entries(headers)) convertedHeaders[name] = isObject(header) ? normaliseSwaggerHeader(header) : header;
|
|
644
|
+
normalised.headers = convertedHeaders;
|
|
645
|
+
}
|
|
607
646
|
return normalised;
|
|
608
647
|
}
|
|
609
648
|
/**
|
|
649
|
+
* Normalise a single Swagger 2.0 response header to OpenAPI 3.x form.
|
|
650
|
+
*
|
|
651
|
+
* Swagger 2.0 headers mirror parameter shape: `type`/`format`/
|
|
652
|
+
* `collectionFormat` live at the root. OpenAPI 3.x requires the type
|
|
653
|
+
* descriptor under `schema`, with collection serialisation expressed via
|
|
654
|
+
* `style`/`explode`. Headers do not carry `name` or `in` — those are not
|
|
655
|
+
* part of either spec at this level — so this is a thin sibling to
|
|
656
|
+
* `normaliseSwaggerParameter` rather than a full reuse. The OpenAPI 3.x
|
|
657
|
+
* default header style is `simple`, so CSV-encoded headers map to
|
|
658
|
+
* `simple`/`explode: false` rather than the `form` style used for query
|
|
659
|
+
* parameters.
|
|
660
|
+
*/
|
|
661
|
+
function normaliseSwaggerHeader(header) {
|
|
662
|
+
const result = {};
|
|
663
|
+
for (const [key, value] of Object.entries(header)) {
|
|
664
|
+
if (key === "type" || key === "format" || key === "collectionFormat") continue;
|
|
665
|
+
result[key] = value;
|
|
666
|
+
}
|
|
667
|
+
if (typeof header.type === "string") {
|
|
668
|
+
const schema = { type: header.type };
|
|
669
|
+
if (typeof header.format === "string") schema.format = header.format;
|
|
670
|
+
if (header.enum !== void 0) schema.enum = header.enum;
|
|
671
|
+
if (header.default !== void 0) schema.default = header.default;
|
|
672
|
+
if (header.minimum !== void 0) schema.minimum = header.minimum;
|
|
673
|
+
if (header.maximum !== void 0) schema.maximum = header.maximum;
|
|
674
|
+
result.schema = schema;
|
|
675
|
+
}
|
|
676
|
+
const cf = header.collectionFormat;
|
|
677
|
+
if (typeof cf === "string") switch (cf) {
|
|
678
|
+
case "csv":
|
|
679
|
+
result.style = "simple";
|
|
680
|
+
result.explode = false;
|
|
681
|
+
break;
|
|
682
|
+
case "ssv":
|
|
683
|
+
result.style = "spaceDelimited";
|
|
684
|
+
result.explode = false;
|
|
685
|
+
break;
|
|
686
|
+
case "tsv":
|
|
687
|
+
result.style = "tabDelimited";
|
|
688
|
+
result.explode = false;
|
|
689
|
+
break;
|
|
690
|
+
case "pipes":
|
|
691
|
+
result.style = "pipeDelimited";
|
|
692
|
+
result.explode = false;
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
return result;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
610
698
|
* Mapping of Swagger 2.0 $ref prefixes to OpenAPI 3.x equivalents.
|
|
611
699
|
* Applied after document restructuring so all $ref strings point
|
|
612
700
|
* to the correct locations in the normalised document.
|
|
@@ -906,10 +994,28 @@ function applyDraft04Translations(node, ctx) {
|
|
|
906
994
|
node.exclusiveMinimum = node.minimum;
|
|
907
995
|
delete node.minimum;
|
|
908
996
|
} else if (node.exclusiveMinimum === false) delete node.exclusiveMinimum;
|
|
997
|
+
else if (node.exclusiveMinimum === true) {
|
|
998
|
+
if (ctx !== void 0) emitDiagnostic(ctx.diagnostics, {
|
|
999
|
+
code: "bare-exclusive-bound",
|
|
1000
|
+
message: "`exclusiveMinimum: true` requires a sibling numeric `minimum` in Draft 04; dropping the keyword",
|
|
1001
|
+
pointer: appendPointer(ctx.pointer, "exclusiveMinimum"),
|
|
1002
|
+
detail: { keyword: "exclusiveMinimum" }
|
|
1003
|
+
});
|
|
1004
|
+
delete node.exclusiveMinimum;
|
|
1005
|
+
}
|
|
909
1006
|
if (node.exclusiveMaximum === true && typeof node.maximum === "number") {
|
|
910
1007
|
node.exclusiveMaximum = node.maximum;
|
|
911
1008
|
delete node.maximum;
|
|
912
1009
|
} else if (node.exclusiveMaximum === false) delete node.exclusiveMaximum;
|
|
1010
|
+
else if (node.exclusiveMaximum === true) {
|
|
1011
|
+
if (ctx !== void 0) emitDiagnostic(ctx.diagnostics, {
|
|
1012
|
+
code: "bare-exclusive-bound",
|
|
1013
|
+
message: "`exclusiveMaximum: true` requires a sibling numeric `maximum` in Draft 04; dropping the keyword",
|
|
1014
|
+
pointer: appendPointer(ctx.pointer, "exclusiveMaximum"),
|
|
1015
|
+
detail: { keyword: "exclusiveMaximum" }
|
|
1016
|
+
});
|
|
1017
|
+
delete node.exclusiveMaximum;
|
|
1018
|
+
}
|
|
913
1019
|
const divisibleBy = node.divisibleBy;
|
|
914
1020
|
if (typeof divisibleBy === "number") {
|
|
915
1021
|
const multipleOf = node.multipleOf;
|
|
@@ -1045,13 +1151,135 @@ function normaliseJsonSchema(schema, draft, diagnostics) {
|
|
|
1045
1151
|
diagnostics,
|
|
1046
1152
|
pointer: ""
|
|
1047
1153
|
};
|
|
1154
|
+
let normalised;
|
|
1048
1155
|
switch (draft) {
|
|
1049
|
-
case "draft-04":
|
|
1050
|
-
|
|
1051
|
-
|
|
1156
|
+
case "draft-04":
|
|
1157
|
+
normalised = deepNormaliseWithContext(schema, normaliseDraft04NodeWithContext, ctx);
|
|
1158
|
+
break;
|
|
1159
|
+
case "draft-2019-09":
|
|
1160
|
+
normalised = deepNormaliseWithContext(schema, normaliseDraft201909NodeWithContext, ctx);
|
|
1161
|
+
break;
|
|
1162
|
+
case "draft-2020-12":
|
|
1163
|
+
normalised = deepNormaliseWithContext(schema, normaliseDynamicRefNodeWithContext, ctx);
|
|
1164
|
+
break;
|
|
1052
1165
|
case "draft-06":
|
|
1053
|
-
case "draft-07":
|
|
1166
|
+
case "draft-07":
|
|
1167
|
+
normalised = deepNormaliseWithContext(schema, normaliseDraft06Or07NodeWithContext, ctx);
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
return resolveRelativeRefs(normalised, diagnostics);
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Parse a string as an absolute URI, returning `undefined` when it has
|
|
1174
|
+
* no scheme. Used to detect whether an `$id` value defines a base URI.
|
|
1175
|
+
*/
|
|
1176
|
+
function parseAbsoluteUri(value) {
|
|
1177
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
1178
|
+
try {
|
|
1179
|
+
const url = new URL(value);
|
|
1180
|
+
if (url.protocol.length === 0) return void 0;
|
|
1181
|
+
return url;
|
|
1182
|
+
} catch {
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Resolve a relative reference against a base URI. Returns `undefined`
|
|
1188
|
+
* when the reference cannot be resolved (e.g. malformed input).
|
|
1189
|
+
*/
|
|
1190
|
+
function resolveAgainst(ref, base) {
|
|
1191
|
+
try {
|
|
1192
|
+
return new URL(ref, base);
|
|
1193
|
+
} catch {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Strip the fragment portion from a URL, returning the canonical
|
|
1199
|
+
* `scheme://authority/path?query` form. Used to compare a resolved
|
|
1200
|
+
* `$ref` URI against the document's `$id` base.
|
|
1201
|
+
*/
|
|
1202
|
+
function stripFragment(url) {
|
|
1203
|
+
const clone = new URL(url.toString());
|
|
1204
|
+
clone.hash = "";
|
|
1205
|
+
return clone.toString();
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Recursively rewrite relative `$ref`s in a schema so they resolve
|
|
1209
|
+
* correctly under the JSON Schema base-URI rules (RFC 3986 + JSON
|
|
1210
|
+
* Schema §8.2). Refs that resolve to the document's own `$id` are
|
|
1211
|
+
* rewritten to fragment-only form so the existing dereferencer can
|
|
1212
|
+
* handle them; refs that resolve outside the document are left as
|
|
1213
|
+
* absolute URIs (handled by the external resolver path).
|
|
1214
|
+
*
|
|
1215
|
+
* Returns the input unchanged when the document has no base URI or
|
|
1216
|
+
* no relative refs.
|
|
1217
|
+
*/
|
|
1218
|
+
function resolveRelativeRefs(schema, diagnostics) {
|
|
1219
|
+
const docBaseUrl = parseAbsoluteUri(schema.$id);
|
|
1220
|
+
if (docBaseUrl === void 0) return schema;
|
|
1221
|
+
const docBase = stripFragment(docBaseUrl);
|
|
1222
|
+
return rewriteRelativeRefsNode(schema, docBase, docBase, "", diagnostics);
|
|
1223
|
+
}
|
|
1224
|
+
function rewriteRelativeRefsNode(node, currentBase, docBase, pointer, diagnostics) {
|
|
1225
|
+
let nextBase = currentBase;
|
|
1226
|
+
const nodeId = node.$id;
|
|
1227
|
+
if (typeof nodeId === "string" && nodeId.length > 0) {
|
|
1228
|
+
const resolved = resolveAgainst(nodeId, currentBase);
|
|
1229
|
+
if (resolved !== void 0) nextBase = stripFragment(resolved);
|
|
1230
|
+
}
|
|
1231
|
+
const result = {};
|
|
1232
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1233
|
+
if (key === "$ref" && typeof value === "string") {
|
|
1234
|
+
result[key] = rewriteRef(value, nextBase, docBase, appendPointer(pointer, key), diagnostics);
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
result[key] = rewriteRelativeRefsValue(value, key, nextBase, docBase, appendPointer(pointer, key), diagnostics);
|
|
1054
1238
|
}
|
|
1239
|
+
return result;
|
|
1240
|
+
}
|
|
1241
|
+
function rewriteRelativeRefsValue(value, parentKey, currentBase, docBase, pointer, diagnostics) {
|
|
1242
|
+
if (Array.isArray(value)) return value.map((item, i) => rewriteRelativeRefsValue(item, parentKey, currentBase, docBase, appendPointer(pointer, String(i)), diagnostics));
|
|
1243
|
+
if (isObject(value)) return rewriteRelativeRefsNode(value, currentBase, docBase, pointer, diagnostics);
|
|
1244
|
+
return value;
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Rewrite a single `$ref` string. Fragment-only refs and refs that
|
|
1248
|
+
* already include a scheme are returned unchanged. Relative refs are
|
|
1249
|
+
* resolved against `currentBase`; if the result lives in the same
|
|
1250
|
+
* document as `docBase`, the ref is rewritten to fragment form.
|
|
1251
|
+
*/
|
|
1252
|
+
function rewriteRef(ref, currentBase, docBase, pointer, diagnostics) {
|
|
1253
|
+
if (ref.startsWith("#")) return ref;
|
|
1254
|
+
if (/^[a-z][a-z0-9+\-.]*:/i.test(ref)) return ref;
|
|
1255
|
+
const resolved = resolveAgainst(ref, currentBase);
|
|
1256
|
+
if (resolved === void 0) return ref;
|
|
1257
|
+
if (stripFragment(resolved) === docBase) {
|
|
1258
|
+
const fragment = resolved.hash === "" ? "#" : resolved.hash;
|
|
1259
|
+
emitDiagnostic(diagnostics, {
|
|
1260
|
+
code: "relative-ref-resolved",
|
|
1261
|
+
message: `Relative $ref "${ref}" resolved to "${fragment}" against base "${currentBase}"`,
|
|
1262
|
+
pointer,
|
|
1263
|
+
detail: {
|
|
1264
|
+
ref,
|
|
1265
|
+
base: currentBase,
|
|
1266
|
+
resolved: fragment
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
return fragment;
|
|
1270
|
+
}
|
|
1271
|
+
const absolute = resolved.toString();
|
|
1272
|
+
emitDiagnostic(diagnostics, {
|
|
1273
|
+
code: "relative-ref-resolved",
|
|
1274
|
+
message: `Relative $ref "${ref}" resolved to "${absolute}" against base "${currentBase}"`,
|
|
1275
|
+
pointer,
|
|
1276
|
+
detail: {
|
|
1277
|
+
ref,
|
|
1278
|
+
base: currentBase,
|
|
1279
|
+
resolved: absolute
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
return absolute;
|
|
1055
1283
|
}
|
|
1056
1284
|
/**
|
|
1057
1285
|
* Normalise an OpenAPI document's schemas for walker consumption.
|
|
@@ -1063,6 +1291,15 @@ function normaliseJsonSchema(schema, draft, diagnostics) {
|
|
|
1063
1291
|
function normaliseOpenApiSchemas(doc, version, diagnostics) {
|
|
1064
1292
|
if (isSwagger2(version)) return normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, diagnostics);
|
|
1065
1293
|
if (isOpenApi30(version)) return deepNormaliseOpenApi30Doc(doc, deepNormalise);
|
|
1294
|
+
if (isOpenApi31(version)) {
|
|
1295
|
+
const dialect = readJsonSchemaDialect(doc);
|
|
1296
|
+
if (dialect.kind === "unknown") emitDiagnostic(diagnostics, {
|
|
1297
|
+
code: "unknown-json-schema-dialect",
|
|
1298
|
+
message: `OpenAPI 3.1 \`jsonSchemaDialect\` URI "${dialect.uri}" does not match a supported JSON Schema draft; falling back to Draft 2020-12`,
|
|
1299
|
+
pointer: "/jsonSchemaDialect",
|
|
1300
|
+
detail: { uri: dialect.uri }
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1066
1303
|
return deepNormaliseOpenApiDoc(doc, (schema) => deepNormalise(schema, normaliseOpenApi30Discriminator));
|
|
1067
1304
|
}
|
|
1068
1305
|
//#endregion
|
package/dist/openapi/bundle.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isObject } from "../core/guards.mjs";
|
|
2
|
+
import { isPrototypePollutingKey } from "../core/uri.mjs";
|
|
2
3
|
//#region src/openapi/bundle.ts
|
|
3
4
|
/**
|
|
4
5
|
* OpenAPI document bundler — inlines external $ref files.
|
|
@@ -104,6 +105,7 @@ function resolveFragment(doc, fragment) {
|
|
|
104
105
|
for (const part of parts) {
|
|
105
106
|
if (!isObject(current)) return void 0;
|
|
106
107
|
const decoded = part.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
108
|
+
if (isPrototypePollutingKey(decoded)) return void 0;
|
|
107
109
|
current = current[decoded];
|
|
108
110
|
}
|
|
109
111
|
return isObject(current) ? current : void 0;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { T as SchemaMeta, u as FieldOverride } from "../types-
|
|
2
|
-
import { a as InferResponseFields,
|
|
1
|
+
import { T as SchemaMeta, u as FieldOverride } from "../types-C9zw9wbX.mjs";
|
|
2
|
+
import { a as InferResponseFields, i as InferRequestBodyFields, m as UnsafeFields, r as InferParameterOverrides } from "../typeInference-CDoD_LZ_.mjs";
|
|
3
3
|
import { WidgetMap } from "../react/SchemaComponent.mjs";
|
|
4
4
|
import { ReactNode } from "react";
|
|
5
5
|
|