routing-controllers icon indicating copy to clipboard operation
routing-controllers copied to clipboard

question: How do I customize internal HttpErrors with custom Http errors with KoaDriver?

Open coneforapine opened this issue 3 years ago • 4 comments

I was sending custom HttpErrors that changes the response to the user but I can't do this when I'm validating request bodies because it's using the one that came with the library.

This is the response I want to throw when a validation fails;

{
    "status": false,
    "message": "Validation errors.",
    "errors": "ValidationError[] from class validator"
}

This is what it currently returns

{
    "name": "BadRequestError",
    "message": "Invalid body, check 'errors' property for more info.",
     "errors": "ValidationError[] from class validator"
}

One way that I could find is to use this but since I'm using Koa I can't use it.

One workaround for this is disabling the validator from @Body() and calling class-validator inside the request function.

coneforapine avatar Jan 24 '21 15:01 coneforapine

@coneforapine did you find the solution? i have the same issue with the expressjs. I do like this:

export class CreateUserDto {
    @IsString({ message: 'lastName_should_be_string'.toUpperCase() })
    lastname: string;
}

And this is my custom error handler:

export class Response<T> {
    successful: boolean;
    message?: string;
    data: T;

    constructor(success: boolean, data: T, message?: string) {
        this.successful = success;
        this.data = data;
        if (message) this.message = message;
    }
}

@Middleware({ type: 'after' })
export class CustomErrorHandler implements ExpressErrorMiddlewareInterface {
    error(
        error: HttpError | ValidationError | UnauthorizedError,
        request: Request,
        response: expressResponse,
    ): void {
        logger.error('http error occurred', {
            meta: {
                ...serializeError(error),
            },
        });
        if (error instanceof HttpError) {
            response
                .status(error.httpCode)
                .json(new Response<null>(false, null, error.message));
        } else if (error instanceof ValidationError) {
            response
                .status(400)
                .json(new Response(false, { errors: error.constraints }));
        } else if (error instanceof UnauthorizedError) {
            response
                .status(401)
                .json(new Response<null>(false, null, 'unauthorized'));
        } else {
            response
                .status(500)
                .json(new Response(false, {}, 'INTERNAL_SERVER_ERROR'));
        }
    }
}


const routingControllersOptions = {
    controllers: [ /* controllers listed here */ ],
    middlewares: [CustomErrorHandler],
    routePrefix: '/api',
    defaultErrorHandler: false,
};

and I get :

{
    "success": false,
    "data": null,
    "message": "Invalid body, check 'errors' property for more info.",
}

and this is what I expect to receive :

{
    "success": false,
    "data": null,
    "message": "LASTNAME_SHOULD_BE_STRING",
}

@MichalLytek @jotamorais what do you think. what is my mistake?

kasir-barati avatar Aug 26 '21 11:08 kasir-barati

I got the problem, When I turn false to true in defaultErrorHandler i get a long response but that is too much for me:

{
    "name": "BadRequestError",
    "message": "Invalid body, check 'errors' property for more info.",
    "stack": "Error: \n    at new HttpError (/home/kasir/Salam-Sakhteman/infiniti-platform-server/node_modules/src/http-error/HttpError.ts:16:18)\n    at new BadRequestError (/home/kasir/Salam-Sakhteman/infiniti-platform-server/node_modules/src/http-error/BadRequestError.ts:10:5)\n    at /home/kasir/Salam-Sakhteman/infiniti-platform-server/node_modules/src/ActionParameterHandler.ts:233:30\n    at async ActionParameterHandler.normalizeParamValue (/home/kasir/Salam-Sakhteman/infiniti-platform-server/node_modules/src/ActionParameterHandler.ts:141:15)\n    at async Promise.all (index 0)",
    "errors": [
        {
            "target": {
                "user": {
                    "username": "ss-53111700",
                    "name": "سلام",
                    "lastname": "تست میکنم",
                    "phoneNumber": false
                },
                "serviceType": "5f930a87b509a90012591a00",
                "location": {
                    "province": "5e196d4fc75ee00018183bed",
                    "city": "5f1d4bc02811b00011d6b331"
                },
                "provinces": [
                    "5ef07dcb492cc20018681060",
                    "5e196d58c75ee00018183bee"
                ],
                "step": "first-sign"
            },
            "value": {
                "username": "ss-53111700",
                "name": "سلام",
                "lastname": "تست میکنم",
                "phoneNumber": false
            },
            "property": "user",
            "children": [
                {
                    "target": {
                        "username": "ss-53111700",
                        "name": "سلام",
                        "lastname": "تست میکنم",
                        "phoneNumber": false
                    },
                    "value": false,
                    "property": "phoneNumber",
                    "children": [],
                    "constraints": {
                        "matches": "PHONENUMBER_SHOULD_BE_IRANIAN_PHONE_NUMBER"
                    }
                }
            ]
        },
        {
            "target": {
                "user": {
                    "username": "ss-53111700",
                    "name": "سلام",
                    "lastname": "تست میکنم",
                    "phoneNumber": false
                },
                "serviceType": "5f930a87b509a90012591a00",
                "location": {
                    "province": "5e196d4fc75ee00018183bed",
                    "city": "5f1d4bc02811b00011d6b331"
                },
                "provinces": [
                    "5ef07dcb492cc20018681060",
                    "5e196d58c75ee00018183bee"
                ],
                "step": "first-sign"
            },
            "value": {
                "province": "5e196d4fc75ee00018183bed",
                "city": "5f1d4bc02811b00011d6b331"
            },
            "property": "location",
            "children": [
                {
                    "target": {
                        "province": "5e196d4fc75ee00018183bed",
                        "city": "5f1d4bc02811b00011d6b331"
                    },
                    "property": "locationType",
                    "children": [],
                    "constraints": {
                        "isEnum": "LOCATIONTYPE_VALUE_SHOULD_BE_VALID",
                        "isString": "NAME_SHOULD_BE_STRING"
                    }
                }
            ]
        }
    ]
}

But my question is why the error is instanceof HttpError (import { HttpError } from 'routing-controllers';) not ValidationError (import { ValidationError } from 'class-validator';) and the error.errors is of type ValidationError[] so i did like this:

class CustomValidationError {
    errors: ValidationError[];
}

if ('errors' in error && error.errors[0] instanceof CustomValidationError) {
            const errorMessages: string[] = [];
            for (let temp of error.errors) {
                errorMessages.push(
                    ...(temp.constraints
                        ? Object.values(temp.constraints)
                        : ''),
                );
            }
            response.status(400).json(new ErrorResponse(false, errorMessages));
}

do you know simpler way?

kasir-barati avatar Aug 26 '21 11:08 kasir-barati

If I remember correctly you can use custom error handlers with express.

Here's the doc for the interface.

coneforapine avatar Aug 26 '21 15:08 coneforapine

If I remember correctly you can use custom error handlers with express.

Here's the doc for the interface.

Hi, I solve it. IDK why, but the ValidationError was in the error.errors and it was an array of ValidationError. so i did this kind of trick to send the error message to the client:

class CustomValidationError {
    errors: ValidationError[];
}

/* CustomErrorHandler class - error method */
if (
    'errors' in error &&
    error.errors[0] instanceof ValidationError
) {
    const errorMessages: { [x: string]: string }[] = findProp(
        error,
        'constraints',
    );

    response
        .status(400)
        .json(new ErrorResponse(false, getValues(errorMessages)));
}

function findProp(obj: any, key: string, result: any[] = []): any[] {
    const proto = Object.prototype;
    const ts = proto.toString;
    const hasOwn = proto.hasOwnProperty.bind(obj);

    if ('[object Array]' !== ts.call(result)) {
        result = [];
    }

    for (let i in obj) {
        if (hasOwn(i)) {
            if (i === key) {
                result.push(obj[i]);
            } else if (
                '[object Array]' === ts.call(obj[i]) ||
                '[object Object]' === ts.call(obj[i])
            ) {
                findProp(obj[i], key, result);
            }
        }
    }

    return result;
}

function getValues(arrayOfObjects: { [x: string]: string }[]): string[] {
    const result: string[] = [];

    for (let item of arrayOfObjects) {
        result.push(...Object.values(item));
    }

    return result;
}

and with this trick, I resolve it. HOPE this would be helpful later. :dart:

kasir-barati avatar Aug 28 '21 10:08 kasir-barati

Closing this as stale.

If the issue still persists, you may open a new Q&A in the discussions tab and someone from the community may be able to help.

attilaorosz avatar Dec 21 '22 15:12 attilaorosz

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

github-actions[bot] avatar Jan 21 '23 00:01 github-actions[bot]