From f7445976f1ff8cac223c4c85dea0a855bcf20d05 Mon Sep 17 00:00:00 2001 From: Pooya Raki Date: Tue, 7 Jan 2025 14:42:37 +0100 Subject: [PATCH 1/2] Return relevant error code in logger interceptor --- .../interceptors/route-logger.interceptor.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/routes/common/interceptors/route-logger.interceptor.ts b/src/routes/common/interceptors/route-logger.interceptor.ts index 5dc22bec01..8787ff345d 100644 --- a/src/routes/common/interceptors/route-logger.interceptor.ts +++ b/src/routes/common/interceptors/route-logger.interceptor.ts @@ -10,10 +10,9 @@ import { ILoggingService, LoggingService } from '@/logging/logging.interface'; import { Inject } from '@nestjs/common/decorators'; import { Observable, tap } from 'rxjs'; import { formatRouteLogMessage } from '@/logging/utils'; -import { DataSourceError } from '@/domain/errors/data-source.error'; import { Request, Response } from 'express'; import isNumber from 'lodash/isNumber'; -import { ZodErrorWithCode } from '@/validation/pipes/validation.pipe'; +import { ZodError } from 'zod'; /** * The {@link RouteLoggerInterceptor} is an interceptor that logs the requests @@ -64,17 +63,13 @@ export class RouteLoggerInterceptor implements NestInterceptor { * @private */ private onError(request: Request, error: Error, startTimeMs: number): void { - let statusCode; - if (error instanceof HttpException) { - statusCode = error.getStatus(); - } else if (error instanceof DataSourceError) { - statusCode = error.code ?? HttpStatus.INTERNAL_SERVER_ERROR; - } else if (error instanceof ZodErrorWithCode) { - statusCode = error.code; - } else if ('code' in error && isNumber(error.code)) { + let statusCode: number = HttpStatus.INTERNAL_SERVER_ERROR; + if ('code' in error && isNumber(error.code)) { statusCode = error.code; - } else { - statusCode = HttpStatus.INTERNAL_SERVER_ERROR; + } else if (error instanceof HttpException) { + statusCode = error.getStatus(); + } else if (error instanceof ZodError) { + statusCode = HttpStatus.UNPROCESSABLE_ENTITY; } const message = formatRouteLogMessage( From 81b7c99c98b439dd2ace97a9e4ffeaae6d81fbf9 Mon Sep 17 00:00:00 2001 From: Pooya Raki Date: Wed, 8 Jan 2025 11:39:47 +0100 Subject: [PATCH 2/2] Add unit tests --- .../route-logger.interceptor.spec.ts | 29 +++++++++++++++++++ .../interceptors/route-logger.interceptor.ts | 3 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/routes/common/interceptors/route-logger.interceptor.spec.ts b/src/routes/common/interceptors/route-logger.interceptor.spec.ts index 8fe1c28ad7..0d333519c8 100644 --- a/src/routes/common/interceptors/route-logger.interceptor.spec.ts +++ b/src/routes/common/interceptors/route-logger.interceptor.spec.ts @@ -15,6 +15,7 @@ import { RouteLoggerInterceptor } from '@/routes/common/interceptors/route-logge import { Server } from 'net'; import { ValidationPipe } from '@/validation/pipes/validation.pipe'; import { NumericStringSchema } from '@/validation/entities/schemas/numeric-string.schema'; +import { ZodError } from 'zod'; // We expect 500 instead of the status code of the DataSourceError // The reason is that this test webserver does not have logic to map @@ -62,6 +63,11 @@ class TestController { ): void {} /* eslint-enable @typescript-eslint/no-unused-vars */ + @Get('zod-error') + zodError(): never { + throw new ZodError([]); + } + @Get('error-level-info-with-code') errorLevelInfoWithCode(): void { throw new ErrorWithCode('error', 430); @@ -233,6 +239,29 @@ describe('RouteLoggerInterceptor tests', () => { expect(mockLoggingService.warn).not.toHaveBeenCalled(); }); + it('400 Zod error triggers info level', async () => { + await request(app.getHttpServer()) + .get('/test/zod-error') + .expect(expectedDatasourceErrorCode); + + expect(mockLoggingService.error).toHaveBeenCalledTimes(1); + expect(mockLoggingService.error).toHaveBeenCalledWith({ + chain_id: null, + client_ip: null, + detail: '[]', + method: 'GET', + path: '/test/zod-error', + response_time_ms: expect.any(Number), + route: '/test/zod-error', + safe_app_user_agent: null, + status_code: 502, + origin: null, + }); + expect(mockLoggingService.info).not.toHaveBeenCalled(); + expect(mockLoggingService.debug).not.toHaveBeenCalled(); + expect(mockLoggingService.warn).not.toHaveBeenCalled(); + }); + it('400 Any error triggers info level', async () => { await request(app.getHttpServer()) .get('/test/error-level-info-with-code') diff --git a/src/routes/common/interceptors/route-logger.interceptor.ts b/src/routes/common/interceptors/route-logger.interceptor.ts index 8787ff345d..92b0876ac1 100644 --- a/src/routes/common/interceptors/route-logger.interceptor.ts +++ b/src/routes/common/interceptors/route-logger.interceptor.ts @@ -69,7 +69,8 @@ export class RouteLoggerInterceptor implements NestInterceptor { } else if (error instanceof HttpException) { statusCode = error.getStatus(); } else if (error instanceof ZodError) { - statusCode = HttpStatus.UNPROCESSABLE_ENTITY; + // Since we mainly use Zod for Datasource validation, we should throw a 502 Bad Gateway instead of a 422 Unprocessable Entity + statusCode = HttpStatus.BAD_GATEWAY; } const message = formatRouteLogMessage(