lukeautry / tsoa

Build OpenAPI-compliant REST APIs using TypeScript and Node
MIT License
3.33k stars 481 forks source link

ErrorHandlerMiddleware is ignore with 6.1.* #1598

Closed VincentClair closed 3 months ago

VincentClair commented 3 months ago

Sorting

Expected Behavior

When an error is thrown, custom ErrorHandlerMiddleware catches the error and returns a response with according errorCode (400, 403, 429, etc.). Works as expected with 6.0.*

Current Behavior

When an error is thrown, custom ErrorHandlerMiddleware is ignore and app hangs up, nothing happens. Breaks with upgrade to 6.1.4 and 6.1.5, and routes regenerated.

Context (Environment)

Version of the library: 6.1. Version of NodeJS: 20.

Breaking change?

It seems that is related to the new way of generated routes that handles requests. I have passed many hours to debug, but does not find something really more helpfull. Sorry !

github-actions[bot] commented 3 months ago

Hello there VincentClair 👋

Thank you for opening your very first issue in this project.

We will try to get back to you as soon as we can.👀

VincentClair commented 3 months ago

Code example :

import * as express from 'express';
import * as http from 'http';
import { APIError } from '../Error/APIError';

const app = express();
const server = http.createServer(app);

RegisterRoutes(app);

app.use((
        err: APIError,
        req: express.Request,
        res: express.Response,
        next: express.NextFunction,
    ) => {
        try {
            if (res.headersSent) {
                next(err);
                return;
            }

            res.status(err.code)
                .json({ success: false });
        } catch (e) {
            console.debug(e);
            res.status(500);
            next(e);
        }
    });

server.listen(8080, () => {
    logger.info(`Server listening on port: ${port}`);
});
import { Get, Route, Security } from 'tsoa';
import { APIError, ForbiddenAPIError, provideSingleton } from '../../src';

@Route('test')
@provideSingleton(TestController)
export class TestController {
    @Get('/success')
    public success(): Promise<{ success: true }> {
        // works as expected
        return Promise.resolve({ success: true });
    }

    @Get('/conflict')
    public conflict(): Promise<void> {
        // fails / hangs
        throw new APIError('test-conflict', null, 409);
    }
}
VincentClair commented 3 months ago

Find the soluton : put buildPromise inside try/catch

https://github.com/lukeautry/tsoa/blob/efdb468a5cedcb9bef345f1eed776e8fc621b443/packages/runtime/src/routeGeneration/templates/express/expressTemplateService.ts#L41 https://github.com/lukeautry/tsoa/blob/efdb468a5cedcb9bef345f1eed776e8fc621b443/packages/runtime/src/routeGeneration/templates/koa/koaTemplateService.ts#L43 https://github.com/lukeautry/tsoa/blob/efdb468a5cedcb9bef345f1eed776e8fc621b443/packages/runtime/src/routeGeneration/templates/hapi/hapiTemplateService.ts#L47

    async apiHandler(params) {
        const { methodName, controller, response, validatedArgs, successStatus, next } = params;
        try {
            const promise = this.buildPromise(methodName, controller, validatedArgs);
            const data = await Promise.resolve(promise);
            let statusCode = successStatus;
            let headers;
            if (this.isController(controller)) {
                headers = controller.getHeaders();
                statusCode = controller.getStatus() || statusCode;
            }
            this.returnHandler({ response, headers, statusCode, data });
        }
        catch (error) {
            return next(error);
        }
    }
jackey8616 commented 3 months ago

@VincentClair thank you for locate the missing part, could you submit a PR including test cases let tsoa prevent such issue in the future?

VincentClair commented 3 months ago

@jackey8616 have you an advice how to test this case ? I suppose it's more an integration test to put in *-server.spec.ts ?

jackey8616 commented 3 months ago

@jackey8616 have you an advice how to test this case ?

I suppose it's more an integration test to put in *-server.spec.ts ?

Yes, you should add one test case in express, koa, hapi integration test.

You can take fixtures/getController.ts as example, duplicate the method with throwing an error and add handle case in .spec.ts of 3 of that framework.

VincentClair commented 3 months ago

Sorry, but i haven't find a way to add a proper test and I have no more time to pass on it.

It seems the solution has to be completed: when we use inversify (and probably all other solutions ?) we have to await for apiHandler to execute, so errors could be catched and pass to next(err). Otherwise, errors could pass through tr/catch due to async code.

https://github.com/lukeautry/tsoa/blob/efdb468a5cedcb9bef345f1eed776e8fc621b443/packages/cli/src/routeGeneration/templates/express.hbs#L106

{{#if ../../iocModule}}async {{/if}}templateService.apiHandler({
jackey8616 commented 3 months ago

Sorry, but i haven't find a way to add a proper test and I have no more time to pass on it.

It seems the solution has to be completed: when we use inversify (and probably all other solutions ?) we have to await for apiHandler to execute, so errors could be catched and pass to next(err). Otherwise, errors could pass through tr/catch due to async code.

https://github.com/lukeautry/tsoa/blob/efdb468a5cedcb9bef345f1eed776e8fc621b443/packages/cli/src/routeGeneration/templates/express.hbs#L106

{{#if ../../iocModule}}async {{/if}}templateService.apiHandler({

Never mind, I will take rest of things, thanks for your informations. I will submit a PR to fix this ASAP.

jackey8616 commented 3 months ago

@VincentClair can you provides more context, everything seems works fine with error handler. test cases is covered with throwing error inside controller, and it can be correctly catched from outside global error handler.

VincentClair commented 3 months ago

Finally found the problem: if the throwing method is not async, error is neither caught by apiHandler nor generated template.

I have made a PR https://github.com/lukeautry/tsoa/pull/1602

VincentClair commented 3 months ago

@WoH When did you plan to publish the fix please ?