From 8325c8cec8942b639a821a3afb83481208b4a19c Mon Sep 17 00:00:00 2001 From: "Diego Armando O. Meneses" Date: Fri, 15 Dec 2023 06:09:13 -0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8feat(main):=20implementa=20middleware?= =?UTF-8?q?=20customizado=20para=20log=20e=20tratamento=20de=20erro=20|=20?= =?UTF-8?q?Parte=2036?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implementa Classes Específicas para Erros HTTP - Implementa e Configura o "Middleware" para "Log" de Erro - Implementa e Configura o "Middleware" para Responder Erros - Refatora o "Controller" Recuperar Categoria Por Id para Interceptar Erros e Usar Erros HTTP Apropriados - Refatorando o "Middleware" para Verificação de "Content-Types"para Usar Classe que Representa Erro HTTP Relacionado - Implementa e Configura o "Middleware" para Lidar com Rotas Inexistentes --- src/main/presentation/http/app.express.ts | 6 ++++ .../middlewares/content-type.middleware.ts | 3 +- .../middlewares/error-logger.middleware.ts | 20 +++++++++++ .../middlewares/error-responser.middleware.ts | 14 ++++++++ .../middlewares/invalid-path.middleware.ts | 10 ++++++ ...ategoria-por-id.express.controller.spec.ts | 3 +- ...rar-categoria-por-id.express.controller.ts | 5 +++ src/shared/presentation/http/http.error.ts | 36 +++++++++++++++++++ 8 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/main/presentation/http/middlewares/error-logger.middleware.ts create mode 100644 src/main/presentation/http/middlewares/error-responser.middleware.ts create mode 100644 src/main/presentation/http/middlewares/invalid-path.middleware.ts create mode 100644 src/shared/presentation/http/http.error.ts diff --git a/src/main/presentation/http/app.express.ts b/src/main/presentation/http/app.express.ts index f87595e..0eaafee 100644 --- a/src/main/presentation/http/app.express.ts +++ b/src/main/presentation/http/app.express.ts @@ -5,6 +5,9 @@ import helmet from "helmet"; import compression from "compression"; import { customMorgan } from "./middlewares/custom-morgan.middleware"; import { logger } from "@shared/helpers/logger.winston"; +import { errorLogger } from "./middlewares/error-logger.middleware"; +import { errorResponder } from "./middlewares/error-responser.middleware"; +import { invalidPath } from "./middlewares/invalid-path.middleware"; const createExpressApplication = async (): Promise => { const app: Application = express(); @@ -25,6 +28,9 @@ const createExpressApplication = async (): Promise => { app.use('/api/v1', apiv1Router); //Middleware de Tratamento de Erros (Error Handling) + app.use(invalidPath); + app.use(errorLogger); + app.use(errorResponder); return app; } diff --git a/src/main/presentation/http/middlewares/content-type.middleware.ts b/src/main/presentation/http/middlewares/content-type.middleware.ts index 29d995f..59c206b 100644 --- a/src/main/presentation/http/middlewares/content-type.middleware.ts +++ b/src/main/presentation/http/middlewares/content-type.middleware.ts @@ -1,3 +1,4 @@ +import { HttpErrors } from "@shared/presentation/http/http.error"; import { NextFunction, Request, Response } from "express"; const allowedContentTypes = ['application/json']; @@ -7,7 +8,7 @@ const contentTypeMiddleware = (request: Request, response: Response, next: NextF const contentType = request.headers['content-type']; if (!contentType || !allowedContentTypes.includes(contentType)) { - return response.status(415).send('Tipo de Mídia Não Suportado'); + next(new HttpErrors.UnsupportedMediaTypeError()); } next(); diff --git a/src/main/presentation/http/middlewares/error-logger.middleware.ts b/src/main/presentation/http/middlewares/error-logger.middleware.ts new file mode 100644 index 0000000..4901bf3 --- /dev/null +++ b/src/main/presentation/http/middlewares/error-logger.middleware.ts @@ -0,0 +1,20 @@ +import { logger } from "@shared/helpers/logger.winston"; +import { HttpError } from "@shared/presentation/http/http.error"; +import { NextFunction, Request, Response } from "express"; + +const errorLoggerMiddleware = (error: HttpError, request: Request, response: Response, next: NextFunction) => { + let statusCode = error.statusCode || 500; + + const logErro = JSON.stringify({ + name: error.name, + statusCode: statusCode, + message: error.message, + stack: process.env.NODE_ENV === 'development' ? error.stack : {} + }, null, 2); + + logger.error(logErro); + + next(error); +} + +export { errorLoggerMiddleware as errorLogger } \ No newline at end of file diff --git a/src/main/presentation/http/middlewares/error-responser.middleware.ts b/src/main/presentation/http/middlewares/error-responser.middleware.ts new file mode 100644 index 0000000..baa4db0 --- /dev/null +++ b/src/main/presentation/http/middlewares/error-responser.middleware.ts @@ -0,0 +1,14 @@ +import { HttpError } from "@shared/presentation/http/http.error"; +import { NextFunction, Request, Response } from "express"; + +const errorResponderMiddleware = (error: HttpError, request: Request, response: Response, next: NextFunction) => { + let statusCode = error.statusCode || 500; + response.status(statusCode).json({ + name: error.name, + statusCode: statusCode, + message: error.message, + stack: process.env.NODE_ENV === 'development' ? error.stack : {} + }); +} + +export { errorResponderMiddleware as errorResponder } \ No newline at end of file diff --git a/src/main/presentation/http/middlewares/invalid-path.middleware.ts b/src/main/presentation/http/middlewares/invalid-path.middleware.ts new file mode 100644 index 0000000..2f28691 --- /dev/null +++ b/src/main/presentation/http/middlewares/invalid-path.middleware.ts @@ -0,0 +1,10 @@ +import { HttpErrors } from "@shared/presentation/http/http.error"; +import { NextFunction, Request, Response } from "express"; + +//Lança um erro 404 para caminhos indefinidos que vai ser tratado pelos middlewares de erros (log de erro e o responder de erro) +const invalidPathMiddleware = (request: Request, response: Response, next: NextFunction) => { + const error = new HttpErrors.NotFoundError(); + next(error); +} + +export { invalidPathMiddleware as invalidPath } \ No newline at end of file diff --git a/src/modules/catalogo/presentation/http/rest/controllers/recuperar-categoria-por-id.express.controller.spec.ts b/src/modules/catalogo/presentation/http/rest/controllers/recuperar-categoria-por-id.express.controller.spec.ts index 06da6a5..fd63571 100644 --- a/src/modules/catalogo/presentation/http/rest/controllers/recuperar-categoria-por-id.express.controller.spec.ts +++ b/src/modules/catalogo/presentation/http/rest/controllers/recuperar-categoria-por-id.express.controller.spec.ts @@ -5,6 +5,7 @@ import { MockProxy, mock, mockReset } from "vitest-mock-extended"; import { RecuperarCategoriaPorIdExpressController } from "./recuperar-categoria-por-id.express.controller"; import { ICategoria } from "@modules/catalogo/domain/categoria/categoria.types"; import { CategoriaApplicationExceptions } from "@modules/catalogo/application/exceptions/categoria.application.exception"; +import { HttpError, HttpErrors } from "@shared/presentation/http/http.error"; let requestMock: MockProxy; @@ -70,7 +71,7 @@ describe('Controller Express: Recuperar Categoria por ID', () => { expect(recuperarCategoriaPorIdUseCaseMock.execute).toHaveBeenCalledWith(categoriaInputDTO.id); expect(nextMock).toHaveBeenCalled(); - expect(nextMock.mock.lastCall[0].name).toBe(CategoriaApplicationExceptions.CategoriaNaoEncontrada.name); + expect(nextMock.mock.lastCall[0].name).toBe(HttpErrors.NotFoundError.name); }); diff --git a/src/modules/catalogo/presentation/http/rest/controllers/recuperar-categoria-por-id.express.controller.ts b/src/modules/catalogo/presentation/http/rest/controllers/recuperar-categoria-por-id.express.controller.ts index 0e237d2..38f0fac 100644 --- a/src/modules/catalogo/presentation/http/rest/controllers/recuperar-categoria-por-id.express.controller.ts +++ b/src/modules/catalogo/presentation/http/rest/controllers/recuperar-categoria-por-id.express.controller.ts @@ -1,6 +1,8 @@ +import { CategoriaApplicationExceptions } from "@modules/catalogo/application/exceptions/categoria.application.exception"; import { RecuperarCategoriaPorIdUseCase } from "@modules/catalogo/application/use-cases/recuperar-categoria-por-id/recuperar-categoria-por-id.use-case"; import { ICategoria } from "@modules/catalogo/domain/categoria/categoria.types"; import { ExpressController } from "@shared/presentation/http/express.controller"; +import { HttpErrors } from "@shared/presentation/http/http.error"; import { NextFunction, Request, Response } from "express"; class RecuperarCategoriaPorIdExpressController extends ExpressController { @@ -19,6 +21,9 @@ class RecuperarCategoriaPorIdExpressController extends ExpressController { this.sendSuccessResponse(response,categoriaOutputDTO); } catch (error) { + if (error instanceof CategoriaApplicationExceptions.CategoriaNaoEncontrada){ + error = new HttpErrors.NotFoundError({ message: error.message }); + } next(error); } } diff --git a/src/shared/presentation/http/http.error.ts b/src/shared/presentation/http/http.error.ts new file mode 100644 index 0000000..31b083d --- /dev/null +++ b/src/shared/presentation/http/http.error.ts @@ -0,0 +1,36 @@ +class HttpError extends Error { + statusCode: number; + + constructor(statusCode:number, message: string = '⚠️ Erro HTTP genérico') { + super(message); + this.name = 'HttpError'; + this.statusCode = statusCode; + this.message = message; + Object.setPrototypeOf(this, HttpError.prototype); + Error.captureStackTrace(this, this.constructor); + } + +} + +class NotFoundError extends HttpError { + constructor( params?: {statusCode?: number, message?: string}) { + const { statusCode, message} = params || {}; + super(statusCode || 404, message || '⚠️ Servidor Não Conseguiu Encontrar o Recurso Solicitado.'); + this.name = 'NotFoundError'; + } +} + +class UnsupportedMediaTypeError extends HttpError { + constructor( params?: {statusCode?: number, message?: string}) { + const { statusCode, message} = params || {}; + super(statusCode || 415, message || '⚠️ Servidor se Recusou a Aceitar a Requisição Porque o Formato do Payload Não é Um Formato Suportado.'); + this.name = 'UnsupportedMediaTypeError'; + } +} + +const HttpErrors = { + NotFoundError: NotFoundError, + UnsupportedMediaTypeError: UnsupportedMediaTypeError +} + +export { HttpError, HttpErrors } \ No newline at end of file