-
-
Notifications
You must be signed in to change notification settings - Fork 7.3k
Description
Bug Report Checklist
- Have you provided a full/minimal spec to reproduce the issue?
- Have you validated the input using an OpenAPI validator?
- Have you tested with the latest master to confirm the issue still exists?
- Have you searched for related issues/PRs?
- What's the actual output vs expected output?
- [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
The typescript-fetch generator incorrectly generates code for multipart/form-data endpoints that accept arrays of files. The generated code has three critical bugs:
- Hardcodes
useFormtofalseinstead of usingcanConsumeForm, causingURLSearchParamsto be used instead ofFormData - Converts File objects to strings using
.join()instead of appending them individually to FormData - Results in incorrect Content-Type (
application/x-www-form-urlencodedinstead ofmultipart/form-data)
This causes file uploads to fail because:
- File objects are converted to the string
[object File]instead of being sent as binary data - The wrong Content-Type header is sent, causing servers to reject the request
- The request body format is incorrect for multipart/form-data
Impact: High severity - breaks all file array uploads in generated TypeScript clients.
openapi-generator version
openapi-generator-cli 7.17.0
commit : 0120486
built : -999999999-01-01T00:00:00+18:00
source : https://github.com/openapitools/openapi-generator
docs : https://openapi-generator.tech/
Is it a regression?: Unknown - not tested with previous versions.
OpenAPI declaration file content or url
openapi: 3.0.0
info:
title: File Upload API
version: 1.0.0
paths:
/recording/upload:
post:
summary: Upload multiple recording files
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- files
properties:
files:
type: array
items:
type: string
format: binary
responses:
'200':
description: Upload successful
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
results:
type: array
items:
type: objectMinimal reproduction spec:
openapi: 3.0.0
info:
title: Minimal File Array Upload Bug
version: 1.0.0
paths:
/upload:
post:
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
files:
type: array
items:
type: string
format: binary
responses:
'200':
description: SuccessGeneration Details
CLI command:
openapi-generator-cli generate \
-i openapi.yaml \
-g typescript-fetch \
-o ./generated/typescript-fetch \
--additional-properties=typescriptThreePlus=true,supportsES6=trueConfig file (alternative):
{
"generatorName": "typescript-fetch",
"inputSpec": "openapi.yaml",
"outputDir": "./generated/typescript-fetch",
"additionalProperties": {
"typescriptThreePlus": true,
"supportsES6": true
}
}Language: TypeScript
Library: fetch API (browser)
Additional properties: None (or as shown above)
Steps to reproduce
-
Create an OpenAPI spec with a
multipart/form-dataendpoint that accepts an array of files (see spec above) -
Generate TypeScript client:
openapi-generator-cli generate -i openapi.yaml -g typescript-fetch -o ./output
-
Use the generated client in browser code:
import { DefaultApi } from './output'; const api = new DefaultApi(); const file1 = new File(['content1'], 'file1.mp3', { type: 'audio/mpeg' }); const file2 = new File(['content2'], 'file2.mp3', { type: 'audio/mpeg' }); // This will fail due to the bug await api.uploadRecordings({ files: [file1, file2] });
-
Inspect the network request in browser DevTools:
- Expected:
Content-Type: multipart/form-datawith binary file data - Actual:
Content-Type: application/x-www-form-urlencodedwithfiles=[object File]
- Expected:
Expected generated code:
const canConsumeForm = runtime.canConsumeForm(consumes);
let formParams: FormData;
let useForm = canConsumeForm; // ✅ Use canConsumeForm
if (useForm) {
formParams = new FormData();
} else {
formParams = new URLSearchParams();
}
if (requestParameters['files'] != null) {
// ✅ Append each file individually
if (Array.isArray(requestParameters['files'])) {
requestParameters['files'].forEach((file) => {
formParams.append('files', file);
});
} else {
formParams.append('files', requestParameters['files']);
}
}Actual generated code (with bug):
const canConsumeForm = runtime.canConsumeForm(consumes);
let formParams: { append(param: string, value: any): any };
let useForm = false; // ❌ Hardcoded to false
if (useForm) {
formParams = new FormData();
} else {
formParams = new URLSearchParams(); // ❌ Wrong type
}
if (requestParameters['files'] != null) {
// ❌ Converts File[] to string "[object File]"
formParams.append('files', requestParameters['files']!.join(runtime.COLLECTION_FORMATS["csv"]));
}Actual network request:
POST /recording/upload HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
files=[object File]Related issues/PRs
Search keywords: typescript-fetch multipart file array, typescript-fetch FormData array, multipart/form-data array binary
Potential related issues (not verified):
- Similar issues may exist for other generators (Java, Python, etc.)
- Single file uploads work correctly (see comparison below)
Comparison with working single file upload:
In the same generated codebase, single file uploads are handled correctly:
// Working example for single file (from deprecated-ucenter/apis/DefaultApi.ts)
let formParams: { append(param: string, value: any): any };
let useForm = false;
useForm = canConsumeForm; // ✅ Correctly assigns canConsumeForm
if (useForm) {
formParams = new FormData();
} else {
formParams = new URLSearchParams();
}
if (requestParameters['avatar'] != null) {
formParams.append('avatar', requestParameters['avatar'] as any); // ✅ Direct append
}Note: The working example handles a single file correctly, but the same pattern is not applied to file arrays.
Suggest a fix
The generator template needs to be updated in two places:
1. Fix useForm assignment:
In the template that generates the form parameter handling code, change:
let useForm = false;To:
let useForm = canConsumeForm;2. Fix file array handling:
When the schema property is an array with items.format === 'binary', the generator should iterate and append each file individually instead of using .join():
if (requestParameters['files'] != null) {
if (Array.isArray(requestParameters['files'])) {
// For arrays of files, append each file individually
requestParameters['files'].forEach((file) => {
formParams.append('files', file);
});
} else {
// For single files, append directly
formParams.append('files', requestParameters['files']);
}
}3. Detection logic:
The generator should detect when:
consumescontainsmultipart/form-data- The schema property is an array type
- The array
itemshaveformat: binary
In such cases, it should:
- Set
useForm = canConsumeForm(not hardcode tofalse) - Use
FormDatainstead ofURLSearchParams - Iterate over the array and append each file individually
Template location: The fix should be in the typescript-fetch generator template files, likely in the form parameter generation logic.
Workaround for users:
Until the generator is fixed, developers can manually override the generated method:
async uploadRecordings(files: File[]): Promise<UploadRecordingsResponse> {
const formData = new FormData();
files.forEach((file) => {
formParams.append('files', file);
});
const config = this.getConfig();
const response = await fetch(`${config.baseUrl}/recording/upload`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
body: formData,
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.statusText}`);
}
const jsonValue = await response.json();
return UploadRecordingsResponseFromJSON(jsonValue);
}Additional Context:
- Generator Template:
typescript-fetch - OpenAPI Spec Source: TypeSpec → OpenAPI conversion
- Runtime: Browser (fetch API)
- Tested Browsers: Chrome, Safari
References: