본문 바로가기

Backend/NestJS

[NestJS] 커스텀 유효성 검사기 작성(커스텀 데커레이터)

728x90
커스텀 데커레이터로 유효성 검사기를 직접 만든 기록이다.

지난 기록에서 Nest의 Pipe에 대해 다룬 적이 있다.

 

[NestJS] NestJS 유효성 파이프 적용 이해하기(ValidationPipe)

validationpipe를 이용하는 것이 구체적으로 이해가 가지 않아 이에 대한 이해 과정 기록이다. 개요 이번 기록에서는 아래의 코드를 이해하는 것이 목적이다. //Boards.controller.ts @Post() @UsePipes(ValidationP

choi-records.tistory.com

이번 기록에서는 유효성 검사 데커레이터를 직접 만들어서 사용해 보려고 한다.

개요

@MinLength, @IsString 등 class-validator에서 제공하는 커스텀 데커레이터로 유효성 검사를 했다.

이번엔 직접 커스텀 데커레이터를 생성할 것이다.

 

class-validator는 custom validation decorators를 직접 생성하는 기능을 제공한다.

 

GitHub - typestack/class-validator: Decorator-based property validation for classes.

Decorator-based property validation for classes. Contribute to typestack/class-validator development by creating an account on GitHub.

github.com

registerDecorator를 이용해 커스텀 데커레이터를 생성해 보자.

 

이전 기록에서 구현하던 user api에서

회원가입 기능 중 ID와 Password의 관련성을 없애려고 한다.

Password가 ID가 포함되어 있지 않도록 해당 역할을 하는 데커레이터를 생성할 것이다.


구현

import {
  registerDecorator,
  ValidationArguments,
  ValidationOptions,
} from 'class-validator';

export function NotIn(property: string, validationOptions: ValidationOptions) {
  return (object: Object, propertyName: string) => {
    registerDecorator({
      name: 'NotIn',
      target: object.constructor,
      propertyName,
      options: validationOptions,
      constraints: [property],
      validator: {
        validate(value: any, args: ValidationArguments) {
          const [relatedPropertyName] = args.constraints;
          const relatedValue = (args.object as any)[relatedPropertyName];
          return (
            typeof value === 'string' &&
            typeof relatedValue === 'string' &&
            !relatedValue.includes(value)
          );
        },
      },
    });
  };
}

구현된 코드를 보고, 이해를 해보자.

 

NotIn

가장 바깥의 함수이다.

사용될 때는 @NotIn()의 모습일 것이다.

따라서 비교하려는 다른 속성의 이름(property)과 다른 ValidationOptions를 인자로 전달받는다.

 

registerDecorator를 호출하는 함수를 리턴한다.

이 익명 함수는 데커레이터가 선언될 객체(object)속성 이름(propertyName)을 전달받는다.

 

해당 데커레이터는 DTO 객체에서 사용될 것이며

타겟 속성과 다른 속성을 비교하는 데커레이터이다.

따라서 선언될 객체는 DTO 객체이고, 속성 이름은 타겟이 될 속성 이름일 것이다.

 

registerDecorator

registerDecorator는 registerDecoratorOptions 객체를 인수로 받는다.

export interface ValidationDecoratorOptions {
    /**
     * Target object to be validated.
     */
    target: Function;
    /**
     * Target object's property name to be validated.
     */
    propertyName: string;
    /**
     * Name of the validation that is being registered.
     */
    name?: string;
    /**
     * Indicates if this decorator will perform async validation.
     */
    async?: boolean;
    /**
     * Validator options.
     */
    options?: ValidationOptions;
    /**
     * Array of validation constraints.
     */
    constraints?: any[];
    /**
     * Validator that performs validation.
     */
    validator: ValidatorConstraintInterface | Function;
}
/**
 * Registers a custom validation decorator.
 */
export declare function registerDecorator(options: ValidationDecoratorOptions): void;
  • target : 유효성 검사를 거칠 DTO의 생성자가 타겟 객체가 된다.
  • name : 생성될 유효성 검사의 이름이다.
  • propertyName : 타겟 속성 즉, DTO에서 타겟으로 결정될 속성의 이름으로 이미 익명 함수를 통해 전달받았다.
  • options : 유효성 검사 옵션이다.
  • constraints : 데커레이터를 실행시킬 때의 제약이다.
  • validator : 유효성 검사를 해줄 함수이다.

validatior

제일 중요한 유효성 검사를 해주는 역할이다.

validator는 ValidatorConstraintInterface를 실행한다.

export interface ValidatorConstraintInterface {
    /**
     * Method to be called to perform custom validation over given value.
     */
    validate(value: any, validationArguments?: ValidationArguments): Promise<boolean> | boolean;
    /**
     * Gets default message when validation for this constraint fail.
     */
    defaultMessage?(validationArguments?: ValidationArguments): string;
}

validate 메서드를 통해 커스텀 유효성 검사를 진행한다.

 

validate메서드의 valueDTO 타겟 속성의 값을 말한다.

export interface ValidationArguments {
    /**
     * Validating value.
     */
    value: any;
    /**
     * Constraints set by this validation type.
     */
    constraints: any[];
    /**
     * Name of the target that is being validated.
     */
    targetName: string;
    /**
     * Object that is being validated.
     */
    object: object;
    /**
     * Name of the object's property being validated.
     */
    property: string;
}

validationArguments타겟 속성과 DTO 객체에 대한 정보, 그리고 위에서 지정한 constraints 값을 담고 있다.

validate(value: any, args: ValidationArguments) {
          const [relatedPropertyName] = args.constraints;
          const relatedValue = (args.object as any)[relatedPropertyName];
          return (
            typeof value === 'string' &&
            typeof relatedValue === 'string' &&
            !relatedValue.includes(value)
          );
        }

relatedPropertyName은 constraints에서 지정한 배열의 값을 가져온 변수이다.

 

지금 하려는 작업은 password에 Id가 포함되어 있는지 확인하는 것이다.

그럼 ID 속성이 target value가 될 것이고, password 속성이 related value가 될 것이다.

args.object는 DTO 객체를 참조하고 이를 통해 password 값을 가져온 것이다.

 

결과적으로 password에 ID 값이 포함되어있지 않으면 유효하다고 판단할 것이다.


참고

 

07장 파이프와 유효성 검사 - 요청이 제대로 전달되었는가

.

wikidocs.net

 

GitHub - typestack/class-validator: Decorator-based property validation for classes.

Decorator-based property validation for classes. Contribute to typestack/class-validator development by creating an account on GitHub.

github.com

마치며

잘못된 정보에 대한 피드백은 환영입니다.

감사합니다.

728x90