Validation - Path 파라미터

저뉼(스님?)·2024년 2월 18일
0

나만의 문제해결

목록 보기
2/3
post-custom-banner

기준: nestjs 10.3.3, class-validator 0.14.1, class-transformer 0.5.1


Object를 통해 받는 경우

NestJS 공식 문서에서 아래와 같은 코드를 사용하고 있다.

@Get(':id')
findOne(@Param() params: FindOneParams) {
  return 'This action returns a user';
}

object의 프로퍼티 id로 endpoint path의 :id 값을 받고 있다.
(object를 통하지 않고 직접 받는 방법에 관해서도 아래에서 다룸)

import { IsNumberString } from 'class-validator';

export class FindOneParams {
  @IsNumberString()
  id: number;
}

number 타입인 id로 들어오는 데이터가 number string인지 validate하고 있다.

❓ 그런데 이 코드는 의도대로 동작하지 않는다.
위처럼 id 타입을 number로 설정하더라도 실제로는 number string 값이 저장되지 자동으로 number로 변환되지 않는다.
게다가, 아마 양의 정수만 받는 상황일 것 같은데, -123.45 같은 값도 그대로 저장된다.

그럼 이 경우 어떻게 해야 id의 타입에 맞게 자동 변환될까❓

자동 변환

❗️ 먼저 아래와 같은 방법으로 기능을 활성화해야 한다. 관련 깃헙 이슈

// main.ts
async function bootstrap() {
  // ...
  
  app.useGlobalPipes(
    new ValidationPipe({
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  );
  
  // ...
}

이렇게 하고 나면 이제 number string 값이 number로 자동 변환된다.

그런데 변환된 후에 validation이 적용되기 때문에 지금 상태로는 @IsNumberString()에 막혀 에러 응답이 반환된다.
기존의 데코레이터를 지우고 양의 정수만 허용하도록 해 보자.

import { IsInt, IsPositive } from 'class-validator';

export class FindOneParams {
  @IsInt()
  @IsPositive()
  id: number;
}

다 되었다. 이제 url에 id 값으로 양의 정수 형식의 값을 넣었을 때만 통과된다.

참고로,
양의 정수 형식이 아닌 값이 들어오더라도 일단은 number로 변환되고(number string이면 값 그대로, 아니면 값이 NaN으로), 그 후 validation이 적용된다.

직접 받는 경우

위처럼 object를 통하지 않고 아래처럼 직접 받을 수도 있다.

@Get(':id')
findOne(@Param('id') id: number) {
  return 'This action returns a user';
}

참고로, 이때는 ValidationPipe에 전달하는 configuration object에 transform: true만 있어도 자동으로 number로 변환된다.

❓ 그런데 이러면 자동 validation이 안 된다.
@IsInt() 같은 것들을 파라미터 id에 붙여줄 수 없다.

그럼 이 경우 자동 validation을 적용하려면 어떻게 해야 할까❓

Validation

❗️ pipe도 함께 사용해야 한다.
built-in pipe들 중 ParseIntPipe를 두 가지 방법으로 사용해 보겠다.

@Get(':id')
@UsePipes(new ParseIntPipe()) // 추가
findOne(@Param('id') id: number) {
  return 'This action returns a user';
}
@Get(':id')
findOne(@Param('id', new ParseIntPipe()) id: number) { // 전달할 option이 없을 때는 그냥 @Param('id', ParseIntPipe)처럼 좀 더 간단히 표현 가능
  return 'This action returns a user';
}

❓ 그런데 이러면 음의 정수까지 허용된다.

양의 정수만 허용하려면 어떻게 해야 할까❓

Custom pipes

❗️ 아래처럼 custom pipe를 만들어 사용해야 한다. ( ParseIntPipe를 참고하여 작성해 보았음 )

// parse-positive-int.pipe.ts
import {
  ArgumentMetadata,
  HttpStatus,
  Injectable,
  PipeTransform,
} from '@nestjs/common';
import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util';

@Injectable()
export class ParsePositiveIntPipe implements PipeTransform {
  /**
   * Method that accesses and performs transformation on argument for
   * in-flight requests.
   *
   * @param value currently processed route argument
   * @param metadata contains metadata about the currently processed route argument
   */
  transform(value: any, metadata: ArgumentMetadata) {
    if (!this.isNumeric(value)) {
      throw new HttpErrorByCode[HttpStatus.BAD_REQUEST](
        'Validation failed: numeric(positive integer) string is expected)',
      );
    }
    return parseInt(value, 10);
  }

  /**
   * @param value currently processed route argument
   * @returns `true` if `value` is a valid positive integer number
   */
  protected isNumeric(value: string): boolean {
    return (
      ['string', 'number'].includes(typeof value) &&
      /^(?!0$)\d+$/.test(value) && // 여기서 양의 정수만 허용
      isFinite(value as any)
    );
  }
}

참고로, transform()에서 반환하는 부분을 보면 알겠지만,
이렇게 pipe를 사용하면 ValidationPipe에 전달하는 configuration object에 transform: true가 없어도 자동으로 변환된다.

post-custom-banner

0개의 댓글