Skip to content

Commit bbb2ffd

Browse files
committed
fix(#3150)!: wrap all clients request in the try catch
1 parent 9abd347 commit bbb2ffd

15 files changed

Lines changed: 462 additions & 409 deletions

File tree

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

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -120,25 +120,30 @@ export const createClient = (config: Config = {}): Client => {
120120
};
121121

122122
const request: Client['request'] = async (options) => {
123-
const { opts, req: initialReq } = await beforeRequest(options);
124-
125-
let req = initialReq;
126-
127-
for (const fn of interceptors.request.fns) {
128-
if (fn) {
129-
req = await fn(req, opts as any);
130-
}
131-
}
123+
const throwOnError = options.throwOnError ?? _config.throwOnError;
124+
const responseStyle = options.responseStyle ?? _config.responseStyle;
132125

133126
const result: {
134-
request: HttpRequest<unknown>;
127+
request?: HttpRequest<unknown>;
135128
response: any;
136129
} = {
137-
request: req,
130+
request: undefined,
138131
response: null,
139132
};
140133

141134
try {
135+
const { opts, req: initialReq } = await beforeRequest(options);
136+
137+
let req = initialReq;
138+
result.request = req;
139+
140+
for (const fn of interceptors.request.fns) {
141+
if (fn) {
142+
req = await fn(req, opts as any);
143+
result.request = req;
144+
}
145+
}
146+
142147
result.response = (await firstValueFrom(
143148
opts
144149
.httpClient!.request(req)
@@ -171,15 +176,20 @@ export const createClient = (config: Config = {}): Client => {
171176

172177
for (const fn of interceptors.error.fns) {
173178
if (fn) {
174-
finalError = (await fn(finalError, result.response as any, req, opts as any)) as string;
179+
finalError = (await fn(
180+
finalError,
181+
result.response as any,
182+
result.request,
183+
options as any,
184+
)) as string;
175185
}
176186
}
177187

178-
if (opts.throwOnError) {
188+
if (throwOnError) {
179189
throw finalError;
180190
}
181191

182-
return opts.responseStyle === 'data'
192+
return responseStyle === 'data'
183193
? undefined
184194
: {
185195
error: finalError,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ export type RequestResult<
130130
| {
131131
data: undefined;
132132
error: TError[keyof TError];
133-
request: HttpRequest<unknown>;
133+
/** request may be undefined, because error may be from building the request object itself */
134+
request?: HttpRequest<unknown>;
134135
response: HttpErrorResponse & {
135136
error: TError[keyof TError] | null;
136137
};

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,8 @@ export const mergeHeaders = (
313313
type ErrInterceptor<Err, Res, Req, Options> = (
314314
error: Err,
315315
response: Res,
316-
request: Req,
316+
/** request may be undefined, because error may be from building the request object itself */
317+
request: Req | undefined,
317318
options: Options,
318319
) => Err | Promise<Err>;
319320

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

Lines changed: 111 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -73,171 +73,154 @@ export const createClient = (config: Config = {}): Client => {
7373
};
7474

7575
const request: Client['request'] = async (options) => {
76-
const { opts, url } = await beforeRequest(options);
77-
const requestInit: ReqInit = {
78-
redirect: 'follow',
79-
...opts,
80-
body: getValidRequestBody(opts),
81-
};
82-
83-
let request = new Request(url, requestInit);
84-
85-
for (const fn of interceptors.request.fns) {
86-
if (fn) {
87-
request = await fn(request, opts);
88-
}
89-
}
76+
const throwOnError = options.throwOnError ?? _config.throwOnError;
77+
const responseStyle = options.responseStyle ?? _config.responseStyle;
9078

91-
// fetch must be assigned here, otherwise it would throw the error:
92-
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
93-
const _fetch = opts.fetch!;
94-
let response: Response;
79+
let request: Request | undefined;
80+
let response: Response | undefined;
9581

9682
try {
97-
response = await _fetch(request);
98-
} catch (error) {
99-
// Handle fetch exceptions (AbortError, network errors, etc.)
100-
let finalError = error;
83+
const { opts, url } = await beforeRequest(options);
84+
const requestInit: ReqInit = {
85+
redirect: 'follow',
86+
...opts,
87+
body: getValidRequestBody(opts),
88+
};
10189

102-
for (const fn of interceptors.error.fns) {
90+
request = new Request(url, requestInit);
91+
92+
for (const fn of interceptors.request.fns) {
10393
if (fn) {
104-
finalError = (await fn(error, undefined as any, request, opts)) as unknown;
94+
request = await fn(request, opts);
10595
}
10696
}
10797

108-
finalError = finalError || ({} as unknown);
98+
// fetch must be assigned here, otherwise it would throw the error:
99+
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
100+
const _fetch = opts.fetch!;
109101

110-
if (opts.throwOnError) {
111-
throw finalError;
112-
}
113-
114-
// Return error response
115-
return opts.responseStyle === 'data'
116-
? undefined
117-
: {
118-
error: finalError,
119-
request,
120-
response: undefined as any,
121-
};
122-
}
102+
response = await _fetch(request);
123103

124-
for (const fn of interceptors.response.fns) {
125-
if (fn) {
126-
response = await fn(response, request, opts);
104+
for (const fn of interceptors.response.fns) {
105+
if (fn) {
106+
response = await fn(response, request, opts);
107+
}
127108
}
128-
}
129-
130-
const result = {
131-
request,
132-
response,
133-
};
134109

135-
if (response.ok) {
136-
const parseAs =
137-
(opts.parseAs === 'auto'
138-
? getParseAs(response.headers.get('Content-Type'))
139-
: opts.parseAs) ?? 'json';
110+
const result = {
111+
request,
112+
response,
113+
};
114+
115+
if (response.ok) {
116+
const parseAs =
117+
(opts.parseAs === 'auto'
118+
? getParseAs(response.headers.get('Content-Type'))
119+
: opts.parseAs) ?? 'json';
120+
121+
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
122+
let emptyData: any;
123+
switch (parseAs) {
124+
case 'arrayBuffer':
125+
case 'blob':
126+
case 'text':
127+
emptyData = await response[parseAs]();
128+
break;
129+
case 'formData':
130+
emptyData = new FormData();
131+
break;
132+
case 'stream':
133+
emptyData = response.body;
134+
break;
135+
case 'json':
136+
default:
137+
emptyData = {};
138+
break;
139+
}
140+
return opts.responseStyle === 'data'
141+
? emptyData
142+
: {
143+
data: emptyData,
144+
...result,
145+
};
146+
}
140147

141-
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
142-
let emptyData: any;
148+
let data: any;
143149
switch (parseAs) {
144150
case 'arrayBuffer':
145151
case 'blob':
152+
case 'formData':
146153
case 'text':
147-
emptyData = await response[parseAs]();
154+
data = await response[parseAs]();
148155
break;
149-
case 'formData':
150-
emptyData = new FormData();
156+
case 'json': {
157+
// Some servers return 200 with no Content-Length and empty body.
158+
// response.json() would throw; read as text and parse if non-empty.
159+
const text = await response.text();
160+
data = text ? JSON.parse(text) : {};
151161
break;
162+
}
152163
case 'stream':
153-
emptyData = response.body;
154-
break;
155-
case 'json':
156-
default:
157-
emptyData = {};
158-
break;
164+
return opts.responseStyle === 'data'
165+
? response.body
166+
: {
167+
data: response.body,
168+
...result,
169+
};
170+
}
171+
172+
if (parseAs === 'json') {
173+
if (opts.responseValidator) {
174+
await opts.responseValidator(data);
175+
}
176+
177+
if (opts.responseTransformer) {
178+
data = await opts.responseTransformer(data);
179+
}
159180
}
181+
160182
return opts.responseStyle === 'data'
161-
? emptyData
183+
? data
162184
: {
163-
data: emptyData,
185+
data,
164186
...result,
165187
};
166188
}
167189

168-
let data: any;
169-
switch (parseAs) {
170-
case 'arrayBuffer':
171-
case 'blob':
172-
case 'formData':
173-
case 'text':
174-
data = await response[parseAs]();
175-
break;
176-
case 'json': {
177-
// Some servers return 200 with no Content-Length and empty body.
178-
// response.json() would throw; read as text and parse if non-empty.
179-
const text = await response.text();
180-
data = text ? JSON.parse(text) : {};
181-
break;
182-
}
183-
case 'stream':
184-
return opts.responseStyle === 'data'
185-
? response.body
186-
: {
187-
data: response.body,
188-
...result,
189-
};
190+
const textError = await response.text();
191+
let jsonError: unknown;
192+
193+
try {
194+
jsonError = JSON.parse(textError);
195+
} catch {
196+
// noop
190197
}
191198

192-
if (parseAs === 'json') {
193-
if (opts.responseValidator) {
194-
await opts.responseValidator(data);
195-
}
199+
throw jsonError ?? textError;
200+
} catch (error) {
201+
let finalError = error;
196202

197-
if (opts.responseTransformer) {
198-
data = await opts.responseTransformer(data);
203+
for (const fn of interceptors.error.fns) {
204+
if (fn) {
205+
finalError = (await fn(error, response, request, options as any)) as unknown;
199206
}
200207
}
201208

202-
return opts.responseStyle === 'data'
203-
? data
204-
: {
205-
data,
206-
...result,
207-
};
208-
}
209-
210-
const textError = await response.text();
211-
let jsonError: unknown;
212-
213-
try {
214-
jsonError = JSON.parse(textError);
215-
} catch {
216-
// noop
217-
}
218-
219-
const error = jsonError ?? textError;
220-
let finalError = error;
209+
finalError = finalError || ({} as unknown);
221210

222-
for (const fn of interceptors.error.fns) {
223-
if (fn) {
224-
finalError = (await fn(error, response, request, opts)) as string;
211+
if (throwOnError) {
212+
throw finalError;
225213
}
226-
}
227-
228-
finalError = finalError || ({} as string);
229214

230-
if (opts.throwOnError) {
231-
throw finalError;
215+
// TODO: we probably want to return error and improve types
216+
return responseStyle === 'data'
217+
? undefined
218+
: {
219+
error: finalError,
220+
request,
221+
response: response as any,
222+
};
232223
}
233-
234-
// TODO: we probably want to return error and improve types
235-
return opts.responseStyle === 'data'
236-
? undefined
237-
: {
238-
error: finalError,
239-
...result,
240-
};
241224
};
242225

243226
const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ export type RequestResult<
125125
error: TError extends Record<string, unknown> ? TError[keyof TError] : TError;
126126
}
127127
) & {
128-
request: Request;
128+
/** request may be undefined, because error may be from building the request object itself */
129+
request?: Request;
129130
response: Response;
130131
}
131132
>;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,10 @@ export const mergeHeaders = (
216216

217217
type ErrInterceptor<Err, Res, Req, Options> = (
218218
error: Err,
219-
response: Res,
220-
request: Req,
219+
/** response may be undefined due to a network error where no response object is produced */
220+
response: Res | undefined,
221+
/** request may be undefined, because error may be from building the request object itself */
222+
request: Req | undefined,
221223
options: Options,
222224
) => Err | Promise<Err>;
223225

0 commit comments

Comments
 (0)