Skip to content

Commit f5d92b7

Browse files
committed
fix: put requests with application/x-www-form-urlencoded is incorrectly handled
1 parent 2f0aa68 commit f5d92b7

File tree

19 files changed

+767
-10
lines changed

19 files changed

+767
-10
lines changed

.changeset/fix-form-urlencoded-put.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"swagger-typescript-api": patch
3+
---
4+
5+
Fix: PUT requests with application/x-www-form-urlencoded content type
6+
7+
This fixes an issue where PUT requests with `application/x-www-form-urlencoded` content type were incorrectly sent as `multipart/form-data`.
8+
A new `createUrlEncoded` method has been added to the `HttpClient` to handle this content type correctly.

src/schema-routes/schema-routes.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,17 @@ export class SchemaRoutes {
561561
});
562562
}
563563

564-
if (routeParams.formData.length) {
564+
if (
565+
contentKind === CONTENT_KIND.URL_ENCODED &&
566+
routeParams.formData.length
567+
) {
568+
schema = this.convertRouteParamsIntoObject(routeParams.formData);
569+
content = this.schemaParserFabric.getInlineParseContent(
570+
schema,
571+
typeName,
572+
[operationId],
573+
);
574+
} else if (routeParams.formData.length) {
565575
contentKind = CONTENT_KIND.FORM_DATA;
566576
schema = this.convertRouteParamsIntoObject(routeParams.formData);
567577
content = this.schemaParserFabric.getInlineParseContent(

templates/base/http-clients/axios-http-client.ejs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,22 @@ export class HttpClient<SecurityDataType = unknown> {
9999
}, new FormData());
100100
}
101101

102+
protected createUrlEncoded(input: Record<string, unknown>): URLSearchParams {
103+
if (input instanceof URLSearchParams) {
104+
return input;
105+
}
106+
return Object.keys(input || {}).reduce((searchParams, key) => {
107+
const property = input[key];
108+
const propertyContent: any[] = (property instanceof Array) ? property : [property]
109+
110+
for (const formItem of propertyContent) {
111+
searchParams.append(key, this.stringifyFormItem(formItem));
112+
}
113+
114+
return searchParams;
115+
}, new URLSearchParams());
116+
}
117+
102118
public request = async <T = any, _E = any>({
103119
secure,
104120
path,
@@ -120,6 +136,10 @@ export class HttpClient<SecurityDataType = unknown> {
120136
body = this.createFormData(body as Record<string, unknown>);
121137
}
122138

139+
if (type === ContentType.UrlEncoded && body && body !== null && typeof body === "object") {
140+
body = this.createUrlEncoded(body as Record<string, unknown>);
141+
}
142+
123143
if (type === ContentType.Text && body && body !== null && typeof body !== "string") {
124144
body = JSON.stringify(body);
125145
}

tests/__snapshots__/extended.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6376,7 +6376,7 @@ export class Api<
63766376
method: "POST",
63776377
body: data,
63786378
secure: true,
6379-
type: ContentType.FormData,
6379+
type: ContentType.UrlEncoded,
63806380
...params,
63816381
}),
63826382

@@ -69452,7 +69452,7 @@ export class Api<
6945269452
method: "POST",
6945369453
body: data,
6945469454
secure: true,
69455-
type: ContentType.FormData,
69455+
type: ContentType.UrlEncoded,
6945669456
...params,
6945769457
}),
6945869458

tests/__snapshots__/simple.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3968,7 +3968,7 @@ export class Api<
39683968
method: "POST",
39693969
body: data,
39703970
secure: true,
3971-
type: ContentType.FormData,
3971+
type: ContentType.UrlEncoded,
39723972
...params,
39733973
}),
39743974

@@ -43623,7 +43623,7 @@ export class Api<
4362343623
method: "POST",
4362443624
body: data,
4362543625
secure: true,
43626-
type: ContentType.FormData,
43626+
type: ContentType.UrlEncoded,
4362743627
...params,
4362843628
}),
4362943629

tests/spec/axios/__snapshots__/basic.test.ts.snap

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,6 +2037,23 @@ export class HttpClient<SecurityDataType = unknown> {
20372037
}, new FormData());
20382038
}
20392039

2040+
protected createUrlEncoded(input: Record<string, unknown>): URLSearchParams {
2041+
if (input instanceof URLSearchParams) {
2042+
return input;
2043+
}
2044+
return Object.keys(input || {}).reduce((searchParams, key) => {
2045+
const property = input[key];
2046+
const propertyContent: any[] =
2047+
property instanceof Array ? property : [property];
2048+
2049+
for (const formItem of propertyContent) {
2050+
searchParams.append(key, this.stringifyFormItem(formItem));
2051+
}
2052+
2053+
return searchParams;
2054+
}, new URLSearchParams());
2055+
}
2056+
20402057
public request = async <T = any, _E = any>({
20412058
secure,
20422059
path,
@@ -2063,6 +2080,15 @@ export class HttpClient<SecurityDataType = unknown> {
20632080
body = this.createFormData(body as Record<string, unknown>);
20642081
}
20652082

2083+
if (
2084+
type === ContentType.UrlEncoded &&
2085+
body &&
2086+
body !== null &&
2087+
typeof body === "object"
2088+
) {
2089+
body = this.createUrlEncoded(body as Record<string, unknown>);
2090+
}
2091+
20662092
if (
20672093
type === ContentType.Text &&
20682094
body &&

tests/spec/axiosSingleHttpClient/__snapshots__/basic.test.ts.snap

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,6 +2037,23 @@ export class HttpClient<SecurityDataType = unknown> {
20372037
}, new FormData());
20382038
}
20392039

2040+
protected createUrlEncoded(input: Record<string, unknown>): URLSearchParams {
2041+
if (input instanceof URLSearchParams) {
2042+
return input;
2043+
}
2044+
return Object.keys(input || {}).reduce((searchParams, key) => {
2045+
const property = input[key];
2046+
const propertyContent: any[] =
2047+
property instanceof Array ? property : [property];
2048+
2049+
for (const formItem of propertyContent) {
2050+
searchParams.append(key, this.stringifyFormItem(formItem));
2051+
}
2052+
2053+
return searchParams;
2054+
}, new URLSearchParams());
2055+
}
2056+
20402057
public request = async <T = any, _E = any>({
20412058
secure,
20422059
path,
@@ -2063,6 +2080,15 @@ export class HttpClient<SecurityDataType = unknown> {
20632080
body = this.createFormData(body as Record<string, unknown>);
20642081
}
20652082

2083+
if (
2084+
type === ContentType.UrlEncoded &&
2085+
body &&
2086+
body !== null &&
2087+
typeof body === "object"
2088+
) {
2089+
body = this.createUrlEncoded(body as Record<string, unknown>);
2090+
}
2091+
20662092
if (
20672093
type === ContentType.Text &&
20682094
body &&

tests/spec/extractRequestBody/__snapshots__/basic.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ export class Api<
636636
method: "POST",
637637
body: data,
638638
secure: true,
639-
type: ContentType.FormData,
639+
type: ContentType.UrlEncoded,
640640
...params,
641641
}),
642642

tests/spec/extractResponseBody/__snapshots__/basic.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ export class Api<
637637
method: "POST",
638638
body: data,
639639
secure: true,
640-
type: ContentType.FormData,
640+
type: ContentType.UrlEncoded,
641641
...params,
642642
}),
643643

tests/spec/extractResponseError/__snapshots__/basic.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ export class Api<
642642
method: "POST",
643643
body: data,
644644
secure: true,
645-
type: ContentType.FormData,
645+
type: ContentType.UrlEncoded,
646646
...params,
647647
}),
648648

0 commit comments

Comments
 (0)