radiant-docs 0.1.64 → 0.1.66
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/index.js +53 -10
- package/package.json +4 -3
- package/template/package-lock.json +1991 -4096
- package/template/package.json +7 -19
- package/template/src/components/OpenApiPage.astro +107 -18
- package/template/src/components/Sidebar.astro +1 -0
- package/template/src/components/endpoint/PlaygroundBar.astro +1 -1
- package/template/src/components/endpoint/PlaygroundField.astro +433 -71
- package/template/src/components/endpoint/PlaygroundForm.astro +470 -87
- package/template/src/components/endpoint/RequestSnippets.astro +6 -1
- package/template/src/components/endpoint/ResponseDisplay.astro +109 -2
- package/template/src/components/endpoint/ResponseFieldTree.astro +1 -1
- package/template/src/components/endpoint/ResponseFields.astro +97 -28
- package/template/src/components/endpoint/ResponseSnippets.astro +19 -7
- package/template/src/layouts/Layout.astro +94 -0
- package/template/src/lib/openapi/operation-doc.ts +308 -70
- package/template/src/lib/openapi/response-content.ts +133 -0
- package/template/src/lib/openapi/schema-variant-labels.ts +213 -0
- package/template/.vscode/extensions.json +0 -4
- package/template/.vscode/launch.json +0 -11
- package/template/scripts/generate-og-images.mjs +0 -667
- package/template/scripts/generate-og-metadata.mjs +0 -206
- package/template/scripts/generate-proxy-allowed-origins.mjs +0 -48
- package/template/scripts/generate-robots-txt.mjs +0 -47
- package/template/scripts/publish-shiki-platform-assets.mjs +0 -1177
- package/template/scripts/remove-assistant-for-non-pro.mjs +0 -28
- package/template/scripts/stamp-image-versions.mjs +0 -355
- package/template/scripts/stamp-og-image-versions.mjs +0 -199
- package/template/scripts/stamp-pagefind-runtime-version.mjs +0 -140
|
@@ -2,6 +2,7 @@ import type { HttpMethods } from "oas/types";
|
|
|
2
2
|
import type { OpenApiRoute } from "../routes";
|
|
3
3
|
import { getOasInstance } from "../oas";
|
|
4
4
|
import { loadOpenApiSpec } from "../validation";
|
|
5
|
+
import { getSchemaVariantLabel } from "./schema-variant-labels";
|
|
5
6
|
|
|
6
7
|
export const OPENAPI_REQUEST_SECTION_KEYS = [
|
|
7
8
|
"header",
|
|
@@ -30,6 +31,11 @@ export interface OpenApiFieldVariant {
|
|
|
30
31
|
fields: OpenApiField[];
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
export interface OpenApiFieldValueVariant {
|
|
35
|
+
label: string;
|
|
36
|
+
field: OpenApiField;
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
export interface OpenApiField {
|
|
34
40
|
name: string;
|
|
35
41
|
required: boolean;
|
|
@@ -50,8 +56,13 @@ export interface OpenApiField {
|
|
|
50
56
|
nested?: OpenApiField[];
|
|
51
57
|
variants?: OpenApiFieldVariant[];
|
|
52
58
|
variantType?: "oneOf" | "anyOf";
|
|
59
|
+
valueVariants?: OpenApiFieldValueVariant[];
|
|
60
|
+
valueVariantType?: "oneOf" | "anyOf";
|
|
53
61
|
isAdditionalProperty?: boolean;
|
|
54
62
|
mapKnownKeys?: string[];
|
|
63
|
+
renderAsJson?: boolean;
|
|
64
|
+
isFile?: boolean;
|
|
65
|
+
valuePrefix?: string;
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
export type OpenApiRequestFields = Record<
|
|
@@ -84,6 +95,7 @@ export interface OpenApiOperationDoc {
|
|
|
84
95
|
>;
|
|
85
96
|
bodyDescription: string;
|
|
86
97
|
bodyDefaultKind?: OpenApiBodyDefaultKind;
|
|
98
|
+
requestBodyContentType?: string;
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
function createEmptyRequestFields(): OpenApiRequestFields {
|
|
@@ -109,6 +121,44 @@ function getSchemaVariantType(schema: any): "oneOf" | "anyOf" | undefined {
|
|
|
109
121
|
return undefined;
|
|
110
122
|
}
|
|
111
123
|
|
|
124
|
+
function isNullSchema(schema: any): boolean {
|
|
125
|
+
if (!schema || typeof schema !== "object") return false;
|
|
126
|
+
if (schema.type === "null") return true;
|
|
127
|
+
return Array.isArray(schema.type) && schema.type.includes("null");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function unwrapNullableSchema(schema: any, seen = new Set<any>()): any {
|
|
131
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
132
|
+
if (seen.has(schema)) return schema;
|
|
133
|
+
|
|
134
|
+
const nextSeen = new Set(seen);
|
|
135
|
+
nextSeen.add(schema);
|
|
136
|
+
|
|
137
|
+
if (Array.isArray(schema.type) && schema.type.includes("null")) {
|
|
138
|
+
const nonNullTypes = schema.type.filter((type: string) => type !== "null");
|
|
139
|
+
if (nonNullTypes.length === 1) {
|
|
140
|
+
return unwrapNullableSchema(
|
|
141
|
+
{
|
|
142
|
+
...schema,
|
|
143
|
+
type: nonNullTypes[0],
|
|
144
|
+
},
|
|
145
|
+
nextSeen,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const variantType = getSchemaVariantType(schema);
|
|
151
|
+
if (!variantType) return schema;
|
|
152
|
+
|
|
153
|
+
const variants = getSchemaVariants(schema);
|
|
154
|
+
const nonNullVariants = variants.filter((variant) => !isNullSchema(variant));
|
|
155
|
+
if (nonNullVariants.length === 1 && nonNullVariants.length < variants.length) {
|
|
156
|
+
return unwrapNullableSchema(nonNullVariants[0], nextSeen);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return schema;
|
|
160
|
+
}
|
|
161
|
+
|
|
112
162
|
function getAdditionalPropertiesSchema(schema: any): any {
|
|
113
163
|
if (!schema || typeof schema !== "object") return undefined;
|
|
114
164
|
|
|
@@ -125,6 +175,60 @@ function getAdditionalPropertiesSchema(schema: any): any {
|
|
|
125
175
|
return additionalProperties;
|
|
126
176
|
}
|
|
127
177
|
|
|
178
|
+
function isEmptySchema(schema: any): boolean {
|
|
179
|
+
return (
|
|
180
|
+
schema &&
|
|
181
|
+
typeof schema === "object" &&
|
|
182
|
+
!Array.isArray(schema) &&
|
|
183
|
+
Object.keys(schema).length === 0
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getSchemaDefault(schema: any, seen = new Set<any>()): {
|
|
188
|
+
hasDefault: boolean;
|
|
189
|
+
defaultValue?: unknown;
|
|
190
|
+
} {
|
|
191
|
+
if (!schema || typeof schema !== "object") return { hasDefault: false };
|
|
192
|
+
|
|
193
|
+
if (Object.prototype.hasOwnProperty.call(schema, "default")) {
|
|
194
|
+
return { hasDefault: true, defaultValue: schema.default };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (seen.has(schema)) return { hasDefault: false };
|
|
198
|
+
|
|
199
|
+
const nextSeen = new Set(seen);
|
|
200
|
+
nextSeen.add(schema);
|
|
201
|
+
|
|
202
|
+
const variantType = getSchemaVariantType(schema);
|
|
203
|
+
if (!variantType) return { hasDefault: false };
|
|
204
|
+
|
|
205
|
+
const variants = getSchemaVariants(schema);
|
|
206
|
+
const nonNullVariants = variants.filter((variant) => !isNullSchema(variant));
|
|
207
|
+
if (nonNullVariants.length === 1 && nonNullVariants.length < variants.length) {
|
|
208
|
+
return getSchemaDefault(nonNullVariants[0], nextSeen);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return { hasDefault: false };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function shouldRenderSchemaAsJson(schema: any): boolean {
|
|
215
|
+
const renderSchema = unwrapNullableSchema(schema);
|
|
216
|
+
if (!renderSchema || typeof renderSchema !== "object") return false;
|
|
217
|
+
if (getSchemaStructuralType(renderSchema) !== "object") return false;
|
|
218
|
+
if (getSchemaVariantType(renderSchema)) return false;
|
|
219
|
+
|
|
220
|
+
const shape = getTopLevelObjectShape(renderSchema);
|
|
221
|
+
if (Object.keys(shape.properties).length > 0) return false;
|
|
222
|
+
|
|
223
|
+
const additionalProperties = getAdditionalPropertiesSchema(renderSchema);
|
|
224
|
+
return additionalProperties === true || isEmptySchema(additionalProperties);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function isFileUploadSchema(schema: any): boolean {
|
|
228
|
+
const renderSchema = unwrapNullableSchema(schema);
|
|
229
|
+
return renderSchema?.type === "string" && renderSchema?.format === "binary";
|
|
230
|
+
}
|
|
231
|
+
|
|
128
232
|
function getSchemaEnumValues(schema: any): (string | number)[] | undefined {
|
|
129
233
|
if (!schema) return undefined;
|
|
130
234
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) {
|
|
@@ -501,19 +605,33 @@ function getSchemaStructuralType(
|
|
|
501
605
|
): OpenApiBodyDefaultKind | undefined {
|
|
502
606
|
if (!schema || typeof schema !== "object") return undefined;
|
|
503
607
|
|
|
608
|
+
const renderSchema = unwrapNullableSchema(schema);
|
|
609
|
+
if (renderSchema !== schema) return getSchemaStructuralType(renderSchema);
|
|
610
|
+
|
|
504
611
|
if (schema.type === "object") return "object";
|
|
505
612
|
if (schema.type === "array") return "array";
|
|
506
613
|
|
|
507
614
|
const variants = getSchemaVariants(schema);
|
|
508
615
|
if (variants.length > 0) {
|
|
616
|
+
const nonNullVariants = variants.filter((variant) => !isNullSchema(variant));
|
|
617
|
+
const structuralVariantTypes = nonNullVariants.map((variant) =>
|
|
618
|
+
getSchemaStructuralType(variant),
|
|
619
|
+
);
|
|
620
|
+
if (
|
|
621
|
+
structuralVariantTypes.some(
|
|
622
|
+
(variantType) =>
|
|
623
|
+
variantType !== "object" && variantType !== "array",
|
|
624
|
+
)
|
|
625
|
+
) {
|
|
626
|
+
return undefined;
|
|
627
|
+
}
|
|
628
|
+
|
|
509
629
|
const variantTypes = Array.from(
|
|
510
630
|
new Set(
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
variantType === "object" || variantType === "array",
|
|
516
|
-
),
|
|
631
|
+
structuralVariantTypes.filter(
|
|
632
|
+
(variantType): variantType is OpenApiBodyDefaultKind =>
|
|
633
|
+
variantType === "object" || variantType === "array",
|
|
634
|
+
),
|
|
517
635
|
),
|
|
518
636
|
);
|
|
519
637
|
if (variantTypes.length === 1) return variantTypes[0];
|
|
@@ -609,6 +727,139 @@ function getTopLevelObjectShape(schema: any): {
|
|
|
609
727
|
return { properties, required, hasObjectShape };
|
|
610
728
|
}
|
|
611
729
|
|
|
730
|
+
function getSchemaVariantObjectProperties(schema: any): Record<string, any> {
|
|
731
|
+
return getTopLevelObjectShape(schema).properties;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function getSchemaDescription(schema: any): string {
|
|
735
|
+
if (!schema || typeof schema !== "object") return "";
|
|
736
|
+
if (typeof schema.description === "string") return schema.description;
|
|
737
|
+
|
|
738
|
+
const renderSchema = unwrapNullableSchema(schema);
|
|
739
|
+
if (
|
|
740
|
+
renderSchema &&
|
|
741
|
+
renderSchema !== schema &&
|
|
742
|
+
typeof renderSchema.description === "string"
|
|
743
|
+
) {
|
|
744
|
+
return renderSchema.description;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return "";
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function getValueVariantData(
|
|
751
|
+
schema: any,
|
|
752
|
+
): { variantType: "oneOf" | "anyOf"; schemas: any[] } | undefined {
|
|
753
|
+
const renderSchema = unwrapNullableSchema(schema);
|
|
754
|
+
const variantType = getSchemaVariantType(renderSchema);
|
|
755
|
+
if (!variantType) return undefined;
|
|
756
|
+
|
|
757
|
+
const variantSchemas = getSchemaVariants(renderSchema)
|
|
758
|
+
.filter((variant) => !isNullSchema(variant))
|
|
759
|
+
.map((variant) => unwrapNullableSchema(variant));
|
|
760
|
+
|
|
761
|
+
if (variantSchemas.length < 2) return undefined;
|
|
762
|
+
|
|
763
|
+
const variantShapeTypes = variantSchemas.map((variantSchema) => {
|
|
764
|
+
const structuralType = getSchemaStructuralType(variantSchema);
|
|
765
|
+
if (structuralType === "array" || structuralType === "object") {
|
|
766
|
+
return structuralType;
|
|
767
|
+
}
|
|
768
|
+
if (getTopLevelObjectShape(variantSchema).hasObjectShape) return "object";
|
|
769
|
+
return "scalar";
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
const hasArrayShape = variantShapeTypes.includes("array");
|
|
773
|
+
const hasObjectShape = variantShapeTypes.includes("object");
|
|
774
|
+
const hasScalarShape = variantShapeTypes.includes("scalar");
|
|
775
|
+
const hasValueShapeChoice =
|
|
776
|
+
hasArrayShape || (hasObjectShape && hasScalarShape);
|
|
777
|
+
|
|
778
|
+
if (!hasValueShapeChoice) return undefined;
|
|
779
|
+
return { variantType, schemas: variantSchemas };
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function createOpenApiFieldFromSchema(args: {
|
|
783
|
+
name: string;
|
|
784
|
+
required: boolean;
|
|
785
|
+
schema: any;
|
|
786
|
+
seen: Set<any>;
|
|
787
|
+
description?: string;
|
|
788
|
+
style?: string;
|
|
789
|
+
explode?: boolean;
|
|
790
|
+
isAdditionalProperty?: boolean;
|
|
791
|
+
mapKnownKeys?: string[];
|
|
792
|
+
includeValueVariants?: boolean;
|
|
793
|
+
}): OpenApiField {
|
|
794
|
+
const {
|
|
795
|
+
name,
|
|
796
|
+
required,
|
|
797
|
+
schema,
|
|
798
|
+
seen,
|
|
799
|
+
description,
|
|
800
|
+
style,
|
|
801
|
+
explode,
|
|
802
|
+
isAdditionalProperty,
|
|
803
|
+
mapKnownKeys,
|
|
804
|
+
includeValueVariants = true,
|
|
805
|
+
} = args;
|
|
806
|
+
const renderSchema = unwrapNullableSchema(schema);
|
|
807
|
+
const valueVariantData = includeValueVariants
|
|
808
|
+
? getValueVariantData(renderSchema)
|
|
809
|
+
: undefined;
|
|
810
|
+
const nestedSchema = valueVariantData ? undefined : renderSchema;
|
|
811
|
+
const nestedContent = nestedSchema
|
|
812
|
+
? extractRequestSchemaContent(nestedSchema, seen)
|
|
813
|
+
: { fields: [] as OpenApiField[] };
|
|
814
|
+
const schemaDefault = getSchemaDefault(schema);
|
|
815
|
+
|
|
816
|
+
const field: OpenApiField = {
|
|
817
|
+
name,
|
|
818
|
+
required,
|
|
819
|
+
type: getSchemaTypeLabel(schema),
|
|
820
|
+
description: description || getSchemaDescription(schema),
|
|
821
|
+
enum: getSchemaEnumValues(schema),
|
|
822
|
+
...getSchemaStringLengthConstraints(schema, seen),
|
|
823
|
+
...getSchemaNumericConstraints(schema, seen),
|
|
824
|
+
hasDefault: schemaDefault.hasDefault,
|
|
825
|
+
defaultValue: schemaDefault.defaultValue,
|
|
826
|
+
isArray: getSchemaStructuralType(renderSchema) === "array",
|
|
827
|
+
style,
|
|
828
|
+
explode,
|
|
829
|
+
nested: nestedContent.fields.length > 0 ? nestedContent.fields : undefined,
|
|
830
|
+
variants: nestedContent.variants,
|
|
831
|
+
variantType: nestedContent.variantType,
|
|
832
|
+
isAdditionalProperty,
|
|
833
|
+
mapKnownKeys,
|
|
834
|
+
renderAsJson: shouldRenderSchemaAsJson(renderSchema),
|
|
835
|
+
isFile: isFileUploadSchema(renderSchema),
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
if (valueVariantData) {
|
|
839
|
+
field.valueVariants = valueVariantData.schemas.map(
|
|
840
|
+
(variantSchema, index) => ({
|
|
841
|
+
label: getSchemaVariantLabel(variantSchema, index, {
|
|
842
|
+
parentSchema: renderSchema,
|
|
843
|
+
siblings: valueVariantData.schemas,
|
|
844
|
+
getObjectProperties: getSchemaVariantObjectProperties,
|
|
845
|
+
getTypeLabel: getSchemaTypeLabel,
|
|
846
|
+
}),
|
|
847
|
+
field: createOpenApiFieldFromSchema({
|
|
848
|
+
name,
|
|
849
|
+
required,
|
|
850
|
+
schema: variantSchema,
|
|
851
|
+
seen,
|
|
852
|
+
description: getSchemaDescription(variantSchema),
|
|
853
|
+
includeValueVariants: false,
|
|
854
|
+
}),
|
|
855
|
+
}),
|
|
856
|
+
);
|
|
857
|
+
field.valueVariantType = valueVariantData.variantType;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
return field;
|
|
861
|
+
}
|
|
862
|
+
|
|
612
863
|
function extractRequestSchemaContent(
|
|
613
864
|
schema: any,
|
|
614
865
|
seen = new Set<any>(),
|
|
@@ -642,29 +893,12 @@ function extractRequestSchemaContent(
|
|
|
642
893
|
return 0;
|
|
643
894
|
})
|
|
644
895
|
.map(([name, propertySchema]: [string, any]) => {
|
|
645
|
-
|
|
646
|
-
propertySchema,
|
|
647
|
-
nextSeen,
|
|
648
|
-
);
|
|
649
|
-
return {
|
|
896
|
+
return createOpenApiFieldFromSchema({
|
|
650
897
|
name,
|
|
651
898
|
required: shape.required.has(name),
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
...getSchemaStringLengthConstraints(propertySchema, nextSeen),
|
|
656
|
-
...getSchemaNumericConstraints(propertySchema, nextSeen),
|
|
657
|
-
hasDefault: Object.prototype.hasOwnProperty.call(
|
|
658
|
-
propertySchema || {},
|
|
659
|
-
"default",
|
|
660
|
-
),
|
|
661
|
-
defaultValue: propertySchema?.default,
|
|
662
|
-
isArray: propertySchema?.type === "array",
|
|
663
|
-
nested:
|
|
664
|
-
nestedContent.fields.length > 0 ? nestedContent.fields : undefined,
|
|
665
|
-
variants: nestedContent.variants,
|
|
666
|
-
variantType: nestedContent.variantType,
|
|
667
|
-
};
|
|
899
|
+
schema: propertySchema,
|
|
900
|
+
seen: nextSeen,
|
|
901
|
+
});
|
|
668
902
|
});
|
|
669
903
|
|
|
670
904
|
const additionalProperties = getAdditionalPropertiesSchema(baseSchema);
|
|
@@ -679,35 +913,16 @@ function extractRequestSchemaContent(
|
|
|
679
913
|
additionalProperties && typeof additionalProperties === "object"
|
|
680
914
|
? additionalProperties
|
|
681
915
|
: null;
|
|
682
|
-
const additionalContent = additionalSchema
|
|
683
|
-
? extractRequestSchemaContent(additionalSchema, nextSeen)
|
|
684
|
-
: { fields: [] as OpenApiField[] };
|
|
685
916
|
|
|
686
|
-
fields.push({
|
|
917
|
+
fields.push(createOpenApiFieldFromSchema({
|
|
687
918
|
name: "[key: string]",
|
|
688
919
|
required: false,
|
|
689
|
-
|
|
920
|
+
schema: additionalSchema,
|
|
921
|
+
seen: nextSeen,
|
|
690
922
|
description: additionalSchema?.description || "",
|
|
691
|
-
enum: additionalSchema
|
|
692
|
-
? getSchemaEnumValues(additionalSchema)
|
|
693
|
-
: undefined,
|
|
694
|
-
...getSchemaStringLengthConstraints(additionalSchema, nextSeen),
|
|
695
|
-
...getSchemaNumericConstraints(additionalSchema, nextSeen),
|
|
696
|
-
hasDefault: Object.prototype.hasOwnProperty.call(
|
|
697
|
-
additionalSchema || {},
|
|
698
|
-
"default",
|
|
699
|
-
),
|
|
700
|
-
defaultValue: additionalSchema?.default,
|
|
701
|
-
isArray: additionalSchema?.type === "array",
|
|
702
|
-
nested:
|
|
703
|
-
additionalContent.fields.length > 0
|
|
704
|
-
? additionalContent.fields
|
|
705
|
-
: undefined,
|
|
706
|
-
variants: additionalContent.variants,
|
|
707
|
-
variantType: additionalContent.variantType,
|
|
708
923
|
isAdditionalProperty: true,
|
|
709
924
|
mapKnownKeys: Object.keys(shape.properties),
|
|
710
|
-
});
|
|
925
|
+
}));
|
|
711
926
|
}
|
|
712
927
|
|
|
713
928
|
const variantType = getSchemaVariantType(schema);
|
|
@@ -720,10 +935,28 @@ function extractRequestSchemaContent(
|
|
|
720
935
|
const variants =
|
|
721
936
|
variantType && variantSchemas.length > 0
|
|
722
937
|
? variantSchemas
|
|
723
|
-
.
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
938
|
+
.flatMap((variantSchema: any, index: number) => {
|
|
939
|
+
const variantContent = extractRequestSchemaContent(
|
|
940
|
+
variantSchema,
|
|
941
|
+
nextSeen,
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
if (variantContent.fields.length > 0) {
|
|
945
|
+
return [
|
|
946
|
+
{
|
|
947
|
+
label: getSchemaVariantLabel(variantSchema, index, {
|
|
948
|
+
parentSchema: schema,
|
|
949
|
+
siblings: variantSchemas,
|
|
950
|
+
getObjectProperties: getSchemaVariantObjectProperties,
|
|
951
|
+
getTypeLabel: getSchemaTypeLabel,
|
|
952
|
+
}),
|
|
953
|
+
fields: variantContent.fields,
|
|
954
|
+
},
|
|
955
|
+
];
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
return variantContent.variants || [];
|
|
959
|
+
})
|
|
727
960
|
.filter((variant: OpenApiFieldVariant) => variant.fields.length > 0)
|
|
728
961
|
: [];
|
|
729
962
|
|
|
@@ -773,6 +1006,7 @@ function addSecurityFields(args: {
|
|
|
773
1006
|
fieldData.name = "Authorization";
|
|
774
1007
|
|
|
775
1008
|
if (scheme.scheme === "bearer") {
|
|
1009
|
+
fieldData.valuePrefix = "Bearer";
|
|
776
1010
|
fieldData.description =
|
|
777
1011
|
fieldData.description || "Bearer token authentication.";
|
|
778
1012
|
} else if (scheme.scheme === "basic") {
|
|
@@ -784,6 +1018,7 @@ function addSecurityFields(args: {
|
|
|
784
1018
|
args.requestFields.header.push(fieldData);
|
|
785
1019
|
} else if (scheme.type === "oauth2" || scheme.type === "openIdConnect") {
|
|
786
1020
|
fieldData.name = "Authorization";
|
|
1021
|
+
fieldData.valuePrefix = "Bearer";
|
|
787
1022
|
fieldData.description = fieldData.description || "OAuth2 Bearer Token.";
|
|
788
1023
|
|
|
789
1024
|
args.requestFields.header.push(fieldData);
|
|
@@ -801,6 +1036,7 @@ function buildOpenApiRequestModel(args: {
|
|
|
801
1036
|
>;
|
|
802
1037
|
bodyDescription: string;
|
|
803
1038
|
bodyDefaultKind?: OpenApiBodyDefaultKind;
|
|
1039
|
+
requestBodyContentType?: string;
|
|
804
1040
|
} {
|
|
805
1041
|
const requestFields = createEmptyRequestFields();
|
|
806
1042
|
const requestSectionVariants: Partial<
|
|
@@ -808,6 +1044,7 @@ function buildOpenApiRequestModel(args: {
|
|
|
808
1044
|
> = {};
|
|
809
1045
|
let bodyDescription = "";
|
|
810
1046
|
let bodyDefaultKind: OpenApiBodyDefaultKind | undefined = undefined;
|
|
1047
|
+
let requestBodyContentType: string | undefined = undefined;
|
|
811
1048
|
|
|
812
1049
|
addSecurityFields({
|
|
813
1050
|
operation: args.operation,
|
|
@@ -818,26 +1055,15 @@ function buildOpenApiRequestModel(args: {
|
|
|
818
1055
|
const parameters = args.operation.getParameters();
|
|
819
1056
|
parameters.forEach((param: any) => {
|
|
820
1057
|
const schema = param.schema as any;
|
|
821
|
-
const
|
|
822
|
-
const schemaContent = extractRequestSchemaContent(schema);
|
|
823
|
-
|
|
824
|
-
const field: OpenApiField = {
|
|
1058
|
+
const field = createOpenApiFieldFromSchema({
|
|
825
1059
|
name: param.name,
|
|
826
1060
|
required: param.required || false,
|
|
827
|
-
|
|
1061
|
+
schema,
|
|
1062
|
+
seen: new Set<any>(),
|
|
828
1063
|
description: param.description || "",
|
|
829
|
-
enum: getSchemaEnumValues(schema),
|
|
830
|
-
...getSchemaStringLengthConstraints(schema),
|
|
831
|
-
...getSchemaNumericConstraints(schema),
|
|
832
|
-
hasDefault: Object.prototype.hasOwnProperty.call(schema || {}, "default"),
|
|
833
|
-
defaultValue: schema?.default,
|
|
834
|
-
isArray,
|
|
835
1064
|
style: param.style,
|
|
836
1065
|
explode: param.explode,
|
|
837
|
-
|
|
838
|
-
variants: schemaContent.variants,
|
|
839
|
-
variantType: schemaContent.variantType,
|
|
840
|
-
};
|
|
1066
|
+
});
|
|
841
1067
|
|
|
842
1068
|
if (param.in === "path") requestFields.path.push(field);
|
|
843
1069
|
if (param.in === "query") requestFields.query.push(field);
|
|
@@ -846,8 +1072,19 @@ function buildOpenApiRequestModel(args: {
|
|
|
846
1072
|
});
|
|
847
1073
|
|
|
848
1074
|
if (args.operation.hasRequestBody()) {
|
|
849
|
-
const requestBody = args.operation.getRequestBody("application/json");
|
|
850
1075
|
const requestBodyObject = args.operation.schema.requestBody;
|
|
1076
|
+
const contentTypes = Object.keys(requestBodyObject?.content || {});
|
|
1077
|
+
requestBodyContentType =
|
|
1078
|
+
contentTypes.find((contentType) =>
|
|
1079
|
+
contentType.toLowerCase().includes("application/json"),
|
|
1080
|
+
) ||
|
|
1081
|
+
contentTypes.find((contentType) =>
|
|
1082
|
+
contentType.toLowerCase().includes("multipart/form-data"),
|
|
1083
|
+
) ||
|
|
1084
|
+
contentTypes[0];
|
|
1085
|
+
const requestBody = requestBodyContentType
|
|
1086
|
+
? args.operation.getRequestBody(requestBodyContentType)
|
|
1087
|
+
: undefined;
|
|
851
1088
|
const bodySchema = (requestBody as any)?.schema as any;
|
|
852
1089
|
bodyDescription =
|
|
853
1090
|
requestBodyObject?.description ||
|
|
@@ -871,6 +1108,7 @@ function buildOpenApiRequestModel(args: {
|
|
|
871
1108
|
requestSectionVariants,
|
|
872
1109
|
bodyDescription,
|
|
873
1110
|
bodyDefaultKind,
|
|
1111
|
+
requestBodyContentType,
|
|
874
1112
|
};
|
|
875
1113
|
}
|
|
876
1114
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export interface OpenApiResponseContentEntry {
|
|
2
|
+
contentType: string;
|
|
3
|
+
schema: any;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface OpenApiBinaryResponseSummary {
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
contentTypes: string[];
|
|
10
|
+
snippetText: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getBaseContentType(contentType: string): string {
|
|
14
|
+
return String(contentType || "").split(";")[0].trim().toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isMediaContentType(contentType: string): boolean {
|
|
18
|
+
const baseContentType = getBaseContentType(contentType);
|
|
19
|
+
return (
|
|
20
|
+
baseContentType.startsWith("audio/") ||
|
|
21
|
+
baseContentType.startsWith("image/") ||
|
|
22
|
+
baseContentType.startsWith("video/")
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getMediaKind(contentType: string): "audio" | "image" | "video" | null {
|
|
27
|
+
const baseContentType = getBaseContentType(contentType);
|
|
28
|
+
if (baseContentType.startsWith("audio/")) return "audio";
|
|
29
|
+
if (baseContentType.startsWith("image/")) return "image";
|
|
30
|
+
if (baseContentType.startsWith("video/")) return "video";
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isKnownBinaryContentType(contentType: string): boolean {
|
|
35
|
+
const baseContentType = getBaseContentType(contentType);
|
|
36
|
+
return (
|
|
37
|
+
isMediaContentType(baseContentType) ||
|
|
38
|
+
baseContentType === "application/octet-stream" ||
|
|
39
|
+
baseContentType === "application/pdf" ||
|
|
40
|
+
baseContentType === "application/zip" ||
|
|
41
|
+
baseContentType === "application/gzip"
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isBinarySchema(schema: any, seen = new Set<any>()): boolean {
|
|
46
|
+
if (!schema || typeof schema !== "object") return false;
|
|
47
|
+
if (seen.has(schema)) return false;
|
|
48
|
+
|
|
49
|
+
const nextSeen = new Set(seen);
|
|
50
|
+
nextSeen.add(schema);
|
|
51
|
+
|
|
52
|
+
if (schema.type === "string" && schema.format === "binary") return true;
|
|
53
|
+
|
|
54
|
+
if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
|
|
55
|
+
const variants = Array.isArray(schema.oneOf) ? schema.oneOf : schema.anyOf;
|
|
56
|
+
return variants.some((variant: any) => isBinarySchema(variant, nextSeen));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (Array.isArray(schema.allOf)) {
|
|
60
|
+
return schema.allOf.some((part: any) => isBinarySchema(part, nextSeen));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function formatContentTypes(contentTypes: string[]): string {
|
|
67
|
+
if (contentTypes.length <= 1) return contentTypes[0] || "binary content";
|
|
68
|
+
if (contentTypes.length === 2) return `${contentTypes[0]} or ${contentTypes[1]}`;
|
|
69
|
+
return `${contentTypes.slice(0, -1).join(", ")}, or ${contentTypes.at(-1)}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getBinaryTitle(contentTypes: string[]): string {
|
|
73
|
+
const mediaKinds = Array.from(
|
|
74
|
+
new Set(
|
|
75
|
+
contentTypes
|
|
76
|
+
.map((contentType) => getMediaKind(contentType))
|
|
77
|
+
.filter((kind): kind is "audio" | "image" | "video" => Boolean(kind)),
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (mediaKinds.length === 1) {
|
|
82
|
+
const [kind] = mediaKinds;
|
|
83
|
+
return `Binary ${kind} response`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (mediaKinds.length > 1) return "Binary media response";
|
|
87
|
+
return "Binary file response";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getResponseContentEntries(
|
|
91
|
+
response: any,
|
|
92
|
+
): OpenApiResponseContentEntry[] {
|
|
93
|
+
const content = response?.content;
|
|
94
|
+
if (!content || typeof content !== "object") return [];
|
|
95
|
+
|
|
96
|
+
return Object.entries(content).map(([contentType, mediaType]: [string, any]) => ({
|
|
97
|
+
contentType,
|
|
98
|
+
schema: mediaType?.schema,
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getBinaryResponseSummary(
|
|
103
|
+
response: any,
|
|
104
|
+
): OpenApiBinaryResponseSummary | undefined {
|
|
105
|
+
const entries = getResponseContentEntries(response);
|
|
106
|
+
const binaryEntries = entries.filter((entry) => {
|
|
107
|
+
return (
|
|
108
|
+
isBinarySchema(entry.schema) || isKnownBinaryContentType(entry.contentType)
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (binaryEntries.length === 0) return undefined;
|
|
113
|
+
|
|
114
|
+
const mediaEntries = binaryEntries.filter((entry) =>
|
|
115
|
+
isMediaContentType(entry.contentType),
|
|
116
|
+
);
|
|
117
|
+
const displayEntries = mediaEntries.length > 0 ? mediaEntries : binaryEntries;
|
|
118
|
+
const contentTypes = Array.from(
|
|
119
|
+
new Set(displayEntries.map((entry) => getBaseContentType(entry.contentType))),
|
|
120
|
+
);
|
|
121
|
+
const title = getBinaryTitle(contentTypes);
|
|
122
|
+
const formattedTypes = formatContentTypes(contentTypes);
|
|
123
|
+
const description = `Returns ${formattedTypes} as raw response bytes. The body is not a structured JSON object.`;
|
|
124
|
+
const contentTypeLabel =
|
|
125
|
+
contentTypes.length === 1 ? "Content-Type" : "Content-Types";
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
title,
|
|
129
|
+
description,
|
|
130
|
+
contentTypes,
|
|
131
|
+
snippetText: `${title}\n${contentTypeLabel}: ${contentTypes.join(", ")}\n\nThe response body is raw bytes, not a JSON object.`,
|
|
132
|
+
};
|
|
133
|
+
}
|