schema-components 1.21.0 → 1.22.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 +1 -1
- package/dist/core/adapter.d.mts +20 -3
- package/dist/core/adapter.mjs +209 -28
- 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/fieldOrder.d.mts +1 -1
- package/dist/core/formats.d.mts +22 -1
- package/dist/core/formats.mjs +21 -0
- package/dist/core/limits.d.mts +2 -0
- package/dist/core/limits.mjs +23 -0
- package/dist/core/merge.d.mts +1 -1
- package/dist/core/normalise.d.mts +29 -3
- package/dist/core/normalise.mjs +2 -2
- package/dist/core/openapi30.mjs +1 -1
- package/dist/core/ref.d.mts +1 -1
- package/dist/core/ref.mjs +1 -0
- package/dist/core/renderer.d.mts +1 -1
- package/dist/core/renderer.mjs +0 -2
- 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 +2 -2
- package/dist/core/types.mjs +1 -4
- package/dist/core/version.d.mts +1 -1
- package/dist/core/walkBuilders.d.mts +3 -3
- package/dist/core/walker.d.mts +1 -1
- package/dist/core/walker.mjs +79 -2
- package/dist/{diagnostics-CbBPsxSt.d.mts → diagnostics-D0QCYGv0.d.mts} +1 -1
- package/dist/{errors-QEwOtQAA.d.mts → errors-DpFwqs5C.d.mts} +1 -1
- package/dist/html/a11y.d.mts +2 -2
- package/dist/html/a11y.mjs +10 -3
- package/dist/html/renderToHtml.d.mts +10 -3
- package/dist/html/renderToHtml.mjs +13 -3
- package/dist/html/renderToHtmlStream.d.mts +2 -2
- package/dist/html/renderers.d.mts +2 -2
- package/dist/html/renderers.mjs +0 -5
- package/dist/html/streamRenderers.d.mts +5 -4
- package/dist/html/streamRenderers.mjs +91 -30
- package/dist/limits-Cw5QZND8.d.mts +29 -0
- package/dist/{normalise-DaSrnr8g.mjs → normalise-DVEJQmF7.mjs} +468 -115
- 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/ApiSecurity.mjs +16 -2
- package/dist/openapi/components.d.mts +150 -18
- package/dist/openapi/components.mjs +129 -15
- package/dist/openapi/parser.d.mts +1 -1
- package/dist/openapi/parser.mjs +35 -3
- package/dist/openapi/resolve.d.mts +12 -5
- package/dist/openapi/resolve.mjs +183 -23
- package/dist/react/SchemaComponent.d.mts +100 -35
- package/dist/react/SchemaComponent.mjs +59 -45
- package/dist/react/SchemaView.d.mts +3 -3
- package/dist/react/SchemaView.mjs +2 -2
- package/dist/react/fieldPath.d.mts +1 -1
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headless.mjs +1 -2
- package/dist/react/headlessRenderers.d.mts +3 -4
- package/dist/react/headlessRenderers.mjs +10 -30
- package/dist/{ref-si8ViYun.d.mts → ref-D-_JBZkF.d.mts} +1 -1
- package/dist/{renderer-DI6ZYf7a.d.mts → renderer-BaRlQIuN.d.mts} +2 -2
- 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-DkcUHfaM.d.mts +982 -0
- package/dist/{types-BnxPEElk.d.mts → types-BrRMV0en.d.mts} +3 -10
- package/package.json +1 -3
- package/dist/typeInference-Bxw3NOG1.d.mts +0 -647
- /package/dist/{version-D-u7aMfy.d.mts → version-D2jfdX6E.d.mts} +0 -0
|
@@ -29,6 +29,8 @@ function normaliseOpenApi30Node(node) {
|
|
|
29
29
|
return node;
|
|
30
30
|
}
|
|
31
31
|
const nullOption = { type: "null" };
|
|
32
|
+
if (typeof node.$ref === "string") return { anyOf: [{ $ref: node.$ref }, nullOption] };
|
|
33
|
+
if (Array.isArray(node.enum) && !node.enum.includes(null)) node.enum = [...node.enum, null];
|
|
32
34
|
if (Array.isArray(node.anyOf)) {
|
|
33
35
|
node.anyOf = [...node.anyOf, nullOption];
|
|
34
36
|
delete node.nullable;
|
|
@@ -113,7 +115,13 @@ function normaliseOpenApi30Discriminator(node) {
|
|
|
113
115
|
}
|
|
114
116
|
if ("oneOf" in node) node.oneOf = normalisedComposite;
|
|
115
117
|
else if ("anyOf" in node) node.anyOf = normalisedComposite;
|
|
116
|
-
|
|
118
|
+
const extensions = {};
|
|
119
|
+
for (const [key, value] of Object.entries(discriminator)) if (key.startsWith("x-")) extensions[key] = value;
|
|
120
|
+
if (Object.keys(extensions).length > 0) node.discriminator = {
|
|
121
|
+
propertyName,
|
|
122
|
+
...extensions
|
|
123
|
+
};
|
|
124
|
+
else delete node.discriminator;
|
|
117
125
|
return node;
|
|
118
126
|
}
|
|
119
127
|
/**
|
|
@@ -480,7 +488,7 @@ function normaliseParameter(param, normaliseSchema) {
|
|
|
480
488
|
const content = param.content;
|
|
481
489
|
if (isObject(content)) result.content = normaliseContentMap(content, normaliseSchema);
|
|
482
490
|
if ("example" in result && !("examples" in result)) {
|
|
483
|
-
result.examples =
|
|
491
|
+
result.examples = { default: { value: result.example } };
|
|
484
492
|
delete result.example;
|
|
485
493
|
} else if ("example" in result) delete result.example;
|
|
486
494
|
return result;
|
|
@@ -506,7 +514,7 @@ function normaliseHeader(header, normaliseSchema) {
|
|
|
506
514
|
const content = header.content;
|
|
507
515
|
if (isObject(content)) result.content = normaliseContentMap(content, normaliseSchema);
|
|
508
516
|
if ("example" in result && !("examples" in result)) {
|
|
509
|
-
result.examples =
|
|
517
|
+
result.examples = { default: { value: result.example } };
|
|
510
518
|
delete result.example;
|
|
511
519
|
} else if ("example" in result) delete result.example;
|
|
512
520
|
return result;
|
|
@@ -579,9 +587,19 @@ function normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, dia
|
|
|
579
587
|
version: "0.0.0"
|
|
580
588
|
}
|
|
581
589
|
};
|
|
582
|
-
if (typeof doc.host
|
|
583
|
-
|
|
584
|
-
|
|
590
|
+
if (typeof doc.host !== "string") {
|
|
591
|
+
if (Array.isArray(doc.schemes) || typeof doc.basePath === "string") emitDiagnostic(diagnostics, {
|
|
592
|
+
code: "swagger-missing-host",
|
|
593
|
+
message: "Swagger 2.0 document declares schemes or basePath without host; skipping server URL synthesis",
|
|
594
|
+
pointer: "",
|
|
595
|
+
detail: {
|
|
596
|
+
hasSchemes: Array.isArray(doc.schemes),
|
|
597
|
+
hasBasePath: typeof doc.basePath === "string"
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
} else {
|
|
601
|
+
const host = doc.host;
|
|
602
|
+
const basePath = typeof doc.basePath === "string" ? doc.basePath : "";
|
|
585
603
|
const schemes = Array.isArray(doc.schemes) ? doc.schemes : ["https"];
|
|
586
604
|
result.servers = [{ url: `${typeof schemes[0] === "string" ? schemes[0] : "https"}://${host}${basePath}` }];
|
|
587
605
|
}
|
|
@@ -597,31 +615,62 @@ function normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, dia
|
|
|
597
615
|
const parameters = doc.parameters;
|
|
598
616
|
const requestBodies = {};
|
|
599
617
|
if (isObject(parameters)) {
|
|
600
|
-
const
|
|
618
|
+
const consumesResolution = resolveSwaggerContentTypes(void 0, doc.consumes);
|
|
619
|
+
const globalConsumes = consumesResolution.types;
|
|
601
620
|
const convertedParameters = {};
|
|
602
621
|
for (const [name, param] of Object.entries(parameters)) {
|
|
603
622
|
if (!isObject(param)) {
|
|
604
623
|
convertedParameters[name] = param;
|
|
605
624
|
continue;
|
|
606
625
|
}
|
|
607
|
-
const
|
|
626
|
+
const resolution = resolveSwaggerParameter(param, doc);
|
|
627
|
+
if (resolution.kind === "cycle") {
|
|
628
|
+
emitDiagnostic(diagnostics, {
|
|
629
|
+
code: "swagger-cyclic-parameter-ref",
|
|
630
|
+
message: `Cyclic Swagger 2.0 parameter $ref "${resolution.ref}"; skipping entry`,
|
|
631
|
+
pointer: appendPointer(appendPointer("", "parameters"), name),
|
|
632
|
+
detail: {
|
|
633
|
+
ref: resolution.ref,
|
|
634
|
+
name
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
const resolved = resolution.param;
|
|
608
640
|
const location = resolved.in;
|
|
609
|
-
if (location === "body")
|
|
610
|
-
|
|
611
|
-
|
|
641
|
+
if (location === "body") {
|
|
642
|
+
if (consumesResolution.source === "synthesised") emitDiagnostic(diagnostics, {
|
|
643
|
+
code: "swagger-missing-consumes",
|
|
644
|
+
message: "Global body parameter declared but document-level `consumes` is absent; defaulting to application/json",
|
|
645
|
+
pointer: appendPointer(appendPointer("", "parameters"), name),
|
|
646
|
+
detail: {
|
|
647
|
+
level: "document",
|
|
648
|
+
name
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
requestBodies[name] = buildRequestBody(resolved, globalConsumes);
|
|
652
|
+
} else if (location === "formData") requestBodies[name] = buildRequestBody(buildFormDataBody(resolved, [resolved]), formDataContentTypes(globalConsumes));
|
|
653
|
+
else {
|
|
654
|
+
const normalised = normaliseSwaggerParameter(resolved, doc, diagnostics, appendPointer(appendPointer("", "parameters"), name));
|
|
655
|
+
if (normalised !== void 0) convertedParameters[name] = normalised;
|
|
656
|
+
}
|
|
612
657
|
}
|
|
613
658
|
if (Object.keys(convertedParameters).length > 0) components.parameters = convertedParameters;
|
|
614
659
|
}
|
|
615
660
|
const responses = doc.responses;
|
|
616
661
|
if (isObject(responses)) {
|
|
617
|
-
const
|
|
662
|
+
const producesResolution = resolveSwaggerContentTypes(void 0, doc.produces);
|
|
618
663
|
const convertedResponses = {};
|
|
619
|
-
for (const [name, response] of Object.entries(responses)) convertedResponses[name] = isObject(response) ? normaliseSwaggerSingleResponse(response, doc,
|
|
664
|
+
for (const [name, response] of Object.entries(responses)) convertedResponses[name] = isObject(response) ? normaliseSwaggerSingleResponse(response, doc, producesResolution.types, producesResolution.source, diagnostics, void 0, void 0, name) : response;
|
|
620
665
|
components.responses = convertedResponses;
|
|
621
666
|
}
|
|
622
667
|
if (Object.keys(requestBodies).length > 0) components.requestBodies = requestBodies;
|
|
623
668
|
const securityDefinitions = doc.securityDefinitions;
|
|
624
|
-
if (isObject(securityDefinitions))
|
|
669
|
+
if (isObject(securityDefinitions)) {
|
|
670
|
+
const translated = {};
|
|
671
|
+
for (const [name, scheme] of Object.entries(securityDefinitions)) translated[name] = isObject(scheme) ? translateSwaggerSecurityScheme(scheme) : scheme;
|
|
672
|
+
components.securitySchemes = translated;
|
|
673
|
+
}
|
|
625
674
|
if (Object.keys(components).length > 0) result.components = components;
|
|
626
675
|
if (Array.isArray(doc.tags)) result.tags = doc.tags;
|
|
627
676
|
if (isObject(doc.externalDocs)) result.externalDocs = doc.externalDocs;
|
|
@@ -658,17 +707,29 @@ function normaliseSwaggerPaths(paths, doc, diagnostics) {
|
|
|
658
707
|
normalisedPath[method] = normaliseSwaggerOperation(operation, doc, path, method, diagnostics);
|
|
659
708
|
}
|
|
660
709
|
const pathParams = pathItem.parameters;
|
|
661
|
-
if (Array.isArray(pathParams))
|
|
710
|
+
if (Array.isArray(pathParams)) {
|
|
711
|
+
const paramsPointer = appendPointer(appendPointer(appendPointer("", "paths"), path), "parameters");
|
|
712
|
+
const out = [];
|
|
713
|
+
for (const [index, p] of pathParams.entries()) {
|
|
714
|
+
if (!isObject(p)) {
|
|
715
|
+
out.push(p);
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
const normalised = normaliseSwaggerParameter(p, doc, diagnostics, appendPointer(paramsPointer, String(index)));
|
|
719
|
+
if (normalised !== void 0) out.push(normalised);
|
|
720
|
+
}
|
|
721
|
+
normalisedPath.parameters = out;
|
|
722
|
+
}
|
|
662
723
|
result[path] = normalisedPath;
|
|
663
724
|
}
|
|
664
725
|
return result;
|
|
665
726
|
}
|
|
666
727
|
function normaliseSwaggerOperation(operation, doc, path, method, diagnostics) {
|
|
667
728
|
const result = {};
|
|
668
|
-
const
|
|
669
|
-
const
|
|
670
|
-
const produces =
|
|
671
|
-
const consumes =
|
|
729
|
+
const consumesResolution = resolveSwaggerContentTypes(operation.consumes, doc.consumes);
|
|
730
|
+
const producesResolution = resolveSwaggerContentTypes(operation.produces, doc.produces);
|
|
731
|
+
const produces = producesResolution.types;
|
|
732
|
+
const consumes = consumesResolution.types;
|
|
672
733
|
for (const [key, value] of Object.entries(operation)) if (key !== "parameters" && key !== "responses" && key !== "produces" && key !== "consumes") result[key] = value;
|
|
673
734
|
const params = operation.parameters;
|
|
674
735
|
if (Array.isArray(params)) {
|
|
@@ -681,7 +742,17 @@ function normaliseSwaggerOperation(operation, doc, path, method, diagnostics) {
|
|
|
681
742
|
nonBodyParams.push(param);
|
|
682
743
|
continue;
|
|
683
744
|
}
|
|
684
|
-
const
|
|
745
|
+
const paramResolution = resolveSwaggerParameter(param, doc);
|
|
746
|
+
if (paramResolution.kind === "cycle") {
|
|
747
|
+
emitDiagnostic(diagnostics, {
|
|
748
|
+
code: "swagger-cyclic-parameter-ref",
|
|
749
|
+
message: `Cyclic Swagger 2.0 parameter $ref "${paramResolution.ref}"; skipping entry`,
|
|
750
|
+
pointer: appendPointer(appendPointer(appendPointer(appendPointer(appendPointer("", "paths"), path), method), "parameters"), String(index)),
|
|
751
|
+
detail: { ref: paramResolution.ref }
|
|
752
|
+
});
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
const resolvedParam = paramResolution.param;
|
|
685
756
|
const location = resolvedParam.in;
|
|
686
757
|
if (location === "body") {
|
|
687
758
|
if (bodyParam !== void 0) {
|
|
@@ -705,16 +776,51 @@ function normaliseSwaggerOperation(operation, doc, path, method, diagnostics) {
|
|
|
705
776
|
bodyParam = buildFormDataBody(resolvedParam, params);
|
|
706
777
|
usesFormData = true;
|
|
707
778
|
}
|
|
708
|
-
} else
|
|
779
|
+
} else {
|
|
780
|
+
const normalised = normaliseSwaggerParameter(resolvedParam, doc, diagnostics, appendPointer(appendPointer(appendPointer(appendPointer(appendPointer("", "paths"), path), method), "parameters"), String(index)));
|
|
781
|
+
if (normalised !== void 0) nonBodyParams.push(normalised);
|
|
782
|
+
}
|
|
709
783
|
}
|
|
710
784
|
if (nonBodyParams.length > 0) result.parameters = nonBodyParams;
|
|
711
|
-
if (bodyParam !== void 0)
|
|
785
|
+
if (bodyParam !== void 0) {
|
|
786
|
+
const bodyContentTypes = usesFormData ? formDataContentTypes(consumes) : consumes;
|
|
787
|
+
if (!usesFormData && consumesResolution.source === "synthesised") emitDiagnostic(diagnostics, {
|
|
788
|
+
code: "swagger-missing-consumes",
|
|
789
|
+
message: "Operation declares a body parameter but neither operation-level nor document-level `consumes` is set; defaulting to application/json",
|
|
790
|
+
pointer: appendPointer(appendPointer(appendPointer("", "paths"), path), method),
|
|
791
|
+
detail: {
|
|
792
|
+
level: "operation",
|
|
793
|
+
method
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
result.requestBody = buildRequestBody(bodyParam, bodyContentTypes);
|
|
797
|
+
}
|
|
712
798
|
}
|
|
713
799
|
const responses = operation.responses;
|
|
714
|
-
if (isObject(responses)) result.responses = normaliseSwaggerResponses(responses, doc, produces);
|
|
800
|
+
if (isObject(responses)) result.responses = normaliseSwaggerResponses(responses, doc, produces, producesResolution.source, diagnostics, path, method);
|
|
715
801
|
return result;
|
|
716
802
|
}
|
|
717
803
|
/**
|
|
804
|
+
* Resolve a Swagger 2.0 `consumes` or `produces` array, recording
|
|
805
|
+
* where the value came from so callers can decide whether to emit a
|
|
806
|
+
* "missing content type" diagnostic. Per the Swagger 2.0 spec, absence
|
|
807
|
+
* at BOTH levels means no body — not an implicit `application/json`.
|
|
808
|
+
*/
|
|
809
|
+
function resolveSwaggerContentTypes(operationLevel, documentLevel) {
|
|
810
|
+
if (Array.isArray(operationLevel)) return {
|
|
811
|
+
types: operationLevel,
|
|
812
|
+
source: "operation"
|
|
813
|
+
};
|
|
814
|
+
if (Array.isArray(documentLevel)) return {
|
|
815
|
+
types: documentLevel,
|
|
816
|
+
source: "document"
|
|
817
|
+
};
|
|
818
|
+
return {
|
|
819
|
+
types: ["application/json"],
|
|
820
|
+
source: "synthesised"
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
718
824
|
* Determine the request body media type for a Swagger 2.0 formData operation.
|
|
719
825
|
*
|
|
720
826
|
* Per the OAS 3 conversion rules, `application/x-www-form-urlencoded` is
|
|
@@ -729,12 +835,77 @@ function formDataContentTypes(consumes) {
|
|
|
729
835
|
return ["multipart/form-data"];
|
|
730
836
|
}
|
|
731
837
|
/**
|
|
732
|
-
*
|
|
838
|
+
* Every JSON-Schema-compatible constraint keyword Swagger 2.0 allows on
|
|
839
|
+
* a Parameter Object or Header Object alongside `type`/`format`. These
|
|
840
|
+
* lift into the synthesised `schema` so consumers see the original
|
|
841
|
+
* validation semantics under OAS 3.x's parameter shape.
|
|
842
|
+
*
|
|
843
|
+
* `allowEmptyValue` is included even though it is a Swagger 2.0
|
|
844
|
+
* parameter-level keyword in the source (not a schema keyword) — OAS
|
|
845
|
+
* 3.x defines it at the Parameter Object root, so the calling function
|
|
846
|
+
* keeps it at the parameter root rather than copying it into `schema`.
|
|
847
|
+
*/
|
|
848
|
+
const SWAGGER_PARAM_SCHEMA_KEYWORDS = [
|
|
849
|
+
"enum",
|
|
850
|
+
"default",
|
|
851
|
+
"minimum",
|
|
852
|
+
"maximum",
|
|
853
|
+
"exclusiveMinimum",
|
|
854
|
+
"exclusiveMaximum",
|
|
855
|
+
"multipleOf",
|
|
856
|
+
"minLength",
|
|
857
|
+
"maxLength",
|
|
858
|
+
"pattern",
|
|
859
|
+
"minItems",
|
|
860
|
+
"maxItems",
|
|
861
|
+
"uniqueItems"
|
|
862
|
+
];
|
|
863
|
+
/**
|
|
864
|
+
* Set of every Swagger 2.0 parameter-root keyword that must be lifted
|
|
865
|
+
* into the synthesised `schema` rather than copied onto the OAS 3.x
|
|
866
|
+
* parameter root. Includes `type`, `format`, `items` (Swagger 2.0
|
|
867
|
+
* parameter-shaped array element descriptor), `collectionFormat`
|
|
868
|
+
* (handled separately by the caller as `style`/`explode`), and every
|
|
869
|
+
* entry from {@link SWAGGER_PARAM_SCHEMA_KEYWORDS}.
|
|
870
|
+
*/
|
|
871
|
+
const PARAM_KEYWORDS_LIFTED_INTO_SCHEMA = new Set([
|
|
872
|
+
"type",
|
|
873
|
+
"format",
|
|
874
|
+
"items",
|
|
875
|
+
"collectionFormat",
|
|
876
|
+
...SWAGGER_PARAM_SCHEMA_KEYWORDS
|
|
877
|
+
]);
|
|
878
|
+
/**
|
|
879
|
+
* Synthesise an OpenAPI 3.x `schema` object from a Swagger 2.0
|
|
880
|
+
* parameter-shaped node (parameter or header). Copies `type`,
|
|
881
|
+
* `format`, and every JSON-Schema-compatible constraint that Swagger
|
|
882
|
+
* 2.0 places at the parameter root. Nested `items` is recursively
|
|
883
|
+
* synthesised the same way so array element constraints survive.
|
|
884
|
+
*/
|
|
885
|
+
function buildSchemaFromSwaggerParameterShape(node) {
|
|
886
|
+
const schema = { type: node.type };
|
|
887
|
+
if (typeof node.format === "string") schema.format = node.format;
|
|
888
|
+
for (const keyword of SWAGGER_PARAM_SCHEMA_KEYWORDS) if (node[keyword] !== void 0) schema[keyword] = node[keyword];
|
|
889
|
+
if (isObject(node.items)) schema.items = buildSchemaFromSwaggerParameterShape(node.items);
|
|
890
|
+
return schema;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Resolve a Swagger parameter that may be a `$ref`. Returns the
|
|
894
|
+
* resolved parameter object, or a cycle marker so the caller can
|
|
895
|
+
* decide how to surface the failure. Non-ref parameters resolve to
|
|
896
|
+
* themselves; ref targets that don't exist also resolve to the input
|
|
897
|
+
* (the caller treats unknown refs the same as bare parameters).
|
|
733
898
|
*/
|
|
734
899
|
function resolveSwaggerParameter(param, doc, visited = /* @__PURE__ */ new Set()) {
|
|
735
900
|
const ref = param.$ref;
|
|
736
|
-
if (typeof ref !== "string" || !ref.startsWith("#/parameters/")) return
|
|
737
|
-
|
|
901
|
+
if (typeof ref !== "string" || !ref.startsWith("#/parameters/")) return {
|
|
902
|
+
kind: "ok",
|
|
903
|
+
param
|
|
904
|
+
};
|
|
905
|
+
if (visited.has(ref)) return {
|
|
906
|
+
kind: "cycle",
|
|
907
|
+
ref
|
|
908
|
+
};
|
|
738
909
|
const nextVisited = new Set(visited);
|
|
739
910
|
nextVisited.add(ref);
|
|
740
911
|
const name = ref.slice(13);
|
|
@@ -743,33 +914,55 @@ function resolveSwaggerParameter(param, doc, visited = /* @__PURE__ */ new Set()
|
|
|
743
914
|
const resolved = globalParams[name];
|
|
744
915
|
if (isObject(resolved)) {
|
|
745
916
|
if (typeof resolved.$ref === "string") return resolveSwaggerParameter(resolved, doc, nextVisited);
|
|
746
|
-
return
|
|
917
|
+
return {
|
|
918
|
+
kind: "ok",
|
|
919
|
+
param: resolved
|
|
920
|
+
};
|
|
747
921
|
}
|
|
748
922
|
}
|
|
749
|
-
return
|
|
923
|
+
return {
|
|
924
|
+
kind: "ok",
|
|
925
|
+
param
|
|
926
|
+
};
|
|
750
927
|
}
|
|
751
928
|
/**
|
|
752
929
|
* Normalise a single Swagger parameter to OpenAPI 3.x form.
|
|
753
930
|
*/
|
|
754
|
-
function normaliseSwaggerParameter(param, doc) {
|
|
931
|
+
function normaliseSwaggerParameter(param, doc, diagnostics, pointer = "") {
|
|
755
932
|
if (typeof param.$ref === "string") {
|
|
756
|
-
const
|
|
757
|
-
if (
|
|
933
|
+
const resolution = resolveSwaggerParameter(param, doc);
|
|
934
|
+
if (resolution.kind === "cycle") {
|
|
935
|
+
emitDiagnostic(diagnostics, {
|
|
936
|
+
code: "swagger-cyclic-parameter-ref",
|
|
937
|
+
message: `Cyclic Swagger 2.0 parameter $ref "${resolution.ref}"; skipping entry`,
|
|
938
|
+
pointer,
|
|
939
|
+
detail: { ref: resolution.ref }
|
|
940
|
+
});
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const resolved = resolution.param;
|
|
944
|
+
if (resolved !== param) return normaliseSwaggerParameter(resolved, doc, diagnostics, pointer);
|
|
758
945
|
}
|
|
759
946
|
const result = {};
|
|
760
947
|
for (const [key, value] of Object.entries(param)) {
|
|
761
|
-
if (key
|
|
948
|
+
if (PARAM_KEYWORDS_LIFTED_INTO_SCHEMA.has(key)) continue;
|
|
762
949
|
result[key] = value;
|
|
763
950
|
}
|
|
764
|
-
if (typeof param.type === "string") {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
951
|
+
if (typeof param.type === "string") if (param.type === "file" && param.in !== "formData") {
|
|
952
|
+
emitDiagnostic(diagnostics, {
|
|
953
|
+
code: "swagger-invalid-file-parameter",
|
|
954
|
+
message: `Swagger 2.0 type: "file" is only valid under in: formData; converting to { type: "string", format: "binary" }`,
|
|
955
|
+
pointer,
|
|
956
|
+
detail: {
|
|
957
|
+
name: param.name,
|
|
958
|
+
in: param.in
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
result.schema = {
|
|
962
|
+
type: "string",
|
|
963
|
+
format: "binary"
|
|
964
|
+
};
|
|
965
|
+
} else result.schema = buildSchemaFromSwaggerParameterShape(param);
|
|
773
966
|
const cf = param.collectionFormat;
|
|
774
967
|
if (typeof cf === "string") switch (cf) {
|
|
775
968
|
case "csv":
|
|
@@ -860,14 +1053,14 @@ function resolveSwaggerResponse(response, doc, visited = /* @__PURE__ */ new Set
|
|
|
860
1053
|
}
|
|
861
1054
|
return response;
|
|
862
1055
|
}
|
|
863
|
-
function normaliseSwaggerResponses(responses, doc, produces) {
|
|
1056
|
+
function normaliseSwaggerResponses(responses, doc, produces, producesSource, diagnostics, path, method) {
|
|
864
1057
|
const result = {};
|
|
865
1058
|
for (const [code, response] of Object.entries(responses)) {
|
|
866
1059
|
if (!isObject(response)) {
|
|
867
1060
|
result[code] = response;
|
|
868
1061
|
continue;
|
|
869
1062
|
}
|
|
870
|
-
result[code] = normaliseSwaggerSingleResponse(response, doc, produces);
|
|
1063
|
+
result[code] = normaliseSwaggerSingleResponse(response, doc, produces, producesSource, diagnostics, path, method, code);
|
|
871
1064
|
}
|
|
872
1065
|
return result;
|
|
873
1066
|
}
|
|
@@ -880,7 +1073,7 @@ function normaliseSwaggerResponses(responses, doc, produces) {
|
|
|
880
1073
|
* operation’s `responses` map or under document-level `responses`
|
|
881
1074
|
* (now `components.responses`).
|
|
882
1075
|
*/
|
|
883
|
-
function normaliseSwaggerSingleResponse(response, doc, produces) {
|
|
1076
|
+
function normaliseSwaggerSingleResponse(response, doc, produces, producesSource = "synthesised", diagnostics, path, method, statusCode) {
|
|
884
1077
|
const resolved = resolveSwaggerResponse(response, doc);
|
|
885
1078
|
const normalised = {};
|
|
886
1079
|
for (const [key, value] of Object.entries(resolved)) if (key !== "schema" && key !== "headers") normalised[key] = value;
|
|
@@ -890,6 +1083,15 @@ function normaliseSwaggerSingleResponse(response, doc, produces) {
|
|
|
890
1083
|
const contentTypes = produces.length > 0 ? produces : ["application/json"];
|
|
891
1084
|
for (const ct of contentTypes) if (typeof ct === "string") content[ct] = { schema };
|
|
892
1085
|
normalised.content = content;
|
|
1086
|
+
if (producesSource === "synthesised") emitDiagnostic(diagnostics, {
|
|
1087
|
+
code: "swagger-missing-consumes",
|
|
1088
|
+
message: "Response declares a schema but neither operation-level nor document-level `produces` is set; defaulting to application/json",
|
|
1089
|
+
pointer: path !== void 0 && method !== void 0 && statusCode !== void 0 ? appendPointer(appendPointer(appendPointer(appendPointer(appendPointer("", "paths"), path), method), "responses"), statusCode) : "",
|
|
1090
|
+
detail: {
|
|
1091
|
+
level: "response",
|
|
1092
|
+
statusCode
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
893
1095
|
}
|
|
894
1096
|
const headers = resolved.headers;
|
|
895
1097
|
if (isObject(headers)) {
|
|
@@ -915,18 +1117,10 @@ function normaliseSwaggerSingleResponse(response, doc, produces) {
|
|
|
915
1117
|
function normaliseSwaggerHeader(header) {
|
|
916
1118
|
const result = {};
|
|
917
1119
|
for (const [key, value] of Object.entries(header)) {
|
|
918
|
-
if (key
|
|
1120
|
+
if (PARAM_KEYWORDS_LIFTED_INTO_SCHEMA.has(key)) continue;
|
|
919
1121
|
result[key] = value;
|
|
920
1122
|
}
|
|
921
|
-
if (typeof header.type === "string")
|
|
922
|
-
const schema = { type: header.type };
|
|
923
|
-
if (typeof header.format === "string") schema.format = header.format;
|
|
924
|
-
if (header.enum !== void 0) schema.enum = header.enum;
|
|
925
|
-
if (header.default !== void 0) schema.default = header.default;
|
|
926
|
-
if (header.minimum !== void 0) schema.minimum = header.minimum;
|
|
927
|
-
if (header.maximum !== void 0) schema.maximum = header.maximum;
|
|
928
|
-
result.schema = schema;
|
|
929
|
-
}
|
|
1123
|
+
if (typeof header.type === "string") result.schema = buildSchemaFromSwaggerParameterShape(header);
|
|
930
1124
|
const cf = header.collectionFormat;
|
|
931
1125
|
if (typeof cf === "string") switch (cf) {
|
|
932
1126
|
case "csv":
|
|
@@ -976,6 +1170,64 @@ function rewriteSwaggerRefs(node) {
|
|
|
976
1170
|
else if (Array.isArray(value)) for (const item of value) rewriteSwaggerRefs(item);
|
|
977
1171
|
}
|
|
978
1172
|
/**
|
|
1173
|
+
* Map from Swagger 2.0 `oauth2.flow` (singular) to the OAS 3.x flow key
|
|
1174
|
+
* under `flows.<key>`. `application` and `accessCode` were renamed in
|
|
1175
|
+
* OAS 3.x to align with RFC 6749 grant-type names.
|
|
1176
|
+
*/
|
|
1177
|
+
const SWAGGER_OAUTH_FLOW_RENAME = {
|
|
1178
|
+
implicit: "implicit",
|
|
1179
|
+
password: "password",
|
|
1180
|
+
application: "clientCredentials",
|
|
1181
|
+
accessCode: "authorizationCode"
|
|
1182
|
+
};
|
|
1183
|
+
/**
|
|
1184
|
+
* Translate a Swagger 2.0 Security Scheme Object into an OpenAPI 3.x
|
|
1185
|
+
* Security Scheme Object. The Swagger 2.0 spec defines three types:
|
|
1186
|
+
*
|
|
1187
|
+
* - `basic` — has no other fields; OAS 3.x represents this as
|
|
1188
|
+
* `{ type: "http", scheme: "basic" }`.
|
|
1189
|
+
* - `apiKey` — carries `name`/`in`; OAS 3.x uses the same shape.
|
|
1190
|
+
* - `oauth2` — carries `flow`/`authorizationUrl`/`tokenUrl`/`scopes` at
|
|
1191
|
+
* the root. OAS 3.x nests these under `flows.<name>` where the flow
|
|
1192
|
+
* name maps via {@link SWAGGER_OAUTH_FLOW_RENAME}.
|
|
1193
|
+
*
|
|
1194
|
+
* Unknown `type` values pass through verbatim — downstream validation
|
|
1195
|
+
* (`unknown-security-scheme-type` diagnostic in the parser) handles
|
|
1196
|
+
* those cases.
|
|
1197
|
+
*/
|
|
1198
|
+
function translateSwaggerSecurityScheme(scheme) {
|
|
1199
|
+
const type = scheme.type;
|
|
1200
|
+
if (type === "basic") {
|
|
1201
|
+
const result = {
|
|
1202
|
+
type: "http",
|
|
1203
|
+
scheme: "basic"
|
|
1204
|
+
};
|
|
1205
|
+
if (typeof scheme.description === "string") result.description = scheme.description;
|
|
1206
|
+
return result;
|
|
1207
|
+
}
|
|
1208
|
+
if (type === "oauth2") {
|
|
1209
|
+
const flowName = scheme.flow;
|
|
1210
|
+
if (typeof flowName !== "string") return {
|
|
1211
|
+
...scheme,
|
|
1212
|
+
type: "oauth2"
|
|
1213
|
+
};
|
|
1214
|
+
const renamedFlow = SWAGGER_OAUTH_FLOW_RENAME[flowName] ?? flowName;
|
|
1215
|
+
const flowBody = {};
|
|
1216
|
+
if (typeof scheme.authorizationUrl === "string") flowBody.authorizationUrl = scheme.authorizationUrl;
|
|
1217
|
+
if (typeof scheme.tokenUrl === "string") flowBody.tokenUrl = scheme.tokenUrl;
|
|
1218
|
+
if (typeof scheme.refreshUrl === "string") flowBody.refreshUrl = scheme.refreshUrl;
|
|
1219
|
+
const scopes = scheme.scopes;
|
|
1220
|
+
flowBody.scopes = isObject(scopes) ? { ...scopes } : {};
|
|
1221
|
+
const result = {
|
|
1222
|
+
type: "oauth2",
|
|
1223
|
+
flows: { [renamedFlow]: flowBody }
|
|
1224
|
+
};
|
|
1225
|
+
if (typeof scheme.description === "string") result.description = scheme.description;
|
|
1226
|
+
return result;
|
|
1227
|
+
}
|
|
1228
|
+
return { ...scheme };
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
979
1231
|
* Recursively check whether any node in the supplied subtree carries an
|
|
980
1232
|
* `xml` annotation. Walks both objects and arrays so the check works for
|
|
981
1233
|
* schemas (definitions, parameter schemas, response schemas, request body
|
|
@@ -1075,24 +1327,34 @@ function deepNormalise(schema, transform, visited = /* @__PURE__ */ new WeakSet(
|
|
|
1075
1327
|
} else result[key] = value;
|
|
1076
1328
|
return result;
|
|
1077
1329
|
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Construct a child {@link NodeContext} that descends to `segment`,
|
|
1332
|
+
* preserving document-level flags (`documentHasDynamicAnchor`,
|
|
1333
|
+
* `documentHasRecursiveAnchor`, `declaredDraft`). Centralising the copy
|
|
1334
|
+
* keeps the recursion in `deepNormaliseWithContext` from drifting from
|
|
1335
|
+
* the {@link NodeContext} shape.
|
|
1336
|
+
*/
|
|
1337
|
+
function childContext(ctx, segment) {
|
|
1338
|
+
return {
|
|
1339
|
+
diagnostics: ctx.diagnostics,
|
|
1340
|
+
pointer: appendPointer(ctx.pointer, segment),
|
|
1341
|
+
documentHasDynamicAnchor: ctx.documentHasDynamicAnchor,
|
|
1342
|
+
documentHasRecursiveAnchor: ctx.documentHasRecursiveAnchor,
|
|
1343
|
+
declaredDraft: ctx.declaredDraft
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1078
1346
|
function normaliseArrayWithContext(items, transform, ctx) {
|
|
1079
1347
|
const result = [];
|
|
1080
1348
|
for (let i = 0; i < items.length; i++) {
|
|
1081
1349
|
const item = items[i];
|
|
1082
|
-
if (isObject(item)) result.push(deepNormaliseWithContext(item, transform,
|
|
1083
|
-
diagnostics: ctx.diagnostics,
|
|
1084
|
-
pointer: appendPointer(ctx.pointer, String(i))
|
|
1085
|
-
}));
|
|
1350
|
+
if (isObject(item)) result.push(deepNormaliseWithContext(item, transform, childContext(ctx, String(i))));
|
|
1086
1351
|
else result.push(item);
|
|
1087
1352
|
}
|
|
1088
1353
|
return result;
|
|
1089
1354
|
}
|
|
1090
1355
|
function normaliseSubSchemaMapWithContext(map, transform, ctx) {
|
|
1091
1356
|
const result = {};
|
|
1092
|
-
for (const [k, v] of Object.entries(map)) if (isObject(v)) result[k] = deepNormaliseWithContext(v, transform,
|
|
1093
|
-
diagnostics: ctx.diagnostics,
|
|
1094
|
-
pointer: appendPointer(ctx.pointer, k)
|
|
1095
|
-
});
|
|
1357
|
+
for (const [k, v] of Object.entries(map)) if (isObject(v)) result[k] = deepNormaliseWithContext(v, transform, childContext(ctx, k));
|
|
1096
1358
|
else result[k] = v;
|
|
1097
1359
|
return result;
|
|
1098
1360
|
}
|
|
@@ -1108,34 +1370,16 @@ function normaliseSubSchemaMapWithContext(map, transform, ctx) {
|
|
|
1108
1370
|
function deepNormaliseWithContext(schema, transform, ctx) {
|
|
1109
1371
|
const node = transform({ ...schema }, ctx);
|
|
1110
1372
|
const result = {};
|
|
1111
|
-
for (const [key, value] of Object.entries(node)) if (isObject(value) && OBJECT_SUBSCHEMA_KEYS.has(key)) result[key] = normaliseSubSchemaMapWithContext(value, transform,
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
else if (
|
|
1116
|
-
diagnostics: ctx.diagnostics,
|
|
1117
|
-
pointer: appendPointer(ctx.pointer, key)
|
|
1118
|
-
});
|
|
1119
|
-
else if (isObject(value) && SINGLE_SUBSCHEMA_KEYS.has(key)) result[key] = deepNormaliseWithContext(value, transform, {
|
|
1120
|
-
diagnostics: ctx.diagnostics,
|
|
1121
|
-
pointer: appendPointer(ctx.pointer, key)
|
|
1122
|
-
});
|
|
1123
|
-
else if (key === "items") if (Array.isArray(value)) result[key] = normaliseArrayWithContext(value, transform, {
|
|
1124
|
-
diagnostics: ctx.diagnostics,
|
|
1125
|
-
pointer: appendPointer(ctx.pointer, key)
|
|
1126
|
-
});
|
|
1127
|
-
else if (isObject(value)) result[key] = deepNormaliseWithContext(value, transform, {
|
|
1128
|
-
diagnostics: ctx.diagnostics,
|
|
1129
|
-
pointer: appendPointer(ctx.pointer, key)
|
|
1130
|
-
});
|
|
1373
|
+
for (const [key, value] of Object.entries(node)) if (isObject(value) && OBJECT_SUBSCHEMA_KEYS.has(key)) result[key] = normaliseSubSchemaMapWithContext(value, transform, childContext(ctx, key));
|
|
1374
|
+
else if (Array.isArray(value) && ARRAY_SUBSCHEMA_KEYS.has(key)) result[key] = normaliseArrayWithContext(value, transform, childContext(ctx, key));
|
|
1375
|
+
else if (isObject(value) && SINGLE_SUBSCHEMA_KEYS.has(key)) result[key] = deepNormaliseWithContext(value, transform, childContext(ctx, key));
|
|
1376
|
+
else if (key === "items") if (Array.isArray(value)) result[key] = normaliseArrayWithContext(value, transform, childContext(ctx, key));
|
|
1377
|
+
else if (isObject(value)) result[key] = deepNormaliseWithContext(value, transform, childContext(ctx, key));
|
|
1131
1378
|
else result[key] = value;
|
|
1132
1379
|
else if (key === "dependencies" && isObject(value)) {
|
|
1133
1380
|
const normalised = {};
|
|
1134
|
-
const
|
|
1135
|
-
for (const [dk, dv] of Object.entries(value)) if (isObject(dv)) normalised[dk] = deepNormaliseWithContext(dv, transform,
|
|
1136
|
-
diagnostics: ctx.diagnostics,
|
|
1137
|
-
pointer: appendPointer(depsPointer, dk)
|
|
1138
|
-
});
|
|
1381
|
+
const depsContext = childContext(ctx, key);
|
|
1382
|
+
for (const [dk, dv] of Object.entries(value)) if (isObject(dv)) normalised[dk] = deepNormaliseWithContext(dv, transform, childContext(depsContext, dk));
|
|
1139
1383
|
else normalised[dk] = dv;
|
|
1140
1384
|
result[key] = normalised;
|
|
1141
1385
|
} else result[key] = value;
|
|
@@ -1391,8 +1635,18 @@ function normaliseDraft06Or07NodeWithContext(node, ctx) {
|
|
|
1391
1635
|
* `$recursiveAnchor` names are likewise preserved as `$anchor`.
|
|
1392
1636
|
*/
|
|
1393
1637
|
function normaliseDraft201909NodeWithContext(node, ctx) {
|
|
1394
|
-
|
|
1395
|
-
|
|
1638
|
+
const recursiveRef = node.$recursiveRef;
|
|
1639
|
+
if (typeof recursiveRef === "string") {
|
|
1640
|
+
if (!recursiveRef.startsWith("#")) emitDiagnostic(ctx.diagnostics, {
|
|
1641
|
+
code: "dynamic-ref-degraded",
|
|
1642
|
+
message: `Cross-document \`$recursiveRef\` "${recursiveRef}" rewritten to a static \`$ref\`; dynamic-scope resolution is not preserved`,
|
|
1643
|
+
pointer: appendPointer(ctx.pointer, "$recursiveRef"),
|
|
1644
|
+
detail: {
|
|
1645
|
+
keyword: "$recursiveRef",
|
|
1646
|
+
ref: recursiveRef
|
|
1647
|
+
}
|
|
1648
|
+
});
|
|
1649
|
+
node.$ref = recursiveRef;
|
|
1396
1650
|
delete node.$recursiveRef;
|
|
1397
1651
|
}
|
|
1398
1652
|
if (node.$recursiveAnchor === true) {
|
|
@@ -1402,12 +1656,24 @@ function normaliseDraft201909NodeWithContext(node, ctx) {
|
|
|
1402
1656
|
if (typeof node.$anchor !== "string") node.$anchor = node.$recursiveAnchor;
|
|
1403
1657
|
delete node.$recursiveAnchor;
|
|
1404
1658
|
}
|
|
1659
|
+
splitDependencies(node, ctx, false);
|
|
1405
1660
|
validateDependentRequired(node, ctx);
|
|
1406
1661
|
return node;
|
|
1407
1662
|
}
|
|
1408
1663
|
function normaliseDynamicRefNodeWithContext(node, ctx) {
|
|
1409
|
-
|
|
1410
|
-
|
|
1664
|
+
const dynamicRef = node.$dynamicRef;
|
|
1665
|
+
if (typeof dynamicRef === "string") {
|
|
1666
|
+
const crossDocument = !dynamicRef.startsWith("#");
|
|
1667
|
+
if (crossDocument || ctx.documentHasDynamicAnchor) emitDiagnostic(ctx.diagnostics, {
|
|
1668
|
+
code: "dynamic-ref-degraded",
|
|
1669
|
+
message: crossDocument ? `Cross-document \`$dynamicRef\` "${dynamicRef}" rewritten to a static \`$ref\`; dynamic-scope resolution is not preserved` : `\`$dynamicRef\` "${dynamicRef}" rewritten to a static \`$ref\` in a document declaring \`$dynamicAnchor\`; dynamic-scope resolution is not preserved`,
|
|
1670
|
+
pointer: appendPointer(ctx.pointer, "$dynamicRef"),
|
|
1671
|
+
detail: {
|
|
1672
|
+
keyword: "$dynamicRef",
|
|
1673
|
+
ref: dynamicRef
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
node.$ref = dynamicRef;
|
|
1411
1677
|
delete node.$dynamicRef;
|
|
1412
1678
|
}
|
|
1413
1679
|
if (typeof node.$dynamicAnchor === "string") {
|
|
@@ -1419,6 +1685,63 @@ function normaliseDynamicRefNodeWithContext(node, ctx) {
|
|
|
1419
1685
|
return node;
|
|
1420
1686
|
}
|
|
1421
1687
|
/**
|
|
1688
|
+
* Pick the per-node transform that normalises a single Schema Object to
|
|
1689
|
+
* canonical Draft 2020-12 form for the supplied draft. Exposed so the
|
|
1690
|
+
* OpenAPI 3.1 path can honour a non-default `jsonSchemaDialect`
|
|
1691
|
+
* declaration by routing each Schema Object through the matching
|
|
1692
|
+
* transform without re-implementing the dispatch.
|
|
1693
|
+
*/
|
|
1694
|
+
function selectDraftTransform(draft) {
|
|
1695
|
+
switch (draft) {
|
|
1696
|
+
case "draft-04": return normaliseDraft04NodeWithContext;
|
|
1697
|
+
case "draft-06":
|
|
1698
|
+
case "draft-07": return normaliseDraft06Or07NodeWithContext;
|
|
1699
|
+
case "draft-2019-09": return normaliseDraft201909NodeWithContext;
|
|
1700
|
+
case "draft-2020-12": return normaliseDynamicRefNodeWithContext;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Scan a JSON document body for the presence of a named keyword
|
|
1705
|
+
* anywhere in the structure. Walks both arrays and objects without
|
|
1706
|
+
* regard to schema-vs-data position — the caller is responsible for
|
|
1707
|
+
* passing a keyword whose presence is meaningful at any depth.
|
|
1708
|
+
*
|
|
1709
|
+
* Cycle-safe: cyclic references introduced by the OpenAPI bundler's
|
|
1710
|
+
* `structuredClone` of external refs are short-circuited via the
|
|
1711
|
+
* `visited` set so the scan terminates.
|
|
1712
|
+
*/
|
|
1713
|
+
function documentContainsKeyword(value, keyword, visited = /* @__PURE__ */ new WeakSet()) {
|
|
1714
|
+
if (Array.isArray(value)) {
|
|
1715
|
+
if (visited.has(value)) return false;
|
|
1716
|
+
visited.add(value);
|
|
1717
|
+
for (const item of value) if (documentContainsKeyword(item, keyword, visited)) return true;
|
|
1718
|
+
return false;
|
|
1719
|
+
}
|
|
1720
|
+
if (isObject(value)) {
|
|
1721
|
+
if (visited.has(value)) return false;
|
|
1722
|
+
visited.add(value);
|
|
1723
|
+
if (keyword in value) return true;
|
|
1724
|
+
for (const v of Object.values(value)) if (documentContainsKeyword(v, keyword, visited)) return true;
|
|
1725
|
+
return false;
|
|
1726
|
+
}
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* Build a root {@link NodeContext} for a document being normalised.
|
|
1731
|
+
* Pre-scans for `$dynamicAnchor` and `$recursiveAnchor` so per-node
|
|
1732
|
+
* transforms can decide whether a `$dynamicRef`/`$recursiveRef`
|
|
1733
|
+
* rewrite needs a `dynamic-ref-degraded` diagnostic.
|
|
1734
|
+
*/
|
|
1735
|
+
function buildRootContext(schema, diagnostics, declaredDraft) {
|
|
1736
|
+
return {
|
|
1737
|
+
diagnostics,
|
|
1738
|
+
pointer: "",
|
|
1739
|
+
documentHasDynamicAnchor: documentContainsKeyword(schema, "$dynamicAnchor"),
|
|
1740
|
+
documentHasRecursiveAnchor: documentContainsKeyword(schema, "$recursiveAnchor"),
|
|
1741
|
+
declaredDraft
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1422
1745
|
* Normalise a JSON Schema to canonical Draft 2020-12 form.
|
|
1423
1746
|
* Deep-clones the input — the original is never mutated.
|
|
1424
1747
|
*
|
|
@@ -1428,27 +1751,8 @@ function normaliseDynamicRefNodeWithContext(node, ctx) {
|
|
|
1428
1751
|
* `dependencies` reaching the 2020-12 path).
|
|
1429
1752
|
*/
|
|
1430
1753
|
function normaliseJsonSchema(schema, draft, diagnostics) {
|
|
1431
|
-
const ctx =
|
|
1432
|
-
|
|
1433
|
-
pointer: ""
|
|
1434
|
-
};
|
|
1435
|
-
let normalised;
|
|
1436
|
-
switch (draft) {
|
|
1437
|
-
case "draft-04":
|
|
1438
|
-
normalised = deepNormaliseWithContext(schema, normaliseDraft04NodeWithContext, ctx);
|
|
1439
|
-
break;
|
|
1440
|
-
case "draft-2019-09":
|
|
1441
|
-
normalised = deepNormaliseWithContext(schema, normaliseDraft201909NodeWithContext, ctx);
|
|
1442
|
-
break;
|
|
1443
|
-
case "draft-2020-12":
|
|
1444
|
-
normalised = deepNormaliseWithContext(schema, normaliseDynamicRefNodeWithContext, ctx);
|
|
1445
|
-
break;
|
|
1446
|
-
case "draft-06":
|
|
1447
|
-
case "draft-07":
|
|
1448
|
-
normalised = deepNormaliseWithContext(schema, normaliseDraft06Or07NodeWithContext, ctx);
|
|
1449
|
-
break;
|
|
1450
|
-
}
|
|
1451
|
-
return resolveRelativeRefs(normalised, diagnostics);
|
|
1754
|
+
const ctx = buildRootContext(schema, diagnostics, draft);
|
|
1755
|
+
return resolveRelativeRefs(deepNormaliseWithContext(schema, selectDraftTransform(draft), ctx), diagnostics);
|
|
1452
1756
|
}
|
|
1453
1757
|
/**
|
|
1454
1758
|
* Parse a string as an absolute URI, returning `undefined` when it has
|
|
@@ -1486,6 +1790,29 @@ function stripFragment(url) {
|
|
|
1486
1790
|
return clone.toString();
|
|
1487
1791
|
}
|
|
1488
1792
|
/**
|
|
1793
|
+
* Emit an `invalid-id-fragment` diagnostic when an `$id` value carries
|
|
1794
|
+
* a fragment that will be stripped during base-URI resolution. Per
|
|
1795
|
+
* JSON Schema 2020-12 §8.2.1, the URI in `$id` MUST NOT contain a
|
|
1796
|
+
* non-empty fragment (an empty `#` fragment is permitted for historical
|
|
1797
|
+
* reasons but conveys nothing). Stripping it silently loses authoring
|
|
1798
|
+
* intent — the caller almost certainly meant to declare an `$anchor`
|
|
1799
|
+
* or sibling identifier instead.
|
|
1800
|
+
*/
|
|
1801
|
+
function reportFragmentInId(value, url, pointer, diagnostics) {
|
|
1802
|
+
if (diagnostics === void 0) return;
|
|
1803
|
+
if (typeof value !== "string") return;
|
|
1804
|
+
if (url.hash.length === 0) return;
|
|
1805
|
+
emitDiagnostic(diagnostics, {
|
|
1806
|
+
code: "invalid-id-fragment",
|
|
1807
|
+
message: `\`$id\` URI "${value}" includes the fragment "${url.hash}", which is not permitted by JSON Schema §8.2.1; the fragment is stripped before use`,
|
|
1808
|
+
pointer: appendPointer(pointer, "$id"),
|
|
1809
|
+
detail: {
|
|
1810
|
+
id: value,
|
|
1811
|
+
fragment: url.hash
|
|
1812
|
+
}
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
/**
|
|
1489
1816
|
* Recursively rewrite relative `$ref`s in a schema so they resolve
|
|
1490
1817
|
* correctly under the JSON Schema base-URI rules (RFC 3986 + JSON
|
|
1491
1818
|
* Schema §8.2). Refs that resolve to the document's own `$id` are
|
|
@@ -1519,7 +1846,10 @@ function rewriteRelativeRefsNode(node, currentBase, docBase, pointer, diagnostic
|
|
|
1519
1846
|
const nodeId = node.$id;
|
|
1520
1847
|
if (typeof nodeId === "string" && nodeId.length > 0) {
|
|
1521
1848
|
const resolved = resolveAgainst(nodeId, currentBase);
|
|
1522
|
-
if (resolved !== void 0)
|
|
1849
|
+
if (resolved !== void 0) {
|
|
1850
|
+
reportFragmentInId(nodeId, resolved, pointer, diagnostics);
|
|
1851
|
+
nextBase = stripFragment(resolved);
|
|
1852
|
+
}
|
|
1523
1853
|
}
|
|
1524
1854
|
const result = {};
|
|
1525
1855
|
for (const [key, value] of Object.entries(node)) {
|
|
@@ -1588,6 +1918,24 @@ function rewriteRef(ref, currentBase, docBase, pointer, diagnostics) {
|
|
|
1588
1918
|
function normaliseOpenApiSchemas(doc, version, diagnostics) {
|
|
1589
1919
|
if (isSwagger2(version)) return normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node, diagnostics);
|
|
1590
1920
|
if (isOpenApi30(version)) return deepNormaliseOpenApi30Doc(applyDiscriminatorAllOfPrepass(doc), deepNormalise);
|
|
1921
|
+
if (!isOpenApi31(version)) {
|
|
1922
|
+
const rawOpenApi = typeof doc.openapi === "string" ? doc.openapi : void 0;
|
|
1923
|
+
const rawSwagger = typeof doc.swagger === "string" ? doc.swagger : void 0;
|
|
1924
|
+
const versionLabel = rawOpenApi ?? rawSwagger ?? `${String(version.major)}.${String(version.minor)}.${String(version.patch)}`;
|
|
1925
|
+
const pointer = rawOpenApi !== void 0 ? "/openapi" : "/swagger";
|
|
1926
|
+
emitDiagnostic(diagnostics, {
|
|
1927
|
+
code: "unknown-openapi-version",
|
|
1928
|
+
message: `Unsupported OpenAPI/Swagger version "${versionLabel}"; falling back to the OpenAPI 3.1 pipeline`,
|
|
1929
|
+
pointer,
|
|
1930
|
+
detail: {
|
|
1931
|
+
version: versionLabel,
|
|
1932
|
+
major: version.major,
|
|
1933
|
+
minor: version.minor,
|
|
1934
|
+
patch: version.patch
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
let dialectDraft;
|
|
1591
1939
|
if (isOpenApi31(version)) {
|
|
1592
1940
|
const dialect = readJsonSchemaDialect(doc);
|
|
1593
1941
|
if (dialect.kind === "unknown") emitDiagnostic(diagnostics, {
|
|
@@ -1596,8 +1944,13 @@ function normaliseOpenApiSchemas(doc, version, diagnostics) {
|
|
|
1596
1944
|
pointer: "/jsonSchemaDialect",
|
|
1597
1945
|
detail: { uri: dialect.uri }
|
|
1598
1946
|
});
|
|
1947
|
+
else if (dialect.kind === "known") dialectDraft = dialect.draft;
|
|
1599
1948
|
}
|
|
1600
|
-
return deepNormaliseOpenApiDoc(applyDiscriminatorAllOfPrepass(doc), (schema) =>
|
|
1949
|
+
return deepNormaliseOpenApiDoc(applyDiscriminatorAllOfPrepass(doc), (schema) => {
|
|
1950
|
+
let intermediate = schema;
|
|
1951
|
+
if (dialectDraft !== void 0 && dialectDraft !== "draft-2020-12") intermediate = deepNormaliseWithContext(intermediate, selectDraftTransform(dialectDraft), buildRootContext(intermediate, diagnostics, dialectDraft));
|
|
1952
|
+
return resolveRelativeRefs(deepNormalise(intermediate, normaliseOpenApi30Discriminator), diagnostics);
|
|
1953
|
+
});
|
|
1601
1954
|
}
|
|
1602
1955
|
//#endregion
|
|
1603
|
-
export { normaliseOpenApiSchemas as a,
|
|
1956
|
+
export { normaliseOpenApiSchemas as a, applyDiscriminatorAllOfPrepass as c, normaliseOpenApi30Combined as d, normaliseOpenApi30Discriminator as f, normaliseJsonSchema as i, deepNormaliseOpenApi30Doc as l, deepNormaliseWithContext as n, selectDraftTransform as o, normaliseOpenApi30Node as p, normaliseDraft04Node as r, normaliseSwagger2Document as s, deepNormalise as t, deepNormaliseOpenApiDoc as u };
|