LoginSignup
0
0

More than 3 years have passed since last update.

バリデーションレスポンスをカスタマイズする[NestJS]

Last updated at Posted at 2020-07-31

やりたいこと

NestJS の Web API は class-validator を使ってリクエストを検証できる。
そして、そこで引っかかった項目をまとめて 400 のエラーを返してくれる。
こんな感じ。

{
    "statusCode": 400,
    "message": [
        "each value in {field} must be a valid enum value"
    ],
    "error": "Bad Request",
}

このフォーマットを好きな形に変更したい。

やりかた

このレスポンスは class-validation が生成したクラスインスタンスからエラーメッセージ取り出して Nest の Exception クラスを作成することによって作られている。
そして、それは ValidationPipe の中で行われている。

なので、ここの実装をいじってあげることで実現できる。

ValidationPipe ってなに

main.ts のここに書かれてるやつ。

app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ transform: true }));
await app.init();

実装する

ValidationPipe を拡張する形で作成。
(private なプロパティが多かったので、この実装方法はあまり想定されていないのかも?)

使っている BadRequestException は独自に実装したもので、ここでレスポンスの形を決めている。
BadRequestException の実装については NestJS でエラーレスポンスをカスタマイズしてみる ここで書いている。

import { ValidationPipe, HttpStatus } from '@nestjs/common';
import { ValidationError } from 'class-validator';
import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util';
import { iterate } from 'iterare';
import { BadRequestException } from 'src/exception/badRequest.exception';

export class CustomValidationPipe extends ValidationPipe {
    createExceptionFactory() {
        return (validationErrors: ValidationError[] = []) => {
            if (this.isDetailedOutputDisabled) {
                return new HttpErrorByCode[this.errorHttpStatusCode]();
            }
            const errors = this.flattenValidationErrorsExtends(
                validationErrors,
            );
            // Bad Request 以外でも使いたければここらへんの実装をいじる
            if (this.errorHttpStatusCode === HttpStatus.BAD_REQUEST)
                return new BadRequestException(errors);
            return new HttpErrorByCode[this.errorHttpStatusCode](errors);
        };
    }
    private flattenValidationErrorsExtends(
        validationErrors: ValidationError[],
    ): string[] {
        return iterate(validationErrors)
            .map(error => this.mapChildrenToValidationErrorsExtends(error))
            .flatten()
            .filter(item => !!item.constraints)
            .map(item => item.constraints && Object.values(item.constraints))
            .flatten()
            .toArray()
            .filter(
                (item): item is Exclude<typeof item, undefined> =>
                    item !== undefined,
            );
    }
    private mapChildrenToValidationErrorsExtends(error: ValidationError) {
        if (!(error.children && error.children.length)) {
            return [error];
        }
        const validationErrors: ValidationError[] = [];
        for (const item of error.children) {
            if (item.children && item.children.length) {
                validationErrors.push(
                    ...this.mapChildrenToValidationErrorsExtends(item),
                );
            }
            validationErrors.push(
                this.prependConstraintsWithParentPropExtends(error, item),
            );
        }
        return validationErrors;
    }
    private prependConstraintsWithParentPropExtends(
        parentError: ValidationError,
        error: ValidationError,
    ) {
        const constraints: { [key: string]: string } = {};
        for (const key in error.constraints) {
            constraints[
                key
            ] = `${parentError.property}.${error.constraints[key]}`;
        }
        return Object.assign(Object.assign({}, error), { constraints });
    }
}

使ってみる

main.ts

こんな感じで使ってあげれば、指定した形でレスポンスが返ってくる。

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { Logger, INestApplication } from '@nestjs/common';
import { Config } from './app.config';
import { CustomValidationPipe } from 'src/presentation/pipe/customValidation.pipe';

async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    app.useGlobalPipes(
        new CustomValidationPipe({
            transform: true,
        }),
    );

    await app.init();
    await app.listen(3000);
}
bootstrap();
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0