Skip to content

Commit ab8a123

Browse files
committed
chore: cleanup, pass responseFn in decode error function
1 parent c1af7da commit ab8a123

File tree

6 files changed

+72
-22
lines changed

6 files changed

+72
-22
lines changed

packages/express-wrapper/src/request.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,15 @@ const createNamedFunction = <F extends (...args: any) => void>(
9797
export const onDecodeError: OnDecodeErrorFn = (errs, _req, res) => {
9898
const validationErrors = PathReporter.failure(errs);
9999
const validationErrorMessage = validationErrors.join('\n');
100-
res.writeHead(400, { 'Content-Type': 'application/json' });
101-
res.write(JSON.stringify({ error: validationErrorMessage }));
102-
res.end();
100+
return {
101+
response: validationErrorMessage,
102+
statusCode: 400,
103+
responseFn: () => {
104+
res.writeHead(400, { 'Content-Type': 'application/json' });
105+
res.write(JSON.stringify({ error: validationErrorMessage }));
106+
res.end();
107+
},
108+
};
103109
};
104110

105111
export const onEncodeError: OnEncodeErrorFn = (err, _req, res) => {

packages/typed-express-router/src/errors.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ import { OnDecodeErrorFn } from './types';
77
export function defaultOnDecodeError(
88
errs: Errors,
99
_req: express.Request,
10-
_res: express.Response,
10+
res: express.Response,
1111
): ReturnType<OnDecodeErrorFn> {
1212
const validationErrors = PathReporter.failure(errs);
1313
const validationErrorMessage = validationErrors.join('\n');
14-
return { response: validationErrorMessage, statusCode: 400 };
14+
const responseFn = () => {
15+
res.status(400).json({ error: validationErrorMessage }).end();
16+
};
17+
return { response: validationErrorMessage, statusCode: 400, responseFn };
1518
}
1619

1720
export function defaultOnEncodeError(

packages/typed-express-router/src/index.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,12 @@ export function wrapRouter<Spec extends ApiSpec>(
163163
pipe(
164164
req.decoded,
165165
E.getOrElseW((errs) => {
166-
const error = (onDecodeError ?? onDecodeError)(errs, req, res);
167-
recordSpanDecodeError(decodeSpan, error.response, error.statusCode);
166+
const { response, statusCode } = (options?.onDecodeError ?? onDecodeError)(
167+
errs,
168+
req,
169+
res,
170+
);
171+
recordSpanDecodeError(decodeSpan, response, statusCode);
168172
}),
169173
);
170174
endSpan(decodeSpan);
@@ -200,10 +204,12 @@ export function wrapRouter<Spec extends ApiSpec>(
200204
req.decoded,
201205
E.matchW(
202206
(errs) => {
203-
const { response, statusCode } = (
204-
options?.onDecodeError ?? onDecodeError
205-
)(errs, req, res);
206-
res.status(statusCode).json(response).end();
207+
const { responseFn } = (options?.onDecodeError ?? onDecodeError)(
208+
errs,
209+
req,
210+
res,
211+
);
212+
responseFn();
207213
},
208214
(value) => {
209215
req.decoded = value;

packages/typed-express-router/src/types.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export type OnDecodeErrorFn = (
2828
errs: t.Errors,
2929
req: express.Request,
3030
res: express.Response,
31-
) => { response: Json; statusCode: number };
31+
) => { response: Json; statusCode: number; responseFn: () => void };
3232

3333
export type OnEncodeErrorFn = (
3434
err: unknown,
@@ -45,13 +45,12 @@ export type AfterEncodedResponseSentFn = (
4545

4646
export type UncheckedWrappedRouteOptions = {
4747
onEncodeError?: OnEncodeErrorFn;
48+
onDecodeError?: OnDecodeErrorFn;
4849
afterEncodedResponseSent?: AfterEncodedResponseSentFn;
4950
routeAliases?: string[];
5051
};
5152

52-
export type WrappedRouteOptions = UncheckedWrappedRouteOptions & {
53-
onDecodeError?: OnDecodeErrorFn;
54-
};
53+
export type WrappedRouteOptions = UncheckedWrappedRouteOptions;
5554

5655
export type WrappedRouterOptions = express.RouterOptions & WrappedRouteOptions;
5756

packages/typed-express-router/test/server.test.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,14 @@ test('should return a 400 when request fails to decode', async () => {
361361

362362
test('should invoke custom decode error function', async () => {
363363
const router = createRouter(TestApiSpec, {
364-
onDecodeError: (_errs, _req, _res) => {
365-
return { response: 'Custom decode error', statusCode: 400 };
364+
onDecodeError: (_errs, _req, res) => {
365+
return {
366+
response: 'Custom decode error',
367+
statusCode: 400,
368+
responseFn: () => {
369+
res.status(400).json('Custom decode error').end();
370+
},
371+
};
366372
},
367373
});
368374

@@ -390,8 +396,14 @@ test('should invoke custom decode error function', async () => {
390396

391397
test('should invoke per-route custom decode error function', async () => {
392398
const router = createRouter(TestApiSpec, {
393-
onDecodeError: (_errs, _req, _res) => {
394-
return { response: 'Top-level decode error', statusCode: 400 };
399+
onDecodeError: (_errs, _req, res) => {
400+
return {
401+
response: 'Top-level decode error',
402+
statusCode: 400,
403+
responseFn: () => {
404+
res.status(400).json('Top-level decode error').end();
405+
},
406+
};
395407
},
396408
});
397409

@@ -403,8 +415,14 @@ test('should invoke per-route custom decode error function', async () => {
403415
},
404416
],
405417
{
406-
onDecodeError: (_errs, _req, _res) => {
407-
return { response: 'Route decode error', statusCode: 400 };
418+
onDecodeError: (_errs, _req, res) => {
419+
return {
420+
response: 'Route decode error',
421+
statusCode: 400,
422+
responseFn: () => {
423+
res.status(400).json('Route decode error').end();
424+
},
425+
};
408426
},
409427
routeAliases: ['/helloNoPathParams'],
410428
},

packages/typed-express-router/test/telemetry.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,17 @@ beforeEach(async () => {
175175
const router = createRouter(TestApiSpec);
176176

177177
router.get('hello.world', [GetHelloWorld]);
178-
router.put('hello.world', [CreateHelloWorld]);
178+
router.put('hello.world', [CreateHelloWorld], {
179+
onDecodeError: (_errs, _req, res) => {
180+
return {
181+
response: 'Custom Decode Error',
182+
statusCode: 400,
183+
responseFn: () => {
184+
res.status(400).json({ error: 'Custom Decode Error' });
185+
},
186+
};
187+
},
188+
});
179189
router.get('hello.world.bad', [GetHelloWorldBad]);
180190

181191
app.use(router);
@@ -359,10 +369,18 @@ test('should capture decode errors', async () => {
359369
);
360370
assert.ok(decodeSpan, 'Encode span not found');
361371
assert.strictEqual(decodeSpan?.attributes?.[ApiTsAttributes.API_TS_PATH], '/hello');
372+
362373
const errorEvents = decodeSpan.events.filter((event) => {
363374
return event['name'] === 'exception';
364375
});
365376
assert.ok(errorEvents.length > 0, 'Expected at least 1 error event');
377+
378+
assert.ok(
379+
errorEvents.find(
380+
(event) => event.attributes?.['exception.message'] === '"Custom Decode Error"',
381+
),
382+
'Expected custom decode reporter message',
383+
);
366384
});
367385
});
368386

0 commit comments

Comments
 (0)