Skip to content

Commit b2b6998

Browse files
author
Fransis Young
committed
fix(client-angular)!: ensures valid serialized or unserialized body is sent with request
BREAKING CHANGE: options.serializedBody is undefined by default, and will only be assigned a value when options.bodySerializer is defined refs: #2553
1 parent 10e5488 commit b2b6998

File tree

3 files changed

+135
-9
lines changed

3 files changed

+135
-9
lines changed

packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-angular/client/client.gen.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export const createClient = (config: Config = {}): Client => {
6969
...options,
7070
headers: mergeHeaders(_config.headers, options.headers),
7171
httpClient: options.httpClient ?? _config.httpClient,
72-
serializedBody: options.body as any,
72+
serializedBody: undefined,
7373
};
7474

7575
if (!opts.httpClient) {
@@ -83,12 +83,12 @@ export const createClient = (config: Config = {}): Client => {
8383
}
8484
}
8585

86-
if (opts.body && opts.bodySerializer) {
86+
if (opts.body !== undefined && opts.bodySerializer) {
8787
opts.serializedBody = opts.bodySerializer(opts.body);
8888
}
8989

9090
// remove Content-Type header if body is empty to avoid sending invalid requests
91-
if (opts.serializedBody === undefined || opts.serializedBody === '') {
91+
if (opts.body === undefined || opts.serializedBody === '') {
9292
opts.headers.delete('Content-Type');
9393
}
9494

@@ -97,7 +97,7 @@ export const createClient = (config: Config = {}): Client => {
9797
const req = new HttpRequest<unknown>(
9898
opts.method ?? 'GET',
9999
url,
100-
opts.serializedBody || null,
100+
getValidRequestBody(opts),
101101
{
102102
redirect: 'follow',
103103
...opts,
@@ -201,6 +201,25 @@ export const createClient = (config: Config = {}): Client => {
201201
}
202202
};
203203

204+
function getValidRequestBody(options: ResolvedRequestOptions) {
205+
const hasBody = options.body !== undefined;
206+
const isSerializedBody = hasBody && options.bodySerializer;
207+
208+
if (isSerializedBody) {
209+
const hasSerializedBody =
210+
options.serializedBody !== undefined && options.serializedBody !== '';
211+
212+
return hasSerializedBody ? options.serializedBody : null;
213+
}
214+
215+
// plain/text body
216+
if (hasBody) {
217+
return options.body;
218+
}
219+
220+
return undefined;
221+
}
222+
204223
const makeMethodFn =
205224
(method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
206225
request({ ...options, method });

packages/openapi-ts/src/plugins/@hey-api/client-angular/__tests__/client.test.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { describe, expect, it } from 'vitest';
1+
import type { HttpClient } from '@angular/common/http';
2+
import { HttpHeaders } from '@angular/common/http';
3+
import { describe, expect, it, vi } from 'vitest';
24

35
import { createClient } from '../bundle/client';
46

@@ -68,3 +70,89 @@ describe('buildUrl', () => {
6870
expect(client.buildUrl(options)).toBe(url);
6971
});
7072
});
73+
74+
describe('unserialized request body handling', () => {
75+
const client = createClient({ baseUrl: 'https://example.com' });
76+
77+
const scenarios = [
78+
{ body: 0 },
79+
{ body: false },
80+
{ body: 'test string' },
81+
{ body: '' },
82+
];
83+
84+
it.each(scenarios)(
85+
'handles plain text body with $body value',
86+
async ({ body }) => {
87+
const spy = vi.spyOn(HttpHeaders.prototype, 'delete');
88+
89+
const request = client.requestOptions({
90+
body,
91+
bodySerializer: null,
92+
httpClient: vi.fn() as Partial<HttpClient> as HttpClient,
93+
url: '/test',
94+
});
95+
96+
expect(request).toMatchObject(
97+
expect.objectContaining({
98+
body,
99+
}),
100+
);
101+
102+
expect(spy).toHaveBeenCalledTimes(0);
103+
},
104+
);
105+
});
106+
107+
describe('requestOptions serialized request body handling', () => {
108+
const client = createClient({ baseUrl: 'https://example.com' });
109+
110+
const scenarios = [
111+
{
112+
body: '',
113+
expectBodyValue: false,
114+
expectDeleteHeader: 1,
115+
serializedBody: '',
116+
},
117+
{
118+
body: 0,
119+
expectBodyValue: true,
120+
expectDeleteHeader: 0,
121+
serializedBody: 0,
122+
},
123+
{
124+
body: false,
125+
expectBodyValue: true,
126+
expectDeleteHeader: 0,
127+
serializedBody: false,
128+
},
129+
{
130+
body: {},
131+
expectBodyValue: true,
132+
expectDeleteHeader: 0,
133+
serializedBody: '{"key":"value"}',
134+
},
135+
];
136+
137+
it.each(scenarios)(
138+
'handles $serializedBody serializedBody value',
139+
async ({ body, expectBodyValue, expectDeleteHeader, serializedBody }) => {
140+
const spy = vi.spyOn(HttpHeaders.prototype, 'delete');
141+
142+
const request = client.requestOptions({
143+
body,
144+
bodySerializer: () => serializedBody,
145+
httpClient: vi.fn() as Partial<HttpClient> as HttpClient,
146+
url: '/test',
147+
});
148+
149+
expect(request).toMatchObject(
150+
expect.objectContaining({
151+
body: expectBodyValue ? serializedBody : null,
152+
}),
153+
);
154+
155+
expect(spy).toHaveBeenCalledTimes(expectDeleteHeader);
156+
},
157+
);
158+
});

packages/openapi-ts/src/plugins/@hey-api/client-angular/bundle/client.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const createClient = (config: Config = {}): Client => {
6767
...options,
6868
headers: mergeHeaders(_config.headers, options.headers),
6969
httpClient: options.httpClient ?? _config.httpClient,
70-
serializedBody: options.body as any,
70+
serializedBody: undefined,
7171
};
7272

7373
if (!opts.httpClient) {
@@ -81,12 +81,12 @@ export const createClient = (config: Config = {}): Client => {
8181
}
8282
}
8383

84-
if (opts.body && opts.bodySerializer) {
84+
if (opts.body !== undefined && opts.bodySerializer) {
8585
opts.serializedBody = opts.bodySerializer(opts.body);
8686
}
8787

8888
// remove Content-Type header if body is empty to avoid sending invalid requests
89-
if (opts.serializedBody === undefined || opts.serializedBody === '') {
89+
if (opts.body === undefined || opts.serializedBody === '') {
9090
opts.headers.delete('Content-Type');
9191
}
9292

@@ -95,7 +95,7 @@ export const createClient = (config: Config = {}): Client => {
9595
const req = new HttpRequest<unknown>(
9696
opts.method ?? 'GET',
9797
url,
98-
opts.serializedBody || null,
98+
getValidRequestBody(opts),
9999
{
100100
redirect: 'follow',
101101
...opts,
@@ -199,6 +199,25 @@ export const createClient = (config: Config = {}): Client => {
199199
}
200200
};
201201

202+
function getValidRequestBody(options: ResolvedRequestOptions) {
203+
const hasBody = options.body !== undefined;
204+
const isSerializedBody = hasBody && options.bodySerializer;
205+
206+
if (isSerializedBody) {
207+
const hasSerializedBody =
208+
options.serializedBody !== undefined && options.serializedBody !== '';
209+
210+
return hasSerializedBody ? options.serializedBody : null;
211+
}
212+
213+
// plain/text body
214+
if (hasBody) {
215+
return options.body;
216+
}
217+
218+
return undefined;
219+
}
220+
202221
const makeMethodFn =
203222
(method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
204223
request({ ...options, method });

0 commit comments

Comments
 (0)