[고스락 티켓 2.0] nest js swagger 같은 코드 여러 응답 예시 만들기 (3) - 에러응답 데코레이터 만들기
이전 포스팅들이다 서론은 생략하고 바로 들어가겠숩니다!
이글을 통해서 얻을 수 있는 점들
- 같은 코드에 여러 성공응답 예시를 작성하는 방법
- 공통 에러응답과 , 에러내용까지 응답예시로 보여주는 방법
들어가기 전에
고티켓 프로젝트에서는 검증오류를 커스텀해서 쓰고 있으니 참고하시길 바란다.!
에러응답 dto
import { EnumToArray } from '../utils/enumNumberToArray';
export class ErrorCommonResponse<T> {
@ApiProperty({ enum: EnumToArray(HttpStatus), description: '상태코드' })
@Expose()
readonly statusCode: number;
@ApiProperty({ type: String, description: '에러 발생시간' })
@Expose()
readonly timestamp: Date;
@ApiProperty({ type: String, description: '에러 발생 url' })
@Expose()
readonly path: string;
@ApiProperty({ type: String, description: '에러 발생 메소드' })
@Expose()
readonly method: string;
@ApiProperty({
type: 'generic',
description:
'HttpExceptionErrorResponseDto,ValidationErrorResponseDto 두가지가 올수있습니다.'
})
@Expose()
error: T;
}
src/common/errors/ErrorCommonResponse.dto.ts
공통응답 디티오이다. error 안에 HttpExceptionErrorResponseDto , ValidationErrorResponseDto 두개가 올수있고
HttpExceptionErrorResponseDto 는 HttpException을 extends 한 에러들의 응답 디티오이다.
error 는 제네릭 형태이지만 제네릭이 컴파일 타임에 타입만 도와주는거라 실제로 타입정보를 얻어올수가없다.
따라서 type을 그냥 문자열 generic으로 적고 makeInstanceByApiProperty 함수에서 별도로 처리를 한다.
export class HttpExceptionErrorResponseDto {
@ApiProperty({
enum: HttpErrorNameEnum,
description: '에러명'
})
@Expose()
error: string;
@ApiProperty({
type: String,
description: '에러메시지'
})
@Expose()
message: string;
@ApiProperty({
enum: EnumToArray(HttpStatus),
description: '상태코드 400~500번대만 봐주세용'
})
@Expose()
statusCode: number;
@ApiProperty({
type: String,
description: '에러코드가 넘어옵니다. 널값일 수 있습니다!!!',
nullable: true
})
@Expose()
code?: string;
constructor(
statusCode: number,
error: string,
message: string,
code?: string
) {
this.error = error;
this.statusCode = statusCode;
this.message = message;
this.code = code;
}
}
srv/common/errors/HttpExceptionError.response.dto.ts
HttpExceptionErrorResponseDto 는 HttpException을 extends 한 에러들의 응답 디티오이다.
code를 통해서 에러 코드화를 시킨 부분도 있다.
export class ValidationErrorResponseDto {
@ApiProperty({
type: String,
description: '에러명',
example: 'ValidationError'
})
@Expose()
error = 'ValidationError';
@ApiProperty({
type: String,
description: '밸리데이션 에러는 코드도 ValidationError입니다.',
example: 'ValidationError'
})
@Expose()
code = 'ValidationError';
@ApiProperty({
type: String,
description: '에러메시지',
example: '검증오류'
})
@Expose()
message = '검증오류';
@ApiProperty({
type: Number,
description: '400 검증오류 고정',
example: 400
})
@Expose()
statusCode = 400;
@ApiProperty({
// type: { fieldName: ['errorinfoOfString'] },
description: '필드 : [에러정보] 형식의 에러정보가 담긴 객체입니다.',
example: { fieldName: ['errorinfoOfString'] }
})
@Expose()
validationErrorInfo: Record<string, Array<string>>;
constructor(validationErrorInfo: Record<string, Array<string>>) {
this.validationErrorInfo = validationErrorInfo;
}
}
srv/common/errors/ValidationError.response.dto.ts
검증오류 커스텀 에러 응답 디티오이다. validationErrorInfo 에 필드 : ["에러정보", "에러정보2"] 이런형식의 object가 들어간다.
ErrorResponse.decorator.ts
export interface ErrorResponseOption {
/**
* HttpException을 extend한 에러 타입을 인자로 받습니다.
* 예시 : BadRequestException
*/
model: Type<HttpException>;
/**
* 예시의 제목을 적습니다
*/
exampleTitle: string;
/**
* 서비스 레이어에서 적었던 오류 메시지를 기술합니다.
*/
message: string | Record<string, Array<string>>;
/**
* 어떠한 상황일 때 오류가나는지 기술합니다.
*/
exampleDescription: string;
/**
* 에러 코드에 대해 기술합니다.
*/
code?: string;
}
model을 통해서 401 ,400 , 403 등 HttpException 을 extends 한 에러를 인자로 받는다
표시될 에러의 제목을 적고
message를 통해서 에러메시지를 적는다
code 부분은 에러코드를 기술해서 클라이언트단에서 에러메시지를 좀 커스텀하고싶으면 이부분을 통해서 하면된다.
export const ErrorResponse = (
StatusCode: HttpStatus,
errorResponseOptions: ErrorResponseOption[]
) => {
let flagValidationErrorExist = false;
const examples = errorResponseOptions
.map((error: ErrorResponseOption) => {
let innerErrorDto;
if (error.model === CustomValidationError) {
flagValidationErrorExist = true;
if (typeof error.message === 'string') {
throw Error(
'검증오류는 넘겨줄때 Record<string, Array<string>> 타입으로 주셔야합니다.'
);
}
innerErrorDto = new ValidationErrorResponseDto(error.message);
} else {
if (typeof error.message !== 'string') {
throw Error('http오류는 넘겨줄때 string 타입으로 주셔야합니다.');
}
innerErrorDto = new HttpExceptionErrorResponseDto(
StatusCode,
error.model.name,
error.message,
error.code
);
}
const commonErrorInstance =
makeInstanceByApiProperty<ErrorCommonResponse<any>>(
ErrorCommonResponse
);
commonErrorInstance.error = innerErrorDto;
return {
[error.exampleTitle]: {
value: commonErrorInstance,
description: error.exampleDescription
}
};
})
.reduce(function (result, item) {
Object.assign(result, item);
return result;
}, {}); // null 값 있을경우 필터링
//console.log(examples);
return applyDecorators(
ApiExtraModels(
ErrorCommonResponse,
HttpExceptionErrorResponseDto,
ValidationErrorResponseDto
),
ApiResponse({
status: StatusCode,
content: {
'application/json': {
schema: {
additionalProperties: { $ref: getSchemaPath(ErrorCommonResponse) },
oneOf: flagValidationErrorExist
? [
{ $ref: getSchemaPath(ValidationErrorResponseDto) },
{ $ref: getSchemaPath(HttpExceptionErrorResponseDto) }
]
: [{ $ref: getSchemaPath(HttpExceptionErrorResponseDto) }]
},
examples: examples
}
}
})
);
};
전편이랑 비슷한이야기다!
밸리데이션 에러이면 벨리데이션 에러까지표현해주고, 없으면 HttpException 까지 포현해준다.
2편에서도 성공응답데코레이터에서 사진하나하나 찍어가며 설명해놓았으니 참고하기 바란다!
이로써 1편,2편,3편을통해
@ApiProperty 로적은 정보들을 메타데이터를 활용해서 가져와서
예시값을 가진 객체를 생성해서 스웨거에 같은 코드에 여러 응답을 나타내줄 수있는 기능을 만들었다!
'Auth-1000': {
model: UnauthorizedException,
exampleDescription: '헤더 Bearer 형식을 지키지 않고 잘못 요청 보냈을 때',
exampleTitle: '어세스토큰-잘못된 헤더 요청',
message: '잘못된 헤더 요청',
code: 'Auth-1000'
},
'Auth-1001': {
model: UnauthorizedException,
exampleDescription:
'손상되거나 Bearer 형식은 맞췄는데 토큰이 이상한 토큰일 때',
exampleTitle: '어세스토큰-잘못된 토큰',
message: '잘못된 토큰',
code: 'Auth-1001'
},
'Auth-1002': {
model: UnauthorizedException,
exampleDescription: '기한이 지난 토큰일때',
exampleTitle: '어세스토큰-기한만료',
message: '기한만료',
code: 'Auth-1002'
},
'Auth-1003': {
model: UnauthorizedException,
exampleDescription:
'어세스토큰은 살아있지만 db에서 유저가 삭제되었을때 (테스트할때 발생할수있는 오류)',
exampleTitle: '어세스토큰-유저없음',
message: '없는 유저입니다.',
code: 'Auth-1003'
},
src/auth/Errors/AuthErrorDefine.ts
요번엔 위처럼 주요 에러들을 코드화해서 나타냈는데
실제로 프론트단에서 코드로 에러잡어서 메시지를 커스텀하던가 공통으로 에러관리를 하고있다.
그람이만!