Skip to content

Commit 3a88588

Browse files
committed
fix(explorer): respect encoding.contentType for multipart/form-data parts
OpenAPI encoding objects specify per-part Content-Type for multipart/form-data but these were ignored, causing file parts to always send as application/octet-stream regardless of the spec. - setBody/buildPostmanRequest: thread encoding through and set contentType on sdk.FormParam for each field that declares one, so code snippets reflect the correct per-part Content-Type - makeRequest: wrap file and string parts in a typed Blob when encoding.contentType is present so the actual HTTP request uses it - Request/index.tsx: extract encoding from requestBody for the active content type and pass to both buildPostmanRequest and makeRequest - CodeSnippets: accept requestBody prop and derive encoding from it so generated code snippets stay in sync encoding.contentType may be comma-separated per OAS spec; the first value is used. Closes #1247
1 parent ba1f51d commit 3a88588

5 files changed

Lines changed: 64 additions & 10 deletions

File tree

packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeSnippets/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface Props {
3030
postman: sdk.Request;
3131
codeSamples: CodeSample[];
3232
maskCredentials?: boolean;
33+
requestBody?: any;
3334
}
3435

3536
function CodeTab({ children, hidden, className }: any): React.JSX.Element {
@@ -44,6 +45,7 @@ function CodeSnippets({
4445
postman,
4546
codeSamples,
4647
maskCredentials: propMaskCredentials,
48+
requestBody,
4749
}: Props) {
4850
const { siteConfig } = useDocusaurusContext();
4951

@@ -76,7 +78,9 @@ function CodeSnippets({
7678
const authOptions =
7779
clonedAuth?.options?.[key] ??
7880
clonedAuth?.options?.[comboAuthId];
79-
placeholder = authOptions?.find((opt: any) => opt.key === key)?.name;
81+
placeholder = authOptions?.find(
82+
(opt: any) => opt.key === key
83+
)?.name;
8084
obj[key] = cleanCredentials(obj[key]);
8185
} else {
8286
obj[key] = `<${placeholder ?? key}>`;
@@ -93,6 +97,8 @@ function CodeSnippets({
9397
})()
9498
: auth;
9599

100+
const encoding = requestBody?.content?.[contentType]?.encoding ?? undefined;
101+
96102
// Create a Postman request object using cleanedAuth or original auth
97103
const cleanedPostmanRequest = buildPostmanRequest(postman, {
98104
queryParams,
@@ -104,6 +110,7 @@ function CodeSnippets({
104110
body,
105111
server,
106112
auth: cleanedAuth,
113+
encoding,
107114
});
108115

109116
// User-defined languages array

packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/Request/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ function Request({ item }: { item: ApiItem }) {
7676
...headerParams,
7777
];
7878

79+
const encoding =
80+
item.requestBody?.content?.[contentType]?.encoding ?? undefined;
81+
7982
const postmanRequest = buildPostmanRequest(postman, {
8083
queryParams,
8184
pathParams,
@@ -86,6 +89,7 @@ function Request({ item }: { item: ApiItem }) {
8689
body,
8790
server,
8891
auth,
92+
encoding,
8993
});
9094

9195
const delay = (ms: number) =>
@@ -175,7 +179,8 @@ function Request({ item }: { item: ApiItem }) {
175179
proxy,
176180
body,
177181
requestTimeout,
178-
requestCredentials
182+
requestCredentials,
183+
encoding
179184
);
180185
if (res.headers.get("content-type")?.includes("text/event-stream")) {
181186
await handleEventStream(res);

packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/Request/makeRequest.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ async function makeRequest(
115115
proxy: string | undefined,
116116
_body: Body,
117117
timeout: number = DEFAULT_REQUEST_TIMEOUT,
118-
credentials?: RequestCredentials
118+
credentials?: RequestCredentials,
119+
encoding?: Record<string, { contentType?: string }>
119120
) {
120121
const headers = request.toJSON().header;
121122

@@ -227,12 +228,25 @@ async function makeRequest(
227228
const members = (request.body as any)?.formdata?.members;
228229
if (Array.isArray(members)) {
229230
for (const data of members) {
231+
const partContentType = encoding?.[data.key]?.contentType
232+
?.split(",")[0]
233+
.trim();
230234
if (data.key && data.value.content) {
231-
myBody.append(data.key, data.value.content);
235+
const blob = partContentType
236+
? new Blob([data.value.content], { type: partContentType })
237+
: data.value.content;
238+
myBody.append(data.key, blob);
232239
}
233240
// handle generic key-value payload
234241
if (data.key && typeof data.value === "string") {
235-
myBody.append(data.key, data.value);
242+
if (partContentType) {
243+
myBody.append(
244+
data.key,
245+
new Blob([data.value], { type: partContentType })
246+
);
247+
} else {
248+
myBody.append(data.key, data.value);
249+
}
236250
}
237251
}
238252
}

packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/buildPostmanRequest.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,11 @@ function tryDecodeJsonParam(value: string): any {
293293
}
294294

295295
// TODO: this is all a bit hacky
296-
function setBody(clonedPostman: sdk.Request, body: Body) {
296+
function setBody(
297+
clonedPostman: sdk.Request,
298+
body: Body,
299+
encoding?: Record<string, { contentType?: string }>
300+
) {
297301
if (clonedPostman.body === undefined) {
298302
return;
299303
}
@@ -336,14 +340,35 @@ function setBody(clonedPostman: sdk.Request, body: Body) {
336340
Object.entries(body.content)
337341
.filter((entry): entry is [string, NonNullable<Content>] => !!entry[1])
338342
.forEach(([key, content]) => {
343+
const partContentType = encoding?.[key]?.contentType
344+
?.split(",")[0]
345+
.trim();
339346
if (content.type === "file") {
340-
params.push(new sdk.FormParam({ key: key, ...content }));
347+
params.push(
348+
new sdk.FormParam({
349+
key: key,
350+
...content,
351+
...(partContentType && { contentType: partContentType }),
352+
})
353+
);
341354
} else if (content.type === "file[]") {
342355
content.value.forEach((file) =>
343-
params.push(new sdk.FormParam({ key, value: file }))
356+
params.push(
357+
new sdk.FormParam({
358+
key,
359+
value: file,
360+
...(partContentType && { contentType: partContentType }),
361+
})
362+
)
344363
);
345364
} else {
346-
params.push(new sdk.FormParam({ key: key, value: content.value }));
365+
params.push(
366+
new sdk.FormParam({
367+
key: key,
368+
value: content.value,
369+
...(partContentType && { contentType: partContentType }),
370+
})
371+
);
347372
}
348373
});
349374
params.forEach((param) => {
@@ -391,6 +416,7 @@ interface Options {
391416
accept: string;
392417
body: Body;
393418
auth: AuthState;
419+
encoding?: Record<string, { contentType?: string }>;
394420
}
395421

396422
function buildPostmanRequest(
@@ -405,6 +431,7 @@ function buildPostmanRequest(
405431
body,
406432
server,
407433
auth,
434+
encoding,
408435
}: Options
409436
) {
410437
const clonedPostman = cloneDeep(postman);
@@ -532,7 +559,7 @@ function buildPostmanRequest(
532559
otherHeaders
533560
);
534561

535-
setBody(clonedPostman, body);
562+
setBody(clonedPostman, body, encoding);
536563

537564
return clonedPostman;
538565
}

packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ function ApiExplorer({
4141
postman={postman}
4242
codeSamples={(item as any)["x-codeSamples"] ?? []}
4343
maskCredentials={mask_credentials}
44+
requestBody={item.requestBody}
4445
/>
4546
)}
4647
<Request item={item} />

0 commit comments

Comments
 (0)